在小内存嵌入式 MCU(如片内 Flash 512 KB、RAM 144 KB)的多语言界面中,直接嵌入完整中文字库会迅速耗尽 Flash 空间。本文介绍一套按需提取 UI 字符子集的 1-bit 点阵字库生成与光栅化流水线,使用 16 px 原生点阵和 24 px/40 px 定制字形,五套字库仅占约 19.2 KB Flash,即可实现高清晰度多语言显示。页面条带渲染原理见 N32G455 LCD 条带渲染与异步刷新实现分析。
字号规划与 Flash 体积预算
在 512 KB Flash 的 MCU 上,如果直接嵌入完整 GB2312 或 Big5 字库,仅单个字号就会消耗数百 KB 空间。因此工程按 UI 视觉层级定义了 5 套按需子集化的 1-bit 点阵字库:
| 字库模块 | 像素尺寸 | 界面用途 | 收录字符范围 | 字形数量 | 点阵体积 |
|---|---|---|---|---|---|
font_16 |
汉字 16×16,ASCII 8×16 等宽 |
简中/英文菜单正文、底栏、状态标签 | 可打印 ASCII + 简中 UI 汉字 + ± |
278 | 7,360 B |
font_16_tw |
汉字 16×16 |
台湾繁体菜单正文、底栏、状态标签 | 当前繁体 UI 使用的全部汉字 | 182 | 5,824 B |
font_24 |
汉字 24×24,ASCII 12×24 等宽 |
简中页面标题 | 简中菜单及常用小节标题字 | 47 | 2,952 B |
font_24_tw |
汉字 24×24 |
台湾繁体页面标题 | 当前繁体标题使用的全部汉字 | 29 | 2,088 B |
font_num_40 |
24×40 等宽 |
首页主读数大数字 | 仅 0123456789.- |
12 | 1,440 B |
五套字库点阵数据合计仅 19,664 字节(约 19.2 KiB),全部以 const uint8_t 存入片内 Flash,运行期零 RAM 占用。
1-bit 点阵二进制格式
所有字库统一采用 1 bit/pixel、行优先(Row-major)、高位在左(MSB leftmost) 的紧凑编码格式:
- 每个字节的最高位(
bit7)对应该行最左侧像素,bit0对应右侧第 8 个像素; - 单行像素宽度不足 8 的整数倍时,行末低位补
0; - 每行字节数计算公式:
stride = (width + 7) / 8; - 单个字形总字节数:
size = stride × height。
典型字形尺寸计算示例:
16×16 汉字:stride = (16 + 7) / 8 = 2 B/行 → 单个字形 = 2 × 16 = 32 B
8×16 ASCII:stride = (8 + 7) / 8 = 1 B/行 → 单个字形 = 1 × 16 = 16 B
24×24 汉字:stride = (24 + 7) / 8 = 3 B/行 → 单个字形 = 3 × 24 = 72 B
24×40 数字:stride = (24 + 7) / 8 = 3 B/行 → 单个字形 = 3 × 40 = 120 B
为什么 16 px 繁体汉字必须用原生点阵而非矢量缩放
在 16×16 的极低分辨率下,台湾繁体字(如“歸”“斷”“輸”“壓”“籤”)笔画极其密集。如果直接将桌面端 TrueType/OpenType 矢量字体缩放到 16 px 再做二值化阈值裁剪,相邻横画与撇捺会严重粘连成实心黑块,导致小屏幕完全无法辨认。
为此,字库生成流水线采用了按字号分源的策略:
| 字库类别 | 字形上游来源 | 选择原因与质量控制 |
|---|---|---|
简体汉字 16×16 / 24×24 |
U8g2 收录的 GB16ST / GB24ST 点阵宋体 |
经典手工修整点阵,横平竖直、笔画清晰 |
拉丁字母与符号 8×16 / 12×24 |
Spleen 等宽点阵字库 | 专为低分屏设计的像素等宽西文字体 |
台湾繁体汉字 16×16 |
GNU Unifont 18.0.01(T-source Chinese)原生点阵 | 直接提取原生 16×16 像素手工点阵,不做任何缩放、抗锯齿或二值化,避免缩放、二值化造成的繁体字笔画粘连 |
台湾繁体标题汉字 24×24 |
Noto Serif TC Regular(23 px 栅格化) | 24 px 空间充足;先在带 8 px 外扩安全区的画布中完整绘制再裁入 24×24 字框,若任一有效像素越界则脚本直接报错中断 |
主读数大数字 24×40 |
JetBrains Mono Bold 定制点阵 | 等宽数字与小数点/负号,保证力值快速跳变时数字列绝不左右晃动 |
此外,构建脚本对上游标准字库文件执行 SHA-256 哈希强校验,确保在不同开发环境或 CI 中重新生成都能得到逐字节完全一致的 C 源码数组。
1-bit 点阵字库生成与光栅化排障实验室
纯前端交互演示/* 正在生成 C99 const uint8_t 数组... */
Unicode 升序索引表与二分查找
为了在 ARMCC 5 下避免 GBK/UTF-8 源文件编码混用问题,所有字库检索表统一按 Unicode 码点(uint16_t codepoint)升序排列:
typedef struct
{
uint16_t codepoint; /* Unicode 码点,如 '荷' = 0x8377 */
uint8_t width; /* 字形像素宽度:ASCII 为 8/12,汉字为 16/24 */
uint8_t size; /* 字形点阵字节数 */
uint16_t offset; /* 在 font_xx_bitmap[] 数组中的起始字节偏移 */
} font_16_glyph_t;
查找单个字符时,font_16_find(uint16_t codepoint) 对升序表执行 O(log N) 二分查找(278 个字形最多比较 9 次即可命中):
const font_16_glyph_t *g = font_16_find(0x8377U); /* 查找 '荷' */
if (g != NULL)
{
const uint8_t *bits = font_16_bitmap + g->offset;
/* 使用 g->width, FONT_16_HEIGHT (16), g->size 进行光栅化 */
}
对于仅含 12 个字符(0123456789.-)的主读数字库 font_num_40,则直接通过 font_num_40_index(char ch) 映射为 0..11 的常数下标,实现 O(1) 零开销寻址。
多语言运行时切换与缺字安全回退
固件支持简体中文(0)、英文(1)、台湾繁体中文(2)三语实时切换:
- ASCII 与
±共享主字库:繁体字库font_16_tw和font_24_tw仅存储繁体汉字点阵;绘制繁体字符串遇到 ASCII 字母、数字或±时,自动回落到font_16/font_24查找,避免重复存储西文字形。 - 整句缺字检查与回退保护:返回繁体 UI 文案前,渲染层会先扫描该 UTF-8 字符串中的每个汉字是否均存在于对应繁体字库中;一旦发现任一字符未收录,整条文案自动回退为对应的简体中文字符串,避免屏幕上出现空白方框或乱码。
- 固定边界防溢出:英文标签使用紧凑缩写(如
PEAK、TRACK),与中繁文案共用同一套行高和列边界,切换语言时无需重算菜单布局坐标。
在 RGB565 条带画布中光栅化 1-bit 字形
在 240×8 的条带渲染架构下,字形绘制函数 AppUi_DrawGlyphBits() 只光栅化当前条带 [stripY, stripY + 7] 与字形外框 [glyphY, glyphY + glyphH - 1] 相交的那几行:
static void AppUi_DrawGlyphBits(int16_t x, int16_t y,
uint8_t width, uint8_t height,
const uint8_t *bits, uint16_t color)
{
uint8_t stride = (uint8_t)((width + 7U) >> 3U);
uint8_t row;
uint8_t col;
for (row = 0U; row < height; row++)
{
int16_t drawY = (int16_t)(y + (int16_t)row);
/* 仅处理落在当前 8 行条带内的扫描行 */
if ((drawY < (int16_t)appUiCanvas.y) ||
(drawY >= (int16_t)(appUiCanvas.y + appUiCanvas.height)))
{
continue;
}
const uint8_t *rowBits = bits + ((uint16_t)row * stride);
uint16_t *dstRow = &appUiCanvas.pixels[(uint16_t)(drawY - (int16_t)appUiCanvas.y) * 240U];
for (col = 0U; col < width; col++)
{
int16_t drawX = (int16_t)(x + (int16_t)col);
if ((drawX >= 0) && (drawX < 240))
{
if ((rowBits[col >> 3U] & (uint8_t)(0x80U >> (col & 7U))) != 0U)
{
dstRow[drawX] = color;
}
}
}
}
}
Unicode 二分查找与字形索引检索
子集化字库按 Unicode 码点升序存储。在嵌入式 C 端,使用二分查找在 O(log N) 时间内完成字形定位:
/* Unicode 升序索引表二分查找字形偏移 */
int32_t Font_FindGlyphIndex(const uint16_t *unicodeTable, uint16_t count, uint16_t codePoint)
{
int32_t low = 0;
int32_t high = (int32_t)count - 1;
while (low <= high)
{
int32_t mid = low + ((high - low) >> 1);
if (unicodeTable[mid] == codePoint)
{
return mid; /* 命中字形偏移索引 */
}
else if (unicodeTable[mid] < codePoint)
{
low = mid + 1;
}
else
{
high = mid - 1;
}
}
return -1; /* 未收录字符,触发缺字回退 */
}
自动化字库提取与代码生成脚本
通过标准 Python 脚本解析 BDF/TTF 字体,提取文案涉及的字符并生成 C99 数组:
# bdf_subset_generator.py (轻量生成流水线核心实现)
def bdf_glyph_to_c_bytes(hex_lines, width, height):
stride = (width + 7) // 8
c_bytes = []
for line in hex_lines:
row_val = int(line, 16)
# 按 stride 拆分字节,高位在左 (MSB leftmost)
for i in range(stride - 1, -1, -1):
c_bytes.append((row_val >> (i * 8)) & 0xFF)
return c_bytes
def generate_font_c_file(chars, bdf_dict, font_name, width, height):
# 1. 按 Unicode 码点严格升序排序
sorted_chars = sorted(list(set(chars)), key=lambda c: ord(c))
stride = (width + 7) // 8
print(f"/* 自动生成的 {font_name} 1-bit 点阵字库 */")
print(f"const uint16_t {font_name}_Index[{len(sorted_chars)}] = {{")
print(" " + ", ".join(f"0x{ord(c):04X}" for c in sorted_chars))
print("};")
print(f"const uint8_t {font_name}_Bitmaps[{len(sorted_chars)} * {stride * height}] = {{")
for c in sorted_chars:
bytes_list = bdf_glyph_to_c_bytes(bdf_dict[c], width, height)
hex_str = ", ".join(f"0x{b:02X}" for b in bytes_list)
print(f" /* '{c}' U+{ord(c):04X} */ {hex_str},")
print("};")
生成后的工程核查要点:
- 确认新增字符已全部收录在升序索引表中,且生成器无字符越界或缺字报警。
- 检查字模预览图,确认极小字号繁体字无笔画重叠粘连,且西文与中文字符基线对齐一致。
- 固件支持整句缺字回退保护机制,当目标语言字库缺失任一汉字时安全降级,避免屏幕显示乱码或空白块。
讨论交流
支持免注册访客评论,填写真实邮箱可接收回复提醒