Files
mobile_comm/luatos/air780epm/module/Air780EPM/demo/ws2812/ws2812_fonts.lua
T
wangfq 173dd6874f init: Air780EPM 官方 LuatOS 项目代码基线
- 来源: 合宙 LuatOS 官方仓库 air780epm 模块完整代码
- 路径: luatos/air780epm/module/Air780EPM/demo 含官方 demo(含 mqtt/mqtts/socket/uart 等)
- 后续: 基于 demo 开发 UART<->MQTT 数据上报功能
2026-08-31 08:53:49 +08:00

360 lines
16 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
--[[
@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