简介在 Arduino 开发中U8G2 图形库对中文显示支持有限自定义中文字体往往需要专门的工具链该资源包聚焦于此。内含 GUITool 字模提取工具与完整的 u8g2-master 库文件压缩后约 54.68MB。GUITool 负责将中文字符转换为 U8G2 可识别的点阵数据u8g2-master 则提供底层驱动接口两者配合即可按需生成不同字号、不同编码的中文字体解决默认字库缺失导致的乱码、空白问题。已有 2238 人学习下载适合使用 Arduino、ESP8266/ESP32 等平台且需要在 OLED/LCD 上显示中文菜单、标签或数据的创客与嵌入式开发者。压缩包采用 zip 格式目录区分明确可单独提取需要的部分结合作者整理的教程能快速掌握字体数据结构与调用逻辑完成从字模生成到板端调用的完整流程减少反复实验的调试成本对中文字体渲染效果有要求的实时显示项目尤其适用。 做嵌入式显示这块的兄弟们应该都遇到过这个场景好不容易把SSD1306点亮了英文数字跑得飞起结果想在OLED上显示一行中文屏幕上直接给你来一排小方块。我当初折腾U8G2中文字库的时候也是被这个老问题卡了好几天后来搞明白原理之后才发现真正的问题不是U8G2不支持中文而是它压根没给你准备中文字模。今天要聊的这个U8G2ChineseFont.zip就是一个帮你把“中文字库”这件事打包好的现成资源解压就能用。这篇东西我会从字库机制、文件结构、引入步骤到常见坑位都过一遍给正在用Arduino或者STM32驱动OLED、想在U8G2下正常显示中文的朋友做参考。1. 为什么U8G2显示中文这么“折腾”字库机制的底层逻辑1.1 U8G2自带的字库只覆盖ASCII先明确一个事实U8G2库体积不小功能也确实强内置了好几十种字体从u8g2_font_ncenB14_tr到u8g2_font_profont12_tf各种风格都有但这些字体的字形范围都集中在ASCII和Latin-1区间也就是U0000到U00FF这个范围内顶多支持一些西欧语言符号中文的CJK统一表意文字区块U4E00到U9FFF一个都没有。原因也简单全量汉字点阵数据量太大了作者不可能把几千个常用汉字塞进一个开源库里那样Arduino Uno这种老平台直接就废了。所以U8G2本身的设计思路很清楚它提供“字形查找”和“位图渲染”的框架字体数据由使用者自己准备。换句话说U8G2管的是“怎么把字画到屏幕上”不管“你的字从哪来”。1.2 字体在显示库中是如何工作的很多人刚开始会困惑为什么英文能用drawStr中文就得用drawUTF8这得从U8G2的字体数据结构说起。U8G2内部对字体的定义是一个u8g2_font_t结构体集群核心是一组const uint8_t数组里面按照固定格式记录了三类信息字符码位映射表、每个字形在数组中的偏移、以及每个字形的位图数据。查询流程大概是这样的当程序调用drawUTF8时库先把UTF-8编码的字节序列解码成一个32位的Unicode码点然后根据这个码点在字体表中查找对应的字形数据结构找不到就直接跳过找到了就把字形位图按特定规则逐字节写入到屏幕缓冲区。所以要让U8G2显示中文核心工作就变成了“按U8G2规定的数据格式生成一套以Unicode码位为索引的中文字模数据”。这里有个容易误解的点drawStr只处理ASCII遇到非ASCII字符会直接乱画或者不画drawUTF8是专门为Unicode设计的入口中文显示必须走它。我见过不少人用drawStr去显示中文结果屏幕一片糊查了半天没想明白其实就是入口用错了。1.3 为什么需要一个打包好的ChineseFont资源知道了上面的原理你就能明白U8G2ChineseFont.zip这种资源包的价值了。要从零开始做一个中文字库你要做的步骤是选一套字体、确定字号和点阵大小、把几千个汉字用取模工具逐个转成位图数据、再按U8G2的字库格式封装成C数组、最后还要把数组放到正确内存段。这一套流程走下来光是取模和整理数据就能耗掉一整天更别说中间任何一个环节出错都会导致显示异常。而这个zip包的主要作用就是把“做字库”这个脏活累活提前干完你拿到手的是一个或多个编译好的C源文件声明清晰、格式现成直接#include进来、调用setFontdrawUTF8就完事了。对绝大多数项目来说这就是效率最高的方案没必要自己重复造轮子。2. 拆解U8G2ChineseFont字库文件里装的到底是什么2.1 字库文件的数据结构解压这个zip包之后你会看到典型的font_Chinese.c和font_Chinese.h这类文件。如果你打开过U8G2自带的字体源文件会发现它们长得非常类似——一个被U8G2_FONT_SECTION宏修饰的const uint8_t数组里面是一长串十六进制字节开头部分还夹着不少0x00和0x01之类的数据。前一段说的是字库的元信息包括起始码位、结束码位、每一个字形数据的索引方式、以及位图的宽度高度格式等后面的数据段才是真正的点阵位图。理论上用户不需要手动去分析这些字节但知道了它们的存在你就能理解为什么“直接拿文本编辑器改几个字节”等于自毁字库。真正影响使用的是你拿到手的字库覆盖了哪些汉字范围以及它是什么编码组织方式。大部分现成的中文资源包会按GB2312一二级汉字或者常用3500字来收录16x16点阵一个字占32字节3500字大概要112KB全量6763字要216KB左右这些数字在选型的时候非常关键。2.2 Unicode编码与UTF-8的关系这里我要单独拎出来强调一下编码问题因为这几乎是中文字库显示乱码的第一大原因。U8G2的drawUTF8函数名里就写明了“UTF-8”它接收的字符串必须是UTF-8编码。而很多嵌入式IDE比如老版本的Keil MDK默认源文件编码是GB2312或者ANSI你在代码里写下u8g2.drawUTF8(0, 0, 你好);编译器虽然能通过但字符串实际存入Flash的字节序列是GB2312的编码不是UTF-8。U8G2按照UTF-8规则去解码这串GB2312字节得到的码位自然不在中文字库的收录范围内结果就是屏幕上出现乱码、错字或者干脆空白。解决办法也很简单把源文件另存为UTF-8编码在Keil里可以通过Edit - Configuration - Encoding设置Arduino IDE新版默认就是UTF-8一般不需要额外处理。判断文件编码是否正确的土办法也很实用用Notepad打开源文件右下角看编码显示是“UTF-8”还是“ANSI”一目了然。2.3 关于存储空间的现实问题字库数据是静态点阵必须存放在非易失性存储里。在Arduino Uno上U8G2_FONT_SECTION这个宏会把字体数组链接到Flash区域也就是程序存储区由编译器保证它不占用运行时内存。但是Uno的Flash只有32KB放一个3500字的16x16字库112KB根本不可能所以你在Arduino Uno上使用这类资源包时往往只能选择极精简版本或者改换主控。STM32F103C8T6这种Flash就有64KB更大的F103ZET6有512KB放全量字库就很从容。如果你的目标平台Flash实在紧张就得考虑外部SPI Flash方案了平时把字模以二进制文件形式存放在Flash芯片里程序启动时按需读入RAM再交给U8G2。这部分我放到后面扩展章节详细说这里先记住一个结论先算好字库体积再定主控型号别等编译爆了再改方案。3. 从zip到屏幕完整引入步骤与实测效果3.1 解压、验证zip包完整性先处理眼前这个zip包本身。压缩包解压虽然不是什么难事但如果你是在网速不稳定的环境下下载的极有可能遇到“解压失败”“invalid zip archive: could not find eocd”这类提示。eocd是zip文件的结尾记录块找不到它就说明压缩包根本没下载完整这时候不要尝试用什么“修复工具”“无视密码直接解压”之类的歪门邪道老老实实重新下载一遍检查文件大小是否和发布页一致。解压工具建议用7-Zip或者Bandizip对中文文件名和编码的兼容性比系统自带的资源管理器好很多。解压出来的.c和.h文件如果能打开最好确认一下编码保持UTF-8这一步能避免后面一大堆乱码问题。3.2 将字库加入工程拿到两个文件后不同开发环境的做法略有区别。Arduino IDE里最简单的方式是把font_Chinese.c和font_Chinese.h直接放在和.ino文件同一个目录下Arduino构建系统会自动编译同目录的.c文件如果你把它放到libraries目录下做成独立库就要注意库的目录结构规范因为U8G2这种老牌库是支持标准库结构的你自己做一套也行但容易遇到构建顺序问题。STM32的Keil工程里操作路径是打开Project窗口右键Source Group选择Add Existing Files把.c文件加进来然后记得在代码里#include font_Chinese.h编译器的Include路径要包含该头文件所在目录。我自己的习惯是单独建一个fonts子目录把字库文件统一扔进去避免和主代码混在一起后面换字库、增删字体都好管理。3.3 调用APIsetFont与drawUTF8的配合这里给一份最小可用的Arduino示例注释写得比较细方便你直接抄作业#include Arduino.h #include U8g2lib.h #include font_Chinese.h // 引入中文字库 U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* clock*/ SCL, /* data*/ SDA, /* reset*/ U8X8_PIN_NONE); void setup(void) { u8g2.begin(); u8g2.setFont(font_Chinese); // 切换到中文字库 u8g2.setFontMode(1); // 透明背景避免覆盖原来内容 } void loop(void) { u8g2.clearBuffer(); u8g2.setCursor(0, 20); u8g2.print(温度 23.5C); // print内部会走drawUTF8逻辑 u8g2.drawUTF8(0, 40, 嵌入式显示); u8g2.sendBuffer(); delay(1000); }注意几个细节setFont必须放在clearBuffer之后、绘制之前因为它只影响后续绘制操作setFontMode(1)表示透明字体模式一般建议开启否则字模背景会变成不透明色块把底图盖掉。另外u8g2.print对UTF-8字符串的处理和drawUTF8是一致的所以你不用纠结用哪个习惯哪个用哪个。绘制坐标是字形的基线坐标对16x16字库来说第一行文字Y设20、第二行设40是比较安全的间隔字高在16到20像素之间浮动可以根据实际显示效果微调。3.4 用U8G2模拟器先预览减少烧录次数这个技巧我觉得值得单独拿出来讲。U8G2官方在源码仓库里带了一个SDL模拟器你可以在PC上用同一个字库文件和同一段绘制代码跑起来屏幕上会弹出一个模拟OLED窗口直接看到渲染结果。这意味着你在调文字布局、验证字库效果的时候根本不需要反复烧录固件改一个坐标在电脑上看一眼改完再上真机整个调试节奏会快很多。模拟器的配置方式在U8G2的sys/sdl目录下有工程文件需要提前安装SDL2开发库。我第一次用模拟器的时候还在怀疑它和真机显示会不会有差异实际对比下来发现点阵字体的渲染是一一对应的模拟器里什么样真机就什么样。做复杂UI界面的时候这个工具能帮你省下大量时间。4. 常见问题与排查技巧实录4.1 编译报错undefined reference to font_Chinese这是最典型的引入失败问题编译时报错说要找font_Chinese这个符号但找不到。出现这个问题的原因九成是.c文件没有参与编译尤其是在Arduino IDE里文件放错目录、目录层级不对、文件名大小写写错都可能导致构建系统没编译它。另一个常见原因是代码里写了extern const uint8_t font_Chinese[];但头文件里实际定义的符号名不匹配比如有的资源包把符号命名为font_16x16你得按实际名字来。排查顺序建议是先打开头文件看真正的符号名再确认.c文件在工程树里存在最后检查include路径。4.2 屏上全是方块或乱码方块或乱码通常是编码问题优先级最高的怀疑对象就是源文件编码不是UTF-8。你可以做一个最小验证把字符串改成全英文数字如果显示正常说明U8G2本身没问题问题出在中文的字节编码上。此时别急着改代码先在编辑器里把文件转成UTF-8编码再编译一次大概率就解决了。如果转完编码还是乱码再看另一个可能你写的某个汉字不在这个字库包的收录范围内。比如资源包只收录了常用3500字你输入一个生僻字“靐”库里面当然没有对应字形U8G2找不到就直接画成方块或者跳过。验证方法是用字库里明确包含的字测试比如“你好世界”这种高频字。4.3 显示偏位、多出黑点点阵字体在显示时出现残影、多出的黑点、上下两行重影多半是坐标或者字模数据格式的问题。先查Y坐标字形绘制是基于基线定位的16x16字库的行距建议不小于20像素如果行距太近上一行的下半部分会顶到下一行的上半部分。再检查字模本身的取模方式是否匹配。U8G2对字形的位图排列有严格要求不同资源包可能用了不同取模顺序如果你的zip包是从某个项目里拆出来的而那个项目用的U8G2版本和你不一样偶尔会碰到兼容性问题。遇到这种情况优先找资源包说明文档里写的U8G2版本尽量对齐。4.4 存储空间不足Arduino Uno编译时提示text段溢出就是Flash不够了。前面算过3500个16x16汉字需要约112KB而Uno的Flash总共只有32KB你换库换到天亮也没用。这个时候要么把字库换成精简版比如只保留几百个自己项目里会用到的汉字要么换主控。STM32F103C8T6的64KB Flash做一个常用字库也偏紧张推荐直接上256KB以上的芯片或者走外部Flash方案。另外提醒一点字库数组一定要靠U8G2_FONT_SECTION宏放到Flash千万别自己写成普通全局数组否则会被放进RAM不仅占掉大量内存刷机后可能直接复位死机。下面整理一个简单的排查速查表方便实战时快速定位现象最可能原因优先排查动作编译报undefined reference.c文件未参与编译检查工程文件列表中文全部乱码源文件编码非UTF-8另存为UTF-8编码个别汉字变方块字库未收录该字符换成常用字测试两行文本重叠Y坐标行距过小加大行距到20px以上字模破碎、雪花点取模方式或U8G2版本不匹配核对资源包版本说明编译提示Flash溢出字库体积超出MCU存储换精简字库或升级主控5. 进一步扩展自制精简字库的思路5.1 只做自己项目真正用到的字如果你不喜欢现成字库要么太大、要么缺字的尴尬可以自己做一个精简字库。思路并不复杂把你项目里所有可能显示的中文句子收集起来去重后得到一个几十到几百字的集合然后给这些字逐个取模生成一个小的font数组。这么做的好处非常直接——一个128字的16x16字库只需要4KB对Flash极小的MCU非常友好。我自己的一个温控项目最终只需要显示“温度湿度设置开关确认取消”这十几个字最后做的精简字库还不到1KB配合U8G2用起来非常清爽。5.2 取模工具的正确姿势自制字库绕不开取模工具目前社区里用得最多的是PCtoLCD2002取模参数设置是个门道。对U8G2来说最省事的方式不是直接用PCtoLCD2002的默认输出而是参考U8G2作者提供的开源转换工具或者找GitHub上现成的Python脚本输入一个TTF字体文件就能自动生成U8G2格式的字库数组。如果你坚持用PCtoLCD2002手工取模要注意点阵方向字节顺序和U8G2约定不一致的问题这也是很多人在自制字库时翻车的重灾区。我的建议是一次性用自动化脚本生成全量数据别靠手工点按钮一方面效率高另一方面不容易错。5.3 与外部Flash结合实现全量字库在需要显示任意中文内容的场合精简字库是不够的这时候就得把GB2312全量字库放进外部Flash芯片。具体做法是先把6763个汉字按Unicode码位顺序生成二进制字模文件烧录到W25Q64或者更大容量的SPI Flash里然后在程序里挂钩一个“动态取模”机制要绘制某个汉字时根据它的Unicode码位算出在Flash中的偏移地址读出一段字模数据临时填充到U8G2提供的字形槽位中。这种方案的代码量比直接用静态数组大不少要做地址换算、缓存管理但好处是支持任意汉字且不占用MCU内部Flash。如果你的产品需要用户可配置显示内容这个方案基本是标准答案。做到这一步U8G2的中文显示问题就算是彻底打通了。回过头来看这个U8G2ChineseFont.zip的意义不只是“给你一个能用字库”更重要的是让你理解了U8G2的字库机制它负责渲染你要负责喂数据。我个人经验是先跑通一个现成资源包把整个链路摸熟再根据自己的项目需求去做精简或者全量定制这条路最稳。最后再分享一个小习惯保留一份自己项目专用的精简字库文件同时把原始zip包的完整源文件归档这样不管是应急改动还是后期升级都有回旋的余地。本文还有配套的精品资源点击获取