init: Air780EPM 官方 LuatOS 项目代码基线

- 来源: 合宙 LuatOS 官方仓库 air780epm 模块完整代码
- 路径: luatos/air780epm/module/Air780EPM/demo 含官方 demo(含 mqtt/mqtts/socket/uart 等)
- 后续: 基于 demo 开发 UART<->MQTT 数据上报功能
This commit is contained in:
wangfq
2026-08-31 08:53:49 +08:00
commit 173dd6874f
813 changed files with 125484 additions and 0 deletions
@@ -0,0 +1,359 @@
--[[
@module ws2812_fonts
@summary WS2812 演示使用的字模数据与文本条带构建(中文 22×22 / 英文 22×11 / ASCII 5×7
@version 1.0
@date 2026.08.17
@usage
适用产品:合宙 Air1780P / Air1780H / Air1780HV。
字模尺寸与灯板 22×22 强绑定,灯板尺寸非 22×22 时需重新生成字模。
本模块由 ws2812_scroll_task 调用,提供:
WS2812_FONT5x7 5×7 ASCII 小字模(列优先,bit0=顶行),放大 2× 后渲染
WS2812_FONT_CN 中文 22×22 字模(欢迎使用),行优先字符串
WS2812_FONT_EN22 英文半宽 22×11 大字模(LuatOS),行优先字符串
ws2812_build_strip(text) 将 UTF-8 文本打包为列掩码数组
最终输出"列掩码数组" columns:每个元素是一个 22-bit 整数,
第 y 位为 1 表示该列第 y 行的 LED 点亮。scroll_task 按列偏移遍历即可渲染。
]]
-- ====== 5×7 ASCII 字模(列优先,bit0=顶行) ======
-- 格式说明:
-- 每个字符是一个含 5 个字节的数组,分别对应 5 列;
-- 每个字节的 bit0~bit6 对应该列自顶向下的 7 行,
-- 即 bit0=最顶行、bit6=最底行,置 1 表示该像素点亮。
-- 渲染时会通过 expand_small_col 将 7 行放大为 14 行(每行翻倍),
-- 再垂直居中下移 EN_VOFFSET_SMALL 排,最终得到约 14 像素高、10 像素宽的字符。
-- 键名使用 ASCII 码(如 65 = 'A')。
local WS2812_FONT5x7 = {
[65] = {0x7E,0x09,0x09,0x09,0x7E}, -- A
[66] = {0x7F,0x49,0x49,0x49,0x36}, -- B
[67] = {0x3E,0x41,0x41,0x41,0x22}, -- C
[68] = {0x7F,0x41,0x41,0x22,0x1C}, -- D
[69] = {0x7F,0x49,0x49,0x49,0x41}, -- E
[70] = {0x7F,0x09,0x09,0x09,0x01}, -- F
[71] = {0x3E,0x49,0x49,0x49,0x7A}, -- G
[72] = {0x7F,0x08,0x08,0x08,0x7F}, -- H
[73] = {0x00,0x41,0x7F,0x41,0x00}, -- I
[74] = {0x20,0x41,0x41,0x3F,0x00}, -- J
[75] = {0x7F,0x08,0x14,0x22,0x41}, -- K
[76] = {0x7F,0x40,0x40,0x40,0x40}, -- L
[77] = {0x7F,0x02,0x04,0x02,0x7F}, -- M
[78] = {0x7F,0x04,0x08,0x10,0x7F}, -- N
[79] = {0x3E,0x41,0x41,0x41,0x3E}, -- O
[80] = {0x7F,0x09,0x09,0x09,0x06}, -- P
[81] = {0x3E,0x41,0x51,0x21,0x5E}, -- Q
[82] = {0x7F,0x09,0x19,0x29,0x46}, -- R
[83] = {0x46,0x49,0x49,0x49,0x31}, -- S
[84] = {0x01,0x01,0x7F,0x01,0x01}, -- T
[85] = {0x3F,0x40,0x40,0x40,0x3F}, -- U
[86] = {0x1F,0x20,0x40,0x20,0x1F}, -- V
[87] = {0x3F,0x40,0x38,0x40,0x3F}, -- W
[88] = {0x63,0x14,0x08,0x14,0x63}, -- X
[89] = {0x07,0x08,0x70,0x08,0x07}, -- Y
[90] = {0x71,0x69,0x55,0x4D,0x47}, -- Z
[97] = {0x78,0x14,0x14,0x14,0x78}, -- a
[116] = {0x04,0x04,0x7F,0x44,0x44}, -- t
[117] = {0x3C,0x40,0x40,0x40,0x7C}, -- u
}
-- ====== 中文 22×22 字模(行优先字符串,# = 亮,. = 灭) ======
-- 格式说明:
-- 每个字符由 22 个字符串组成,每个字符串固定 22 个字符宽;
-- 字符串数组顺序是行序(第 1 个字符串 = 最顶行),
-- 字符串内从左到右是列序,'#' 表示该像素亮、'.' 表示灭。
-- 渲染时按列扫描:col_mask(glyph, col, voffset) 对每一行取该列字符,
-- 为 '#' 就把对应行位置 1,最终拼成一个 22-bit 的列掩码。
-- 键名是 UTF-8 中文字符串(占 3 字节),build_strip 中用 string.sub 取出 3 字节作为键。
local WS2812_FONT_CN = {
[""] = {
"......................",
"............##........",
"...........###........",
"...........##.........",
"..#######..##.........",
"..#######..#########..",
".......##.##########..",
".......##.##......##..",
"..###..#..##.##..##...",
"...##.##.##..##.......",
"....####.....##.......",
"....###......##.......",
".....###.....###......",
"....####.....###......",
"....#####...####......",
"...###.##...##.##.....",
"...##...##.###.###....",
"..##......###....###..",
".###.....###.....####.",
"........###.......###.",
"........##............",
"......................",
},
[""] = {
"......................",
"...#.......#..........",
"..##....############..",
"..###..###...#######..",
"...##..##....##...##..",
"...##..##....##...##..",
".......##....##...##..",
".......##....##...##..",
".####..##....##...##..",
".####..##....##...##..",
"...##..##....##...##..",
"...##..##....##...##..",
"...##..##....##...##..",
"...##..##..#.##...##..",
"...##..#####.##..###..",
"...##..####..##.###...",
"...##..##....##.......",
"...##........##.......",
"...###.......##.......",
"..#######.............",
".###..###############.",
".##.......###########.",
},
["使"] = {
"......................",
".....#......##........",
"....##......##........",
"....##......#.........",
"....##.##############.",
"...##...############..",
"...##........#........",
"..###...############..",
"..###...############..",
".####...#....#....##..",
".##.#...#....#....##..",
"....#...############..",
"....#...###########...",
"....#...##..##........",
"....#...###.##........",
"....#....#####........",
"....#.....####........",
"....#......###........",
"....#....########.....",
"....#...###...#######.",
"....#..###.......###..",
"......................",
},
[""] = {
"......................",
"......................",
"...################...",
"...################...",
"...##.....##.....##...",
"...##.....##.....##...",
"...##.....##.....##...",
"...################...",
"...################...",
"...##.....##.....##...",
"...##.....##.....##...",
"...##.....##.....##...",
"...################...",
"...################...",
"...##.....##.....##...",
"...##.....##.....##...",
"...##.....##.....##...",
"..##......##.....##...",
"..##......##....###...",
".###......##...####...",
"..#...................",
"......................",
},
}
-- ====== 英文半宽大字模 22 行 × 11 列(行优先字符串) ======
-- 格式说明:
-- 每个字符由 22 个字符串组成,每个字符串宽 11 字符(半宽,约为中文的一半);
-- 行序、'#'/'.' 含义与中文字模一致。
-- 渲染时通过 col_mask(big, col, 2) 扫描每一列,voffset=2 下移 2 排,
-- 使其与 22×22 中文在视觉基线上对齐(英文上下留白较多)。
-- 键名使用 ASCII 码(如 76 = 'L'),仅包含本演示用到的 L/u/a/t/O/S。
local WS2812_FONT_EN22 = {
[76] = { -- L
"...........","...........","..##.......","..##.......","..##.......",
"..##.......","..##.......","..##.......","..##.......","..##.......",
"..##.......","..##.......","..##.......","..##.......","..########.",
"..########.","...........","...........","...........","...........",
"...........","...........",
},
[117] = { -- u
"...........","...........","...........","...........","...........",
".##.....##.",".##.....##.",".##.....##.",".##.....##.",".##.....##.",
".##.....##.",".##.....##.",".##....###.",".###..####.","..########.",
"..#####.##.","...........","...........","...........","...........",
"...........","...........",
},
[97] = { -- a
"...........","...........","...........","...........","...........",
"...#####...","..#######..","..#....###.","........##.","...#######.",
".#########.",".###....##.",".##.....##.",".###..####.",".#########.",
"..#####.##.","...........","...........","...........","...........",
"...........","...........",
},
[116] = { -- t
"...........","...........","...##......","...##......","...##......",
"##########.","##########.","...##......","...##......","...##......",
"...##......","...##......","...##......","...###.....","....######.",
".....#####.","...........","...........","...........","...........",
"...........","...........",
},
[79] = { -- O
"...........","...........","...#####...","..########.",".###...###.",
".##.....###","###......##","##.......##","##.......##","##.......##",
"##.......##","##.......##","###.....###",".###...###.",".########..",
"...#####...","...........","...........","...........","...........",
"...........","...........",
},
[83] = { -- S
"...........","...........","...######..","..#######..",".###....#..",
".##........",".###.......",".####......","..#####....","....#####..",
".......###.","........##.","........##.",".#.....###.",".#########.",
"..######...","...........","...........","...........","...........",
"...........","...........",
},
}
-- ====== 条带尺寸常量 ======
-- 条带虚拟高度(与 ws2812_config 中的 LED_H 对齐,均为 22 行)。
-- 列掩码最多使用 bit0~bit21,超过 STRIP_H 的位会被 col_mask 丢弃。
local STRIP_H = 22
-- 5×7 小字模放大 2× 后垂直居中下移的排数。
-- 计算方式:5×7 字形高 7 像素,放大 2× 后高 14 像素;
-- 在 22 像素高的条带里垂直居中,需要下移 (22-14)/2 = 4 排。
local EN_VOFFSET_SMALL = 4 -- 5×7 英文放大 2× 后下移居中
-- ====== 5×7 列字节 → 放大 2× 后的位掩码 ======
--- 将 5×7 字模的单列字节(7 像素)放大为 14 像素高的位掩码
-- 放大规则:每个原始像素在垂直方向翻倍占两行(2× 缩放)。
-- 例:原 bit r 为 1,则在结果掩码中置位 (r*2+voff) 和 (r*2+voff+1) 两个 bit。
-- 使用 (3 << ((r*2)+voff))3 的二进制是 11,左移后恰好是相邻两个 1。
-- @param byte integer 5×7 字模某一列的字节(bit0=顶行)
-- @return integer 放大并垂直居中后的 22-bit 列掩码
local function expand_small_col(byte)
local mask = 0
-- 遍历 7 行
for r = 0, 6 do
-- (byte >> r) & 1 取出第 r 位
if (byte >> r) & 1 == 1 then
-- 3 = 二进制 11,左移 (r*2)+EN_VOFFSET_SMALL 位:
-- 原始第 r 像素 → 放大后占第 (r*2+voff) 和 (r*2+voff+1) 两排
-- |= 把这两个 bit 合并进总掩码
mask = mask | (3 << ((r * 2) + EN_VOFFSET_SMALL))
end
end
return mask
end
-- ====== 行优先字模 → 单列位掩码 ======
--- 从行优先字符串字模中取出某一列,拼成列掩码
-- 双向越界保护:r + off 可能小于 0(voffset 为负、字形靠上)
-- 或大于等于 STRIP_H(字形靠下超出 22 行),这两种情况都跳过,
-- 避免位运算产生无效 bit 或写到条带范围外。
-- @param glyph table 字模表(22 个字符串,行序)
-- @param col integer 要取的列号(0 起始)
-- @param voffset integer 垂直偏移(可正可负,nil 视为 0)
-- @return integer 22-bit 列掩码
local function col_mask(glyph, col, voffset)
local mask = 0
local h = #glyph
local off = voffset or 0
-- 遍历字形的每一行
for r = 0, h - 1 do
-- 取出第 r 行、第 col 列的字符;Lua 字符串索引从 1 开始故 +1
local ch = string.sub(glyph[r + 1], col + 1, col + 1)
if ch == "#" then
-- 该像素亮:计算它落在条带中的实际行号
local bit = r + off
-- 双向越界保护:bit>=0 防止负偏移(左移负数位未定义),
-- bit<STRIP_H 防止超出 22 行虚拟高度
if bit >= 0 and bit < STRIP_H then
mask = mask | (1 << bit)
end
end
end
return mask
end
-- ====== UTF-8 文本 → 列掩码数组 ======
--- 将 UTF-8 文本打包成滚动条带用的列掩码数组
-- UTF-8 编码要点:
-- * ASCII 字符(0x00~0x7F)占 1 字节
-- * 0x80 及以上的字节是多字节序列的起始或后续字节
-- * 常用中文在 UTF-8 中占 3 字节(1110xxxx 10xxxxxx 10xxxxxx
-- 为什么用 while 而不是 for
-- Lua 数值 for 循环在循环开始时就固化了起始/终止/步长,
-- 循环体内对 i 的赋值(如 i=i+3)不会改变下一次迭代值,
-- 无法实现"中文跳 3 字节、ASCII 走 1 字节"的变长步进;
-- 而 while + 手动 i=i+n 可以精确控制每次前进的字节数。
-- ASCII 字符的字模优先级:
-- 1) 先查 WS2812_FONT_EN2222×11 大尺寸英文字模,最清晰)
-- 2) 查不到再回退到 WS2812_FONT5x7(5×7 小字模,放大 2× 后使用)
-- @param text string UTF-8 编码的文本
-- @return table 列掩码数组(每元素是 22-bit 整数,含字间距空白列)
function ws2812_build_strip(text)
local columns = {}
local i = 1
-- while 手动步进:i 在中文分支 +3ASCII 分支 +1
while i <= #text do
local byte = string.byte(text, i)
if byte >= 0x80 then
-- ====== 多字节分支:按中文 3 字节处理 ======
-- 取当前位置起连续 3 个字节,得到一个完整的 UTF-8 中文字符串,
-- 直接用作 WS2812_FONT_CN 的键
local full = string.sub(text, i, i + 2)
local glyph = WS2812_FONT_CN[full]
-- 中文字模标准宽度 22 列;查不到字模时也保留 22 列空白占位,
-- 避免后续列错位(不会因为缺字把后面的字符挤到前面)
local gw = glyph and #glyph[1] or 22
for c = 0, gw - 1 do
-- voffset=0:中文 22×22 正好占满条带高度,无需垂直偏移
columns[#columns + 1] = glyph and col_mask(glyph, c, 0) or 0
end
-- 中文占 3 字节,前进 3
i = i + 3
else
-- ====== ASCII 分支:1 字节 ======
-- 优先尝试 22×11 大字模
local big = WS2812_FONT_EN22[byte]
if big then
local ew = #big[1]
for c = 0, ew - 1 do
-- voffset=2:英文字模整体下移 2 排,
-- 让英文视觉基线与旁边的中文对齐(英文字形上下留白较多)
columns[#columns + 1] = col_mask(big, c, 2)
end
else
-- 回退到 5×7 小字模:放大 2× 后渲染
local glyph = WS2812_FONT5x7[byte]
if glyph then
-- 5×7 共 5 列
for c = 0, 4 do
local m = expand_small_col(glyph[c + 1])
-- 水平也放大 2×:每列重复写两次,得到 10 像素宽
columns[#columns + 1] = m
columns[#columns + 1] = m
end
end
-- 若 5×7 也没有该字符,则什么都不输出(不占列、字符直接消失)
end
-- ASCII 占 1 字节,前进一步
i = i + 1
end
-- ====== 字间距:每个字符后追加 2 列空白 ======
-- 0 表示该列没有任何像素点亮,形成字符间的可见间隔,
-- 避免中英文挤在一起难以辨认
columns[#columns + 1] = 0
columns[#columns + 1] = 0
end
return columns
end
-- 导出条带高度给 scroll_task 使用(渲染时遍历 0~WS2812_STRIP_H-1 行)
WS2812_STRIP_H = STRIP_H