news 2026/5/8 23:11:43

使用u8g2在STM32上绘制中文字符:项目应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用u8g2在STM32上绘制中文字符:项目应用

在STM32上用u8g2绘制中文字符:从零构建高效嵌入式HMI

你有没有遇到过这样的场景?项目明明功能都实现了,客户却皱着眉头说:“界面全是英文,我们工人看不懂啊。”——于是原本“完工”的状态瞬间被打回“待优化”。这正是许多嵌入式开发者在工业控制、医疗设备或智能家居产品开发中面临的现实问题:如何让资源有限的MCU也能显示清晰可读的中文?

今天我们就来解决这个痛点。主角是轻量级图形库u8g2和广泛使用的STM32平台。目标很明确:不加外部Flash、不用RTOS、不换大容量芯片,照样把“设置”、“报警”、“温度”这些常用汉字稳稳地画在OLED屏幕上。

这不是理论推演,而是基于多个量产项目的实战总结。我们将一步步拆解字体定制、内存管理、硬件对接和性能调优的关键技巧,最终实现一个既省资源又流畅可用的中文人机界面(HMI)系统。


为什么选u8g2?因为它够“瘦”

先说结论:如果你正在为STM32这类资源紧张的MCU做本地显示,而且需要支持非ASCII字符,那u8g2 几乎是最优解之一

它不像LVGL那样功能丰富但动辄占用几十KB RAM和上百KB Flash,也不依赖操作系统或动态内存分配。相反,它的设计哲学是“最小化运行时开销,最大化编译期确定性”。

比如一块常见的0.96英寸SSD1306 OLED屏(128×64分辨率),使用u8g2的Page Mode模式,RAM占用仅约128字节,而全缓冲模式也才1KB出头。相比之下,LVGL在这种小屏上光帧缓冲就要吃掉至少8KB RAM。

更关键的是,u8g2对中文的支持不是靠内置庞大全字库,而是通过高度可裁剪的自定义字体机制来实现。这意味着你可以只包含你需要的那几十个汉字,而不是塞进几万个用不到的字符。

📌一句话定位
u8g2 = 轻量绘图引擎 + 可定制字体系统 + 硬件无关接口

这种“按需加载”的思路,正是我们在资源受限环境下破局的核心武器。


中文显示的本质:不是“渲染”,而是“查表”

很多人一开始会误以为u8g2像手机系统一样能“自动显示任何Unicode字符”。其实不然。u8g2本身并不解析UTF-8或多语言编码,它只是个位图搬运工。

你要让它显示“中文”,就得提前准备好一张“字典”——也就是一个包含了这些汉字点阵数据的C数组文件。每次调用u8g2_DrawString()时,库函数就在这个字典里查找对应字符的位图,然后复制到显示缓冲区。

所以问题就转化了:
如何生成这张只包含你需要汉字的小型字典,并且尽可能压缩体积?

答案就是工具链:FontForge + bdfconv

字体提取实战流程

假设你的温控仪只需要显示以下词汇:

开机 停机 加热 冷却 温度 设定 当前 报警 故障 模式 手动 自动 返回 确认

共14个词,28个不同汉字。我们只需要这28个字就够了,其余4万多个统统不要。

第一步:用 FontForge 导出BDF格式
# 使用开源字体 SourceHanSansSC(思源黑体简体) fontforge -lang=ff -c " Open('SourceHanSansSC-Regular.otf'); Select(unescape('\\\\\\u5F00\\u673A\\u505C\\u673A...')); // Unicode转义序列 Generate('chinese_12px.bdf'); "

这一步将指定字符导出为标准的BDF(Bitmap Distribution Format)位图字体文件。

第二步:用 bdfconv 转成u8g2兼容的C数组
java -jar bdfconv.jar \ -v -f 0 \ -n 2 \ # 启用哈夫曼压缩 -M 0,0,255,255 \ # 映射范围(灰度→单色) -d 0 \ # 单色输出 -e UTF8 \ # 输入编码 -o u8g2_font_chinese_12x12.c \ chinese_12px.bdf

执行后你会得到两个文件:.c.h。其中.c文件里就是一个巨大的const uint8_t[]数组,包含了头部信息、哈夫曼码表、字符索引和压缩后的位图数据。

🔍实测数据参考
- 12×12像素,28个汉字
- 原始位图大小:28 × 12×12 / 8 ≈ 504 字节
- 经过RLE+哈夫曼压缩后:约1.2KB
- 若扩展至128个常用字,仍可控制在4.8KB以内

这个量级完全可以塞进STM32F103C8T6这种只有64KB Flash的入门级MCU中,无需外挂存储。


如何接入STM32?只需写好两个回调

u8g2的跨平台能力来自于其精巧的回调驱动模型。你不需要修改库代码,只要实现几个底层函数,告诉它“怎么发数据”、“怎么延时”即可。

以最常见的I²C接口SSD1306为例,我们需要提供两个回调:

1. I²C通信回调

uint8_t u8x8_stm32_i2c_cb(u8x8_t *u8g2, uint8_t msg, uint8_t arg_int, void *arg_ptr) { switch (msg) { case U8X8_MSG_BYTE_SEND: HAL_I2C_Master_Transmit(&hi2c1, SLAVE_ADDR, (uint8_t*)arg_ptr, arg_int, 10); break; case U8X8_MSG_BYTE_INIT: MX_I2C1_Init(); // 初始化I2C外设 break; case U8X8_MSG_BYTE_SET_DC: // I²C协议无DC线,忽略 break; case U8X8_MSG_BYTE_START_TRANSFER: u8x8_SetI2CAddress(u8g2, u8g2_GetI2CAddress(u8g2)); break; default: return 0; } return 1; }

这里的关键是HAL_I2C_Master_Transmit的超时时间不能设太长(建议10ms),否则会影响系统响应。

2. GPIO与延时回调(通用)

int u8x8_gpio_and_delay_cb(u8x8_t *u8g2, uint8_t msg, uint8_t arg_int, void *arg_ptr) { switch (msg) { case U8X8_MSG_DELAY_NANO: delay_nano(arg_int); break; case U8X8_MSG_DELAY_10MICRO: delay_micro(10*arg_int); break; case U8X8_MSG_DELAY_100NANO: delay_nano(100); break; case U8X8_MSG_DELAY_MILLI: HAL_Delay(arg_int); break; case U8X8_MSG_GPIO_I2C_CLOCK: break; // 忽略模拟时钟 case U8X8_MSG_GPIO_I2C_DATA: break; default: return 0; } return 1; }

这两个回调注册之后,就可以初始化整个显示系统了:

u8g2_t u8g2; void display_init(void) { u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0, // 正常方向 u8x8_stm32_i2c_cb, u8x8_gpio_and_delay_cb); u8g2_InitDisplay(&u8g2); u8g2_SetPowerSave(&u8g2, 0); // 唤醒屏幕 }

注意函数名中的_f表示 Full Buffer 模式;如果是_p则代表 Page Mode。后者更适合RAM极小的系统。


实际UI绘制:混合字体与布局技巧

现在我们已经可以画中文了,但真正的挑战在于如何让界面好看又好用

来看一个典型的双行数据显示界面:

void draw_temperature_screen(void) { u8g2_ClearBuffer(&u8g2); // 使用中文字体 u8g2_SetFont(&u8g2, &u8g2_font_chinese_12x12); u8g2_DrawString(&u8g2, 0, 12, "当前温度"); u8g2_DrawString(&u8g2, 0, 28, "设定温度"); // 切换为等宽英数字体(如u8g2_font_6x10) u8g2_SetFont(&u8g2, u8g2_font_6x10_tf); u8g2_DrawString(&u8g2, 80, 12, temp_cur_str); // "25.6" u8g2_DrawString(&u8g2, 80, 28, temp_set_str); // "30.0" u8g2_SendBuffer(&u8g2); // 刷新屏幕 }

你会发现一个问题:中文字体12像素高,英文字体只有10像素,baseline对不齐怎么办?

解决方案一:手动调整Y坐标偏移

u8g2_DrawString(&u8g2, 80, 14, temp_cur_str); // Y+2补偿

解决方案二:选用基线一致的字体组合

推荐搭配:
- 中文:12×12 或 16×16 等宽点阵
- 英文:u8g2_font_6x12u8g2_font_8x13等相近高度字体

也可以自己微调字体生成参数,在bdfconv中加入-b参数强制对齐基线。


性能与稳定性优化:避开那些“坑”

即使功能跑通了,实际部署中仍可能遇到各种诡异问题。以下是几个高频“踩坑点”及应对策略。

❌ 问题1:屏幕偶尔花屏或通信失败

原因分析:I²C总线受干扰或地址冲突。

解决方案
- 添加上拉电阻(通常模块已有,但长线需额外加强)
- 在SCL/SDA线上各串接100Ω小电阻抑制振铃
- 电源端加0.1μF陶瓷电容去耦
- 设置I²C速率不超过400kHz(尤其在板子较复杂时)

❌ 问题2:频繁刷新导致主循环卡顿

典型场景:每100ms刷新一次屏幕,结果发现按键响应变慢。

根本原因u8g2_SendBuffer()是同步阻塞操作,一次传输可能耗时数毫秒。

优化手段
- 改用Page Mode,每次只更新变化的部分页
- 将刷新操作放在低优先级任务中(如空闲循环)
- 引入“脏区域”标记机制,仅当数据变化时才重绘

static uint8_t need_refresh = 1; if (need_refresh) { draw_ui(); u8g2_SendBuffer(&u8g2); need_refresh = 0; }

❌ 问题3:堆栈溢出导致程序跑飞

隐藏风险:u8g2内部有较多递归调用和局部数组,特别是在处理复杂字体时。

建议做法
- 在启动文件中将main栈(MSP)设为至少512~1024字节
- 避免在中断服务程序中调用绘图函数
- 使用arm-none-eabi-size工具检查栈使用情况


最佳实践清单:让你的HMI更健壮

最后分享一套经过验证的工程级最佳实践,帮助你在未来项目中快速复用这套方案。

类别推荐做法
字体管理按功能划分字体文件,如font_menu_12x12,font_num_8x10
命名规范自定义字体变量名统一前缀u8g2_font_xxx
链接优化将字体放入独立section,避免挤占代码区
.fontram : { *(.u8g2_font*) } > FLASH

| ✅构建自动化| 将字体生成脚本纳入Makefile或CI流程,支持一键更新 |
| ✅调试开关| Release版本关闭U8G2_USE_PINS宏,减少GPIO模拟开销 |
| ✅功耗控制| 在待机模式下调用u8g2_SetPowerSave(&u8g2, 1)关闭显示 |

此外,还可以考虑将部分静态文本预渲染为XBM位图,进一步提升绘制速度。对于固定菜单项尤其有效。


结语:以软补硬,才是嵌入式开发的艺术

回到最初的问题:能不能在STM32上流畅显示中文?

答案不仅是“能”,而且可以做得很好——只要你愿意花点时间理解底层机制,合理裁剪资源,精细调优流程。

我们已经在智能电表、PLC调试器、便携医疗设备等多个项目中成功应用这一方案。最大的收获不是技术本身,而是那种“用软件智慧弥补硬件局限”的思维方式。

当你看到一台没有操作系统、只有几KB内存的小设备,却能清晰地显示出“系统正常”四个字时,那种成就感,远胜于堆砌高性能芯片带来的即时满足。

如果你也在为类似需求苦恼,不妨试试这条路。
从裁剪第一个汉字开始,一步步搭建属于你的本土化HMI系统。

如果你在实现过程中遇到了其他挑战,欢迎在评论区分享讨论。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/8 0:01:20

Crow Translate:轻量级多语言翻译工具的终极使用指南

Crow Translate:轻量级多语言翻译工具的终极使用指南 【免费下载链接】crow-translate Crow Translate - 一个用C/Qt编写的简单轻量级翻译器,支持使用Google、Yandex、Bing等API进行文本翻译和朗读。 项目地址: https://gitcode.com/gh_mirrors/cr/cro…

作者头像 李华
网站建设 2026/4/30 8:41:51

Dify可视化界面实时显示token使用情况

Dify可视化界面实时显示token使用情况 在构建AI应用的今天,一个看似微小却极具影响力的细节正在改变开发者的日常体验:你是否曾因为一条“Context Length Exceeded”错误而反复调试?是否在月底看到账单时才发现某个接口消耗了远超预期的资源&…

作者头像 李华
网站建设 2026/5/1 8:03:15

解锁macOS隐藏能力:iSCSI Initiator网络存储完全指南

解锁macOS隐藏能力:iSCSI Initiator网络存储完全指南 【免费下载链接】iSCSIInitiator iSCSI Initiator for macOS 项目地址: https://gitcode.com/gh_mirrors/is/iSCSIInitiator 你是否曾经为macOS本地存储空间不足而烦恼?或者需要让多台苹果电脑…

作者头像 李华
网站建设 2026/5/2 15:54:59

JPEGsnoop 图像分析工具:新手完整使用指南

JPEGsnoop 图像分析工具:新手完整使用指南 【免费下载链接】JPEGsnoop JPEGsnoop: JPEG decoder and detailed analysis 项目地址: https://gitcode.com/gh_mirrors/jp/JPEGsnoop 想要深入了解 JPEG 图像的内部结构吗?JPEGsnoop 就是你的专业助手…

作者头像 李华