简介这份资源面向使用 Arduino 与 u8g2 库进行中文显示开发的嵌入式爱好者与工程师解决 u8g2 默认字库不含中文字形、需要自行生成定制字体的实际问题。压缩包内包含 GUITool 字体取模工具与 u8g2-master 源码库两部分核心内容前者用于将中文字符转换为 u8g2 可识别的点阵字模数据后者提供库文件与示例参考便于在项目中直接调用自定义中文字体。资源包整体约 54.68MB文件类型以工具程序与源码文件为主结构清晰适合按需取用。目前已有 2242 人学习下载读者可借助这套资料完成从取模、生成字库到在 Arduino 工程中加载显示中文的完整流程减少自行摸索字库格式与配置参数的时间对 OLED、LCD 等小屏中文界面开发具有直接参考价值。1. 从 U8G2ChineseFont.zip 说起嵌入式中文点阵字库到底怎么落地如果你玩过 OLED 或 LCD 单色屏大概率绕不开 U8G2 这个图形库。它轻量、跨平台、驱动覆盖广但真正让国内开发者头疼的从来不是画线画圆而是在只有几十 KB RAM 的单片机上显示中文。U8G2ChineseFont.zip 这类资源包本质上就是一套已经裁剪好的中文字库文件加配套取模配置解决的就是“屏幕能亮、英文能显示、中文一写就乱码或编译爆 Flash”的经典问题。它适合做小家电面板、工控仪表、穿戴设备、电子价签的嵌入式工程师也适合刚接触 U8G2、被字库格式劝退的新手。核心矛盾只有一个中文字形数据太大而单片机资源太小必须靠取模裁剪和按需加载来平衡。2. 中文字库在 U8G2 里的三种存法选错了后面全是坑2.1 全字库、子集字库和动态加载的取舍U8G2 原生支持的中文字库调用方式常见做法有三种。第一种是全字库编译进 Flash比如把 GB2312 一二级字库全部转成 C 数组优点是调用简单缺点是 Flash 占用动辄几百 KB 到 1 MB 以上STM32F103C8T6 这种 64 KB Flash 的芯片直接出局。第二种是子集字库只提取项目实际用到的几十到几百个汉字生成一个自定义小字库这是 U8G2ChineseFont.zip 这类资源最典型的用法。第三种是外部 Flash 动态加载字库放在 W25Q64 这类 SPI Flash 里运行时按需读取字形点阵RAM 和内部 Flash 都省但需要自己写读取和缓存逻辑。选型判断很简单如果你的 MCU Flash 小于 128 KB优先子集字库如果产品需要显示用户输入或大量动态文本考虑外部 Flash 方案如果只是固定显示几十个菜单词子集字库加U8G2_USE_FONT宏裁剪就够了。我一般会先统计 UI 上所有可能出现的汉字去重后通常不超过 300 个这样生成的字库体积可以压到 20 KB 以内。2.2 用 bdfconv 把 TTF 转成 U8G2 可用的 C 数组U8G2 官方工具链里有一个bdfconv工具配合otf2bdf可以把 TTF/OTF 字体转成 U8G2 能识别的.bdf再转成 C 数组。下面是我在 Linux 下常用的命令流程Windows 下可以用 WSL 或 MSYS2 跑同样的命令。# 第一步把 TTF 转成 BDF指定字号 16 像素 otf2bdf -p 16 -o myfont16.bdf SourceHanSansCN-Regular.ttf # 第二步用 bdfconv 生成 U8G2 格式的 C 文件 # -f 1 表示输出格式为 U8G2 字体 # -m 0 表示不生成映射表使用 Unicode 直接索引 # -n 指定字体变量名 # -o 输出文件名 # -b 0 表示从 0 开始编码 # -e 1 表示编码范围结束标志 bdfconv -f 1 -m 0 -n u8g2_font_myfont16 -o myfont16.c myfont16.bdf # 第三步如果只需要部分汉字用 -u 指定 Unicode 列表文件 # unicode_list.txt 每行一个十六进制码点例如 4E2D 表示“中” bdfconv -f 1 -m 0 -n u8g2_font_subset16 -o subset16.c -u unicode_list.txt myfont16.bdf这里的关键参数是-m。-m 0表示不做映射表U8G2 会按 Unicode 码点直接计算偏移适合码点连续的 GB2312 子集如果码点跳跃很大建议用-m 1生成映射表虽然多占一点空间但查找速度稳定。-u指定的 Unicode 列表文件就是你把 UI 上所有汉字去重后转成十六进制码点的结果可以用 Python 脚本从字符串里自动提取。2.3 在 U8G2 工程里注册自定义字库并调用生成 C 文件后把它加入工程然后在调用u8g2_DrawStr之前设置字体。下面是一个 STM32 HAL U8G2 的典型片段。// 声明外部字库变量变量名要和 bdfconv -n 参数一致 extern const uint8_t u8g2_font_subset16[]; // 初始化 U8G2 后设置自定义中文字库 u8g2_Setup_ssd1306_i2c_128x64_noname_f( u8g2, U8G2_R0, u8x8_byte_hw_i2c, u8x8_gpio_and_delay); u8g2_InitDisplay(u8g2); u8g2_SetPowerSave(u8g2, 0); // 关键一步把字体设置为自定义子集字库 u8g2_SetFont(u8g2, u8g2_font_subset16); // 绘制中文注意字符串要用 UTF-8 编码 u8g2_DrawUTF8(u8g2, 0, 20, 温度); u8g2_DrawUTF8(u8g2, 0, 40, 湿度); u8g2_SendBuffer(u8g2);逻辑说明u8g2_SetFont只是切换当前字体指针真正绘制中文要用u8g2_DrawUTF8而不是u8g2_DrawStr因为DrawStr按单字节处理遇到 UTF-8 多字节编码会直接乱码。参数上u8g2_font_subset16这个变量名必须和bdfconv -n指定的一致否则链接阶段会报未定义符号。另外注意u8g2_Setup_ssd1306_i2c_128x64_noname_f里的_f后缀表示全缓冲模式RAM 占用 1 KB如果 RAM 紧张可以换成_1或_2分页模式但分页模式下DrawUTF8的坐标计算会更麻烦。3. 裁剪与压缩把中文字库塞进 64 KB Flash 的实操参数3.1 按 Unicode 码点生成最小子集子集字库的核心是“只取所需”。我一般用下面这个 Python 脚本从 UI 字符串里提取所有不重复汉字输出 bdfconv 需要的 Unicode 列表文件。# extract_unicode.py # 从文本文件中提取所有汉字输出十六进制 Unicode 码点列表 import re def extract_chinese_chars(text): # 匹配 CJK 统一汉字基本区 pattern re.compile(r[\u4e00-\u9fff]) return set(pattern.findall(text)) def main(): with open(ui_strings.txt, r, encodingutf-8) as f: content f.read() chars extract_chinese_chars(content) # 排序后输出每行一个码点格式如 4E2D with open(unicode_list.txt, w, encodingutf-8) as f: for ch in sorted(chars): f.write(f{ord(ch):04X}\n) print(f共提取 {len(chars)} 个不重复汉字) if __name__ __main__: main()逻辑说明ui_strings.txt里放你界面上所有可能出现的字符串一行一个或直接一段文本都行。脚本用正则匹配\u4e00-\u9fff范围内的字符去重后按码点排序输出。参数上04X表示至少 4 位十六进制不足补零这是 bdfconv 能识别的格式。如果你的项目还包含标点符号和数字需要额外匹配\u3000-\u303f和\uff00-\uffef区段。3.2 用 RLE 压缩点阵数据再省一半空间即使裁剪到 300 个汉字16x16 点阵每个字 32 字节总共也要 9.6 KB加上 U8G2 的字体结构体开销差不多 12 KB。如果 Flash 还是紧张可以对点阵数据做游程编码RLE。U8G2 本身不直接支持 RLE 字库但你可以自己写一个解压函数在绘制前把字形解到 RAM 缓冲区。// 简单的 RLE 解压每个字节的高 4 位是重复次数低 4 位是数据 // 注意这是自定义格式需要配合你自己的取模脚本生成 void rle_decode(const uint8_t *src, uint8_t *dst, uint16_t dst_len) { uint16_t di 0; while (di dst_len) { uint8_t count (*src 4) 1; // 重复次数 1-16 uint8_t value *src 0x0F; // 实际数据这里只用了低 4 位 for (uint8_t i 0; i count di dst_len; i) { dst[di] value; } src; } }逻辑说明这个 RLE 方案把每个字节拆成“重复次数 数据”适合点阵里大量连续 0x00 或 0xFF 的情况。参数上高 4 位最大表示 16 次重复低 4 位表示数据值实际使用中如果数据超过 4 位范围需要改成整字节压缩方案。解压后的数据要按 U8G2 的字形格式重新组装这部分需要你根据u8g2_font_decode的源码逻辑来适配属于进阶操作新手建议先用未压缩子集字库跑通。3.3 外部 SPI Flash 方案的分区与读取参数当子集字库也放不下时外部 Flash 是唯一出路。以 W25Q64 为例8 MB 空间可以放完整 GB2312 字库还有富余。我的做法是把字库按 4 KB 扇区对齐存放每个汉字 32 字节一个扇区存 128 个汉字。读取时根据 Unicode 码点计算扇区地址和偏移。// 假设字库从外部 Flash 的 0x10000 地址开始存放 // 每个汉字 32 字节按 Unicode 码点顺序排列 #define FONT_BASE_ADDR 0x10000 #define FONT_CHAR_SIZE 32 void read_font_from_flash(uint16_t unicode, uint8_t *buf) { // 计算偏移只处理 CJK 基本区码点减去 0x4E00 uint32_t offset (unicode - 0x4E00) * FONT_CHAR_SIZE; uint32_t addr FONT_BASE_ADDR offset; // W25Q64 读取函数每次最多读一页 256 字节 w25q64_read(addr, buf, FONT_CHAR_SIZE); }逻辑说明FONT_BASE_ADDR是字库在外部 Flash 中的起始地址需要根据你的分区表调整。FONT_CHAR_SIZE是单个汉字点阵的字节数16x16 是 32 字节24x24 是 72 字节。w25q64_read是底层 SPI 读取函数注意 W25Q64 的页读取边界是 256 字节跨页读取需要分两次。这个方案省内部 Flash但每次绘制中文都要读 SPI刷新率会下降适合静态显示场景。4. 避坑与排查中文显示翻车的五个血泪现场4.1 现象中文显示为方块或问号原因字体没有正确设置或者调用了u8g2_DrawStr而不是u8g2_DrawUTF8。DrawStr按单字节处理UTF-8 编码的汉字会被拆成多个无效字节。解决确认u8g2_SetFont设置的是中文字库并且绘制函数用u8g2_DrawUTF8。另外检查源文件编码是否为 UTF-8Keil 默认 GBK 编码会导致字符串常量本身就不对。4.2 现象编译报错 “undefined reference to u8g2_font_xxx”原因bdfconv 生成的 C 文件没有加入工程或者变量名和-n参数不一致。解决检查生成的.c文件是否在编译列表里检查extern声明和实际变量名是否完全一致包括大小写。如果用的是 C 工程记得加extern C包裹。4.3 现象显示一半汉字后面的乱码原因子集字库的 Unicode 列表不完整或者 bdfconv 的-m映射模式选错。如果用了-m 0但码点不连续U8G2 会按错误偏移查找。解决重新生成 Unicode 列表确保覆盖所有用到的汉字如果码点跳跃大改用-m 1生成映射表。另外检查unicode_list.txt里是否有重复或格式错误的行。4.4 现象Flash 占用远超预期原因bdfconv 默认会生成完整的字体结构体包括所有字形的偏移表即使子集很小偏移表也可能占几百字节。另外如果-m 1映射表过大也会增加体积。解决用arm-none-eabi-size查看各段大小确认字库确实在.rodata段。如果偏移表太大可以手动裁剪只保留实际用到的码点范围。我一般会把字库单独放一个.c文件方便用size命令单独统计。4.5 现象外部 Flash 方案读取速度慢界面卡顿原因每次绘制都直接读 SPI Flash没有缓存机制。解决在 RAM 里开一个字形缓存比如缓存最近用到的 16 个汉字点阵用简单的 LRU 淘汰。缓存大小根据 RAM 余量决定16 个 16x16 汉字占 512 字节对大多数 MCU 可以接受。另外把 SPI 时钟提高到 40 MHz 以上W25Q64 支持 80 MHz 读取但要注意 PCB 走线质量。5. 进阶技巧用 U8G2 的字体裁剪宏和运行时切换做多语言界面U8G2 本身提供了一组编译期裁剪宏比如U8G2_USE_FONT可以在u8g2.h里禁用不需要的字体减少 Flash 占用。但很多人不知道的是你可以在运行时动态切换字体指针实现中英文混排或多语言界面。具体做法是定义多个字体变量在界面切换时调用u8g2_SetFont切换。// 定义中英文字体指针 extern const uint8_t u8g2_font_subset16[]; extern const uint8_t u8g2_font_helvB12_tf[]; // 根据语言设置切换字体 void set_language_font(u8g2_t *u8g2, uint8_t lang) { if (lang LANG_CN) { u8g2_SetFont(u8g2, u8g2_font_subset16); } else { u8g2_SetFont(u8g2, u8g2_font_helvB12_tf); } } // 绘制时统一用 DrawUTF8英文也能正常显示 u8g2_DrawUTF8(u8g2, 0, 20, 温度: 25C);逻辑说明u8g2_font_helvB12_tf是 U8G2 自带的英文字体_tf后缀表示包含完整 ASCII 字符集。DrawUTF8对纯 ASCII 字符串同样有效所以中英文混排不需要切换绘制函数。参数上LANG_CN和LANG_EN是你自己定义的宏根据界面状态或用户设置来切换。这个技巧在需要支持多国语言的产品里很实用代价是同时链接两套字体Flash 占用增加但比全字库方案还是省很多。另一个进阶点是字库的按需加载与释放。如果你的系统有外部 RAM 或足够大的内部 RAM可以把整个子集字库在启动时从外部 Flash 读到 RAM 里之后绘制直接读 RAM速度提升明显。代价是启动时间增加几百毫秒以及 RAM 占用。我一般会在产品启动时做一个进度条把字库加载和界面初始化并行处理用户感知不到延迟。最后说一个我踩过的坑不要用u8g2_DrawGlyph直接绘制中文。这个函数按字形索引查找而中文字库的字形索引和 Unicode 码点不是一一对应关系除非你完全清楚 bdfconv 生成的索引表结构否则很容易画错字。老老实实用u8g2_DrawUTF8让它内部去处理编码转换和字形查找。这个习惯帮我省了至少两天调试时间。希望帮到你。本文还有配套的精品资源点击获取