- 来源: 合宙 LuatOS 官方仓库 air780epm 模块完整代码 - 路径: luatos/air780epm/module/Air780EPM/demo 含官方 demo(含 mqtt/mqtts/socket/uart 等) - 后续: 基于 demo 开发 UART<->MQTT 数据上报功能
360 lines
16 KiB
Lua
360 lines
16 KiB
Lua
--[[
|
||
@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_EN22(22×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 在中文分支 +3,ASCII 分支 +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
|