☰
U8g2中文显示不再难:手把手教你生成轻量级自定义字库
2026/9/28 16:30:44 网站建设 项目流程

做显示类项目最烦的是什么?画点、画线、画框都好办,一旦涉及中文,麻烦就来了。我前阵子帮朋友改一个断丝检测仪的显示面板,MCU用的是 Arduino Uno,屏是常见的 0.96 寸 SSD1306 OLED,界面要显示“运行”“待机”“断丝报警”这类状态。U8g2 库本身很顺手,I2C 接上就能跑,可惜它的默认字体全是拉丁字符和符号,汉字一个都没有。网上常见的解决办法有两个:一是直接用 U8g2 内置的全量中文字体,比如u8g2_font_unifont_t_chinese系列,结果一看 Uno 只有 32KB Flash,屏幕还没点亮就把芯片塞满了;二是用 PCtoLCD 之类的取模工具,把整句话抠成一张位图,这方案更痛苦,改一个字就得重新抠一遍图。今天这篇就聊第三条路:只把项目里真正用到的汉字挑出来,生成一个几 KB 甚至不到 1KB 的自定义子集字库,直接丢给 U8g2 用。这套方案我在多个 Arduino 小屏项目里跑过,稳定、轻量、可维护,适合所有被中文显示折磨的嵌入式玩家。

1. 为什么 U8g2 显示中文这么麻烦

1.1 U8g2 本质上是位图字体引擎

先说清楚 U8g2 的显示逻辑。它跟电脑上的字体渲染完全是两回事,电脑里有操作系统、字库服务、TrueType 引擎帮你曲线描边,U8g2 没有这个能力。它只能做一件事:拿到一个字符的编码,去一张“字模表”里查地址,然后把那段位图数据画到屏幕上。所以 U8g2 里所谓“字体”,本质是一堆像素点阵 + 字符映射表 + 宽度信息。显示英文和数字没问题,因为默认字体里已经内置了 ASCII 全套;显示汉字,U8g2 本身并不认识,它只知道“这是一个编码”,如果字模表里查不到这个编码对应的位图,就画一个空心方块或者什么都不画。

这个机制决定了所有中文显示问题都绕不开同一个核心:你得先有一份能被 U8g2 识别的中文字模表。问题在于,中文字符实在太多了,常用汉字就有三千多,GB2312 全量更是有 6763 个。一个 16x16 点阵的汉字需要 32 字节,6763 个字就是 216KB 的 Flash,这还没算字体元数据和 ASCII 部分。Arduino Uno 的 ATmega328P 总共就 32KB Flash,塞全量字库想都别想。即便换成 ESP32,虽然 Flash 够大,但一次性加载几万字的字库,本质上也是极大的浪费——你项目里真正会显示出来翻来覆去也就那几十个字。

1.2 全量字库对小内存 MCU 是降维打击

我见过不少新手拿到 Uno 之后,听人说 U8g2 有内置中文字体,于是高高兴兴写了一句u8g2.setFont(u8g2_font_unifont_t_chinese);,编译直接爆掉。这不是 U8g2 的问题,是 Flash 总量撑不住全量字库。32KB 的 Flash 里,U8g2 库和 SSD1306 驱动要把代码、初始化数据、缓冲管理都塞下去,大概占 8~10KB,剩下 22KB 左右。GB2312 全字库一份就要 200 多 KB,在 Uno 面前根本没有讨论余地。

很多人退而求其次用 ESP32,Flash 倒是大了,但还是不划算。ESP32 的模组 Flash 通常是 4MB,存 216KB 字库确实没问题,可问题是运行内存也不是白给的,大字体数组会拖慢启动、占用 Flash 分区,而且你为了几句话背下全字库,到头来 99% 的字永远用不到。真正合理的思路应该是“按需取字”:项目文案里出现哪个字,字库就编入哪个字。这就是轻量级自定义字库的核心逻辑,也是我推荐给所有 Flash 紧张项目的方案。

1.3 别再用这三个“土办法”了

方案一,整句话扣图。用取模工具把“温度正常”做成一整张位图,再通过drawXBM或drawBitmap画上去。这个方案单看一个界面没问题,但项目只要有多屏、多状态、多语言,位图数量会爆炸,改一个词就要重新出图,而且位图在 Flash 里的体积跟字库差不多,完全没占到便宜。

方案二,SD 卡或外部 Flash 存全字库,运行时读。这个方案适合需要频繁切换大量文本的场景,但它有一个现实约束:U8g2 原生不支持从文件系统里按需加载字形。你要自己写一个“编码到字模偏移”的索引函数,然后把读到的位图通过 U8g2 的低层绘制接口拼出来。不是不能做,工作量和代码复杂度都上去了,对大多数小项目来说属于过度设计。

方案三,用 PCtoLCD 取模工具导出一段段字模数组,手动拼到代码里。这个比方案一灵活,但 PCtoLCD 生成的是纯粹的位图数组,没有字符映射关系、没有宽度信息,U8g2 根本不认。你要自己写查找函数、自己管理字符宽度,最后发现自己实际上是在重造一个残缺的字体引擎。

正确做法就一条:走 U8g2 官方支持的字体生成管线,用 bdfconv 工具从位图字体文件中提取指定字符,生成一份标准 U8g2 字体 C 文件。这套流程从一开始就是为了解决字体定制问题设计的,稳定、可复现,生成的字体跟 U8g2 内置字体是同一套格式,setFont一行就能切换。

2. 方案选型:轻量级自定义字库应该怎么设计

2.1 核心流程只有四步

整个自定义字库的思路,跟我去仓库拣货差不多。你手里有一份需求单,上面写着项目需要哪些字符;仓库里存着完整的大货,也就是一份覆盖大量字符的位图字体文件(BDF 格式);拣货员根据需求单把需要的货挑出来,装进一个定制的小箱子;最后这个小箱子就是给 U8g2 用的字体库。

具体到操作就是四步:先整理出项目所有界面文案并去重,得出字符清单;再找一份(或生成一份)覆盖这些字符的 BDF 位图字体文件;然后用 bdfconv 工具把字符清单里指定编码的位图数据提取出来,生成一个 C 源文件;最后把这个 C 文件放进 Arduino 工程,在代码里setFont后正常drawUTF8绘制。

这套流程的好处非常明显:字符完全可控,生成的字库体积跟字符数量成正比,10 个汉字 + 20 个 ASCII 字符的字库可能连 2KB 都不到;改一条文案只需要在字符清单里增删几个码点,重新跑一次 bdfconv,半分钟就能重新生成。而且生成的 C 文件本质就是一个const uint8_t数组,完全离线可用,不需要在运行时做任何解析,更不依赖文件系统,Uno 这种小芯片跑起来毫无压力。

2.2 为什么非得是 BDF 格式

BDF(Bitmap Distribution Format)是一种纯文本格式的位图字体文件,最早来自 X Window 系统。它的特点是把每个字符的位图数据以十六进制文本方式描述,人类可读、解析简单,所以在嵌入式字体工具链里生命力极强。U8g2 的官方字体生成工具 bdfconv 只吃 BDF,不接受 TTF/OTF 直接转换。

有人可能会问,我直接拿 TTF 转成点阵不就行了吗?原理上没错,但这里有个隐藏问题:TTF 是矢量字体,字形数据是曲线方程,U8g2 要的是固定尺寸的像素位图。你必须在某个字号上把 TTF“栅格化”成点阵,这个中间转换结果用 BDF 来存储最合适。BDF 里每一行数据就是 BMP 位图,bdfconv 读进去之后再做一次裁剪、映射、压缩,输出 U8g2 自己的格式。整个过程等于做了一次“二次转换”,虽然多了一步,但每一步都是标准化操作,出错概率反而低。

还有个现实原因:U8g2 的 GitHub 仓库里,tools/font/bdf目录下已经提供了一批现成的 BDF 字体文件,其中就包括文泉驿系列和 Unifont 系列的中文字体。也就是说,你不想折腾 FontForge,直接从仓库里把 BDF 下载下来就能用,省掉了最难的一步。

2.3 字体源和字号怎么选

BDF 里存的已经是点阵位图,所以“字体源”决定了最终像素长什么样,这一步值得认真选。我常用的来源有三个。

第一个是 U8g2 仓库自带的 BDF。它内置的文泉驿点阵字体质量不错,字形规整,小字号下可读性很好;Unifont 则胜在字符覆盖全,但字形比较像素风。这两个都能直接下载下来跑 bdfconv。

第二个是开源 TTF/OTF 字体,用 FontForge 转成 BDF。如果想要更现代的汉字字形,可以用思源黑体、文泉驿微米黑这类开源中文字体。不过要注意,矢量字体在小尺寸下栅格化出来的点阵效果不一定好看,笔画多的字在 16px 这种小字号下会糊成一团,需要实际转出来看效果。

第三个是系统自带的点阵字体文件,比如 Windows 的宋体、黑体点阵,这类字体在特定字号下是专门优化过的,转成 BDF 后清晰度反而可能更高,但要注意版权问题,商用项目别乱用。

字号选择上,我的经验是:0.96 寸 128x64 的 OLED,常用汉字 16x16,整数倍缩放或者跟 8x16 的 ASCII 混合排版都很自然;如果是 1.3 寸或更大屏,可以用 24x24,看起来更饱满,但每个汉字要占 72 字节;如果屏幕够小且只显示状态,12 或 14 点阵也能接受,缺点是笔画多时辨识度下降。总之先确定渲染尺寸,再找对应字号的 BDF,不要拿着 24px 的字库缩放到 16px 用,那会破坏点阵对齐。

3. 实操:从零生成自己的 U8g2 中文字库

3.1 第一步:整理字符清单,宁可少不要多

这一步看着简单,但决定了后面所有工作,一定要把项目里所有界面文案全部列出来。拿一个最常见的温湿度监测屏举例,屏幕要显示四行内容:

温度: 26.5℃ 湿度: 48% 状态: 正常 设备ID: A-01

把这些文案拆成独立字符并去重:中文部分是“温、度、湿、态、正、常、设、备、状”共 9 个汉字;ASCII 部分是0123456789:.%和空格、A、-;另外还有一个特殊字符摄氏度符号℃。注意文案里我统一用了半角冒号,没有用全角冒号“:”,因为全角冒号的码点是U+FF1A,如果不小心漏加进字符清单,屏幕上是画不出来的。所以我的建议是:坐标轴上能统一就统一,不要给自己挖坑。

把这个清单转成 bdfconv 能认的 map 文件。bdfconv 的-M参数接收一个文本文件,每行一个十六进制 Unicode 码点。上面那组字符对应的 map 文件长这样:

0x20 0x25 0x2D 0x2E 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x3A 0x41 0x2103 0x5907 0x5EA6 0x6001 0x6E29 0x6E7F 0x6B63 0x5E38 0x72B6 0x8BBE

这些十六进制分别对应:空格、%、-、.、数字 0-9、半角冒号、字母A、摄氏度℃,以及“备、度、态、温、湿、正、常、状、设”这几个汉字。你可以通过在线 Unicode 编码查询工具查到任意字符的码点,不用记。

注意:map 文件里每一行只能写一个码点,不要写多个字符,也不要带注释,解析工具很死板。这文件就跟 BOM 一样,多一行少一行都会影响结果。

3.2 第二步:准备 BDF 位图字体文件

这一步有两条路。第一,直接用 U8g2 仓库里现成的 BDF。去 GitHub 上把 U8g2 的源码仓库 clone 下来,在tools/font/bdf目录下能找到wenquanyi系列和unifont系列的 BDF 文件,文件名形如wenquanyi_16pt.bdf或unifont.bdf。下载你需要的那个字号,放到工作目录即可。

第二,自己用 FontForge 把 TTF 转成 BDF。如果你对字形有特殊要求,比如必须用思源黑体,那就需要自己转。操作也不复杂:下载 FontForge 并安装,打开字体文件,在“Font Info”里设置需要的像素尺寸,然后 File -> Generate Fonts,保存格式选 BDF。命令行下也能做,核心就是用 FontForge 的脚本接口打开字体、设置大小、导出 BDF。第一次转出来的 BDF 可能很大,这正常,因为里面包含了字体文件里所有字符的位图,等会儿 bdfconv 会帮你做裁剪。

我个人更推荐直接拿现成 BDF,因为省事、可靠,文泉驿点阵在小尺寸下的显示效果也足够好了。自己转 TTF 容易出现字号、对齐、基线位置不对的问题,调起来费时间。

3.3 第三步:用 bdfconv 生成 U8g2 字体 C 文件

准备工作做完,重头戏来了。Windows 用户可以直接从 U8g2 仓库的 release 里拿 bdfconv 的可执行文件,放在工作目录下。Linux/macOS 用户需要自己编译一下bdfconv.c,它是单文件程序,编译起来不麻烦。

假设你的工作目录里已经有了charset.map和wenquanyi_16pt.bdf,执行下面这行命令:

bdfconv.exe -b 0 -f 1 -M charset.map -n u8g2_font_myfont -d wenquanyi_16pt.bdf -o u8g2_font_myfont.c

逐个解释一下参数。-b 0指定位图数据的排列方式,直接用默认值 0 即可,U8g2 内部会按自己的规则取数据。-f 1指定字体格式为 Unicode 编码模式,中文显示必须用这个。-M charset.map就是告诉 bdfconv:我只保留 map 文件里列出的这些码点,其他全部丢弃。-n后面是生成的字体变量名,你可以写成项目相关的名字。-d指定输入的 BDF 文件。-o指定输出的 C 文件路径。

跑完后,目录下会多出一个u8g2_font_myfont.c。打开看一下,里面就是一个很长的const uint8_t u8g2_font_myfont[]数组,外加一些字体元数据。这就是定制好的轻量级字库。你可能会发现数组里还有U8G2_FONT_SECTION这类宏,别管它,U8g2 库编译时会自动处理,把它放到 Flash 的正确段位。

如果 bdfconv 报错说找不到某个码点,通常是 BDF 文件里没有那个字符,比如你的 BDF 是文泉驿的点阵字体,但 map 里写了一个生僻字,自然查不到。解决办法是换一个覆盖字符更全的 BDF,或者确认码点有没有写错。

3.4 第四步:把字库接入 Arduino 工程

接下来把生成的 C 文件放进 Arduino 工程。我习惯在 Sketch 目录下建一个font子目录,把u8g2_font_myfont.c放进去。Arduino 构建系统会自动编译项目所有子目录下的.c和.cpp文件,所以不需要额外配置。

但有个很关键的坑必须提前踩平:bdfconv 生成的 C 文件直接编译时,可能会因为U8G2_FONT_SECTION这个宏没定义而报错。解决办法是在这个 C 文件的最开头加一行#include <U8g2lib.h>,让它能拿到宏定义。加了这一行之后,整个文件丢进 Arduino 工程就不会有依赖问题。

然后在主.ino文件里声明这个字体数组,引用即可:

#include <Arduino.h> #include <U8g2lib.h> #include <Wire.h> extern const uint8_t u8g2_font_myfont[]; U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); void setup() { u8g2.begin(); u8g2.setFont(u8g2_font_myfont); u8g2.setFontPosTop(); u8g2.setFontMode(1); // 透明模式,不覆盖背景 }

如果你的 Arduino IDE 版本比较老,可能不会自动编译font子目录下的文件,那就要么把它移到工程根目录,要么用#include "font/u8g2_font_myfont.c"这种方式显式包含。我更推荐显式包含之外的另一条路:写一个myfont.h头文件,里面放extern const uint8_t u8g2_font_myfont[];,然后把 C 文件放在工程根目录,这样最稳。

3.5 第五步:写显示代码,注意 drawUTF8 和 RAM 缓冲

字体接入后,显示代码有几个细节决定了你能不能顺利把中文画出来。

第一,必须用drawUTF8而不是drawStr。U8g2 的drawStr把字符串按单字节编码处理,只适合纯 ASCII;中文是 UTF-8 多字节编码,必须用drawUTF8做解码,否则一个汉字会被当成两三个独立字符,画出来全是乱码。

第二,在 AVR 平台上,传给drawUTF8的字符串最好放在 RAM 数组里,不要直接传字符串字面量。原因是 AVR 是哈佛架构,字符串常量默认存放在 Flash,但drawUTF8内部是按普通 RAM 指针读取字符串的,直接传字面量在 Uno 上可能读出一串错误数据。解决办法是拷贝到 RAM,比如用strcpy_P:

char line[32]; strcpy_P(line, PSTR("状态: 正常")); u8g2.drawUTF8(0, 36, line);

第三,动态拼接数值时,尽量避免浮点格式化。AVR 上snprintf默认不支持%f,真要用浮点还得链接额外的 printf 浮点库,很占 Flash。我习惯用整数表示十分之一单位,比如26.5℃就存成265,显示时手动拆开:

char line[32]; int temp_x10 = 265; snprintf(line, sizeof(line), "温度%d.%d℃", temp_x10 / 10, temp_x10 % 10); u8g2.drawUTF8(0, 0, line); snprintf(line, sizeof(line), "湿度%d%%", 48); u8g2.drawUTF8(0, 18, line);

注意snprintf格式串里%要写成%%,这个百分之九十九的新手都会踩一次。

第四,想居中显示某行文本,可以用getUTF8Width拿到字符串像素宽度,再计算起始 X 坐标,不用手工去数格子:

int w = u8g2.getUTF8Width(line); u8g2.drawUTF8((128 - w) / 2, 18, line);

这样改文案也不会跑偏。

完整的 demo 显示循环我贴一段在这,直接照着用就行:

void loop() { int temp_x10 = 265; char line[32]; u8g2.clearBuffer(); snprintf(line, sizeof(line), "温度%d.%d℃", temp_x10 / 10, temp_x10 % 10); u8g2.drawUTF8(0, 0, line); snprintf(line, sizeof(line), "湿度%d%%", 48); u8g2.drawUTF8(0, 18, line); strcpy_P(line, PSTR("状态: 正常")); u8g2.drawUTF8(0, 36, line); strcpy_P(line, PSTR("设备ID: A-01")); u8g2.drawUTF8(0, 54, line); u8g2.sendBuffer(); delay(1000); }

这个例子在 Uno 上编译通过,生成的 hex 文件也很小,Flash 占用根本不是问题。

4. 实际运行效果与内存占用分析

4.1 在 Arduino Uno 上跑通是什么体验

前面那套温湿度显示例程,用 Arduino Uno + 0.96 寸 SSD1306 I2C OLED 实测,编译输出大概在 19~20KB 左右,Flash 占用不到 65%,RAM 占用因为用了全屏缓冲模式,大约 1KB 出头,剩余 1KB 给局部变量,完全够用。

这里要提一下,U8g2 对 SSD1306 的驱动分好几种,名字里带_F_的是全屏缓冲模式,需要在内存里开辟一整块 1KB 的缓冲区;带_1_或_2_的是分页缓冲模式,内存占用更少,但绘制逻辑要改写成firstPage/nextPage循环。对于 Uno 这种只有 2KB RAM 的板子,如果你同时还需要比较大的动态数据,建议改用_1_模式。中文显示不是靠缓冲区才行的,分页模式同样支持drawUTF8,只是你的绘制代码要放在 do-while 循环里。

尽管分页模式能省 RAM,我在这个例子里用全屏缓冲也足够,因为其它变量很少。如果你同时接了多个传感器,有大数组,那还是建议换成分页模式。代码改动也不大:

U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); void loop() { u8g2.firstPage(); do { // drawUTF8 等绘图指令都放在这里 } while (u8g2.nextPage()); delay(1000); }

4.2 字库到底能有多小

这份温湿度项目最终需要 9 个汉字、16 个 ASCII 字符、1 个摄氏度符号,共 26 个字形。每个汉字按 16x16 算,位图数据 32 字节;每个 ASCII 按 8x16 算,位图数据 16 字节;摄氏度符号通常也是 16 字节左右。所有字形位图加起来约 600 字节,再算上字体元数据、字符映射表、宽度表,整份字库也就 1.5KB 上下。也就是说,你花 1.5KB 换来了一个完整的、可扩展的中文显示能力。

跟全量字库对比一下:

字体方案字符覆盖大约 Flash 占用推荐 MCU
U8g2 内置 unifont 中文全字库数千汉字约 100~216KBESP32、STM32F4 等大 Flash
U8g2 内置 wqy 中文 GB2312 字库6763 汉字约 216KBESP32、STM32F4 等大 Flash
本文自定义子集字库(26 字符)9 汉字 + ASCII + ℃约 1.5KBArduino Uno 完全无压力
PCtoLCD 整句位图按句覆盖视句子长度而定不明显优于字库

这个对比够直观了:如果你只显示固定几屏状态,用全量字库本质上就是开着货车去送一个快递,浪费资源还容易堵车。

4.3 从 Uno 换到 ESP32 怎么扩展

如果你从 Uno 换到 ESP32,Flash 突然大了几十倍,这套流程依然适用。只不过你可以把字符集扩得更大,比如把整个项目所有可能出现的汉字全塞进去,几百个字也才十几 KB。这时候 bdfconv 还能继续用,只是 map 文件多写几行而已。

ESP32 上还有另一个选择:直接用 U8g2 内置的文泉驿 GB2312 字体,省去生成步骤。U8g2 官方库内置了u8g2_font_wqy16_t_gb2312这类字体,中文覆盖很全,Flash 占用虽然大,但 ESP32 完全扛得住。不过我的主力小项目还是坚持用自定义字库,理由只有一个:可控。内置字库的字形你改不了,想要更粗的笔画、更大的字号、特殊的符号,最终还是得回到定制这条路。

5. 踩坑实录与排查手册

5.1 中文全是豆腐块或空白

这是最高频的问题。现象是英文字母数字显示正常,但中文位置要么是空心方块,要么干脆什么都不显示。几乎所有这类问题都指向同一个原因:当前字库数组里没有这个汉字的字形数据。

排查思路很简单。先确认 map 文件里有没有包含这个字的码点;再确认 bdfconv 的-M参数是不是真的生效了;最后在代码里用getUTF8Width打印一下期望宽度,如果宽度等于 0,基本就是字库缺这个字。解决办法是把这个字的码点补进 map,重新跑一遍 bdfconv 生成新 C 文件即可。

另外还要确认你的显示文本是标准的 UTF-8 编码。假如 Arduino IDE 的编辑器把源文件存成了 GBK 或 ANSI,那么字符串里的汉字字节跟 UTF-8 完全不同,即使字库里有字形,也无法匹配。新版 IDE 默认 UTF-8 没问题,但老版本、其它编辑器要学会检查保存编码。

5.2 用 drawStr 显示中文乱码

如果你用drawStr而不是drawUTF8,中文显示一定会出问题。U8g2 有一个按字节处理的drawStr,也有一个解析 UTF-8 的drawUTF8,两者在 ASCII 字符上表现一致,遇到中文就有本质区别。UTF-8 编码的汉字每个占 3 个字节,drawStr会把三个字节当成三个独立字符去查字库,自然出错。

解决方案就是代码里统一用drawUTF8。如果项目中有些地方必须用drawStr,比如只画纯数字,也不是不行,但容易跟中文字符串混在一起难以排查,我建议干脆全项目统一用drawUTF8,一行代码也不多,还省得后面踩雷。

5.3 AVR 上字符串从 Flash 读到 RAM 的问题

这个坑比较隐蔽,很多人怎么排查都找不出原因。Uno 上直接写u8g2.drawUTF8(0, 0, "温度正常");,有时能显示,有时乱码,有时第一次正常第二次乱码,非常诡异。

原因在于 AVR 的字符串字面量存放在 Flash 区域,而drawUTF8内部是以普通 RAM 指针的方式读取字符串的。哈佛架构下,Flash 和 RAM 是两个独立的地址空间,不能用一个普通指针去访问 Flash。如果字符串恰好被编译进了 Flash,drawUTF8按 RAM 指针访问,读出来的就是无效数据。

解决方式就是我在代码示例里演示过的:先拷贝到 RAM 数组,再传给drawUTF8。用strcpy_P加上PSTR宏是标准做法。ESP32 上不存在这个问题,因为它的 Flash 和 RAM 是统一编址,直接传字符串字面量也能工作,但strcpy_P在 ESP32 Arduino 核心中也有实现,写上去完全兼容,所以习惯上我会统一这么写,避免跨平台时踩坑。

5.4 全角标点和特殊符号缺失

界面文案里经常有全角冒号“:”、引号“”等,或者单位符号,比如℃、Ω、μ,这些字符的 Unicode 码点不在 ASCII 范围,map 文件如果只列了汉字和 ASCII,这些符号就会空白或变成豆腐块。解决办法就是遇到一个补一个,把符号的码点加进 map。实操里我习惯把项目里所有界面文本的每个字符都过一遍码点,宁可多补一个也用不到,也不要漏。

如果文本来自串口、网络或传感器返回,还需要考虑字符编码转换。比如从某些串口设备拿到的中文是 GB2312 编码,要显示到 U8g2 就得先转成 UTF-8。同样,如果你的整个项目都是 GBK 环境,那字库侧也可以按 GBK 编码生成,但那样反而复杂,不如统一转 ASCII 方案。我建议全链路统一 UTF-8,这也是 U8g2 官方推荐的。

5.5 bdfconv 报错和生成结果不正确

bdfconv 最常报的错是“glyph not found”之类,原因就两个字:缺字。要么 map 码点写错,要么 BDF 文件里没有这个字符。还有一招比较实用:先看 bdfconv 的命令帮助,用不带任何参数的方式运行一遍,它会列出所有可用的选项。不要完全照抄网上的命令,版本不同参数会略有差异。

生成结果不对,比如字体偏上、偏下、宽度不对,第一个怀疑点是 BDF 文件本身的字体属性。BDF 里有 FONTBOUNDINGBOX 和字符的 DWIDTH 信息,不同来源的字体这些字段不一样。如果你从网上下载的 BDF 转出来显示位置很奇怪,可以换一个 BDF 文件试,通常文泉驿那套是经过 U8g2 作者调过的,比较靠谱。

5.6 显示方向、分辨率配置错误

显示屏接线没问题但整体颠倒、或显示内容偏移,这是 U8g2 初始化时旋转参数的问题。U8G2_R0表示正常方向,R1旋转 90 度,R2转 180 度,R3转 270 度。分辨率选错会导致显示区域混乱,比如 128x64 的屏选了 128x32 的驱动类,画面只显示一半。这类问题跟中文无关,但遇到中文字体时更容易被误判成字库问题,所以排查顺序放前面。

另外,如果你用的是 Wokwi 这类在线仿真平台,SSD1306 和 U8g2 都是支持的,可以先在仿真里验证字库和布局效果再上真机,省去烧录下载的等待时间。仿真里显示乱码的规律跟真机一致,调试字符集非常方便。

5.7 我的一套收尾习惯

最后分享一个维护经验。我在工程根目录下会长期放两份文件:charset.map和rebuild_font.bat。前者是字符清单,每次界面文案有变动就更新;后者是重新生成字库的命令。需求方说“这里加个‘暂停’状态”,我就打开 charset.map 补一个0x6682,双击 bat 重新生成 C 文件,重新编译上传,五分钟内完事。这套流程做顺了以后,我所有用 U8g2 的中文显示项目都不再焦虑 Flash 空间,因为我知道字库只装需要的东西,永远可控。

提示:如果你第一次生成后,发现显示出来的字形跟 BDF 预览有差异,比如粗细、间距对不上,可以先确认setFontPosTop是否设置,以及字号和屏幕分辨率是否匹配。中文字体的像素风本来就跟矢量渲染不同,接受这种差异,反而能体会到点阵字体的独特质感。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询