news 2026/9/7 6:29:03

U8g2库实战指南:从Arduino OLED驱动到中文显示与仿真

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
U8g2库实战指南:从Arduino OLED驱动到中文显示与仿真

简介:这套Arduino u8g2图形库资源,为需要驱动OLED、LCD和电子纸显示屏的开发者,提供了完整的库源码与示例工程。压缩包内含160个文件,以96个C语言源文件构成核心绘图引擎,53个示例程序展示不同屏幕驱动芯片的初始化与调用方式,另有头文件、配置文件及许可说明,整体只有7.44MB,便于快速部署。u8g2库是经典u8g库的升级版,兼容超过两百种显示屏,支持高效的位图绘制、多语言与自定义字体,以及矩形、圆形、线条等基本图形操作,并具备旋转、镜像变换功能;其页面模式能明显降低内存占用,适合资源有限的Arduino控制器。包内示例覆盖常见OLED和LCD型号,演示了主要绘图函数的用法,结构清晰,可方便地移植到实际项目中。目前已有2366人学习下载,无论新手想快速点亮屏幕,还是进阶者希望研究底层渲染机制,这份资源都能提供扎实的参考价值。 前阵子一个做智能小车项目的朋友问我:Arduino Uno 接了一块 0.96 寸 OLED,想同时显示速度、方向角度和障碍物距离,用什么库最省事?我说你直接上 U8g2 吧,别再去啃 SSD1306 的数据手册了。很多 Arduino 玩家都走过这条路:先点亮一块屏幕,然后被驱动、字体、刷新率这些东西反复折腾,最后老老实实回到 U8g2 这个库上面。这篇我就把 U8g2 从为什么选、怎么装、怎么写代码、显示中文、到排查问题一次讲透,不管你是刚点亮第一块 OLED,还是想把界面做得更顺眼,都能找到能直接抄的东西。

1. 先搞清楚 U8g2 这个库到底解决了什么问题

1.1 一个库覆盖市面上绝大多数单色屏

U8g2 是德国开发者 Oliver Kraus 维护的开源单色图形库,目前几乎是 Arduino 社区显示方案的默认答案。它不是只支持某一块屏,而是把所有常见的单色 OLED/LCD 驱动芯片都收编了。我列几个比较常见的:

驱动芯片常见屏幕典型分辨率
SSD13060.96 寸 OLED128x64 / 128x32
SH11061.3 寸 OLED128x64
SSD13092.42 寸 OLED128x64
ST7920经典 12864 LCD128x64
PCD8544Nokia 5110 屏84x48
ST7565绘图 LCD128x64

你只要记住一个 API 风格,换屏幕的时候改一下构造函数前缀就能跑起来。比如我现在用的屏幕是 SSD1306,如果哪天换成 SH1106 控制的 1.3 寸屏,代码里只需要把U8G2_SSD1306_128X64_NONAME_F_HW_I2C改成U8G2_SH1106_128X64_NONAME_F_HW_I2C,其他绘图代码一行都不用动。这种解耦能力,是自己封装驱动时很难快速做到的。

1.2 双缓冲机制:U8g2 不闪屏的秘密

U8g2 构造函数里有个F1的标识,很多人不知道这是什么意思。这其实是缓冲模式:

  • F全称 Full Buffer,完整帧缓冲。所有绘图指令先画到内存里的一块缓冲区,等你说sendBuffer()时一次性推到屏幕上。
  • 1是 Page Buffer,页面缓冲。只留一页(比如 128x8 像素)的内存,绘图时分页往屏幕送。

拿生活类比:完整帧缓冲就像你先在草稿纸上画完整张画,再贴到墙上,观众看到的是最终结果;页面缓冲则是你直接拿笔往墙上分区画,画完上半区再画下半区,观众能看到过程。所以 F 模式画面更干净,不闪不撕裂,代价是吃内存。128x64 分辨率全缓冲需要 1024 字节 SRAM,Arduino Uno 总共只有 2KB,用 F 模式会非常紧,Nano 也一样。ESP32、STM32F103C8T6 这种内存大的板子随便用 F。

还有一个容易忽略的对比对象:U8g2 的兄弟库 U8x8。U8x8 只能显示文本和简单字符,不能画点线圆,但速度极快、内存占用极小。如果你只需要显示两行数字,U8x8 更合适;只要你想画进度条、波形图或者做图形界面,老老实实用 U8g2。

1.3 为什么不建议自己手写驱动

刷过 SSD1306 数据手册的同学应该知道,初始化序列、翻页寻址、显存分割这些内容,自己踩一遍少说一两天,而且很容易出现基于某一块屏写出来的驱动换屏就废的情况。U8g2 把这些芯片差异全部封装好了,内部还有软件 I2C、硬件 I2C、SPI 多种接口选择,你要做的只是选对构造函数。我见过不少项目号称“尽量少依赖库”,结果显示模块拖了整个项目进度。U8g2 本身开源、免费、社区维护活跃,放心依赖没毛病。

2. 安装与库目录改造

2.1 用库管理器一次性装好

在 Arduino IDE 里选择工具 -> 管理库,搜索框输入U8g2,找到作者 Oliver 的U8g2直接安装就行。这里提醒一句:如果你的项目里之前装过老版本的 U8glib,最好把老库先删掉或者注意两者的区别,它们在函数命名上有不少重叠,同时保留容易报重复定义。

安装完成后,在文件 -> 示例 -> U8g2下能看到官方示例列表,比如full_buffer目录里的GraphicsTest。打开这个示例直接编译上传,是验证板子和屏幕能不能正常工作的最快方式。我第一次接触 U8g2 就是靠这个示例确认硬件没接错的。

2.2 库不想装在 C 盘,位置怎么改

很多人问 Arduino 安装库怎么改位置。Arduino IDE 默认会把库放在用户目录下,比如 Windows 的C:\Users\你的用户名\Documents\Arduino\libraries。C 盘空间吃紧或者你想把整个开发环境迁到 D 盘时,可以这样改:

打开文件 -> 首选项 -> 设置,找到项目文件夹位置,也就是 Sketchbook 位置,把它改到D:\ArduinoSketchbook,重启 IDE。之后再通过库管理器安装库,IDE 就会把库放到D:\ArduinoSketchbook\libraries下面。这是一个全局改动,对后装的任何库都生效,比单个库手动挪来挪去干净得多。

如果是手动安装源码包,从 GitHub 下载 zip,解压后把文件夹名字改干净(比如去掉-master后缀),放到上面说的libraries目录里,重启 IDE 即可。改位置这条经验我是在做多用户开发的时候踩出来的,换了电脑路径不对导致库一直识别不到,后来统一约定把 sketchbook 路径固定下来,问题再没出现过。

2.3 不同主控板要注意什么

库管理器装的是同一套 U8g2,但不同板子的 I2C 引脚不同。Arduino Uno/Nano 默认 SDA 是 A4、SCL 是 A5;ESP32 开发板默认 SDA 是 GPIO21、SCL 是 GPIO22;ESP8266(比如 Wemos D1 mini)默认 SDA 是 GPIO4(D2)、SCL 是 GPIO5(D1)。用硬件 I2C 的构造函数时,系统宏SDASCL会根据所选板子自动替换,所以写代码时直接写SCL, SDA一般没问题,但接线的时候一定要查一下板子引脚定义,别按 Uno 的图去接 ESP32。

3. 一个能直接抄的 OLED 显示实例

3.1 I2C 接线:四根线的事

以最常见的 0.96 寸 SSD1306 I2C 屏为例,模块上一般有 4 个引脚:VCC、GND、SDA、SCL。Arduino Uno 接线是:

  • VCC -> 3.3V 或 5V(模块上有稳压电路的用 5V 也没事,没有稳压的保险起见用 3.3V)
  • GND -> GND
  • SDA -> A4
  • SCL -> A5

一些模块还有 RES 复位脚,如果构造函数里把复位脚写成了U8X8_PIN_NONE,那 RES 可以悬空;如果构造函数里填了某个引脚编号,就必须把 RES 接到对应引脚上。我习惯直接用U8X8_PIN_NONE,让库自己处理,少一根线少一个错误源。

3.2 构造函数到底怎么选

U8g2 构造函数全名很长,很多人一看就懵。拆开看其实有规律:

U8G2_SSD1306_128X64_NONAME_F_HW_I2C
  • SSD1306:屏幕的主控芯片,按实际情况选
  • 128X64:屏幕分辨率
  • NONAME:代表屏厂商的命名,通常 0.96 寸的通用 SSD1306 屏选 NONAME 就行
  • F:完整帧缓冲
  • HW_I2C:使用硬件 I2C,还有SW_I2C软件模拟 I2C、HW_SPI

构造函数最后面的三个参数:

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);

U8G2_R0是屏幕旋转方向,还有R1R2R3,分别对应 0 度、90 度、180 度、270 度旋转。U8X8_PIN_NONE是复位脚不使用。后面的SCL, SDA指定硬件 I2C 引脚,一般直接写这两个宏。

3.3 静态显示:从初始化到输出第一行字

看一个最简示例:

#include <Arduino.h> #include <Wire.h> #include <U8g2lib.h> U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, SCL, SDA); void setup(void) { u8g2.begin(); u8g2.setFont(u8g2_font_ncenB14_tr); u8g2.clearBuffer(); u8g2.drawStr(0, 24, "Hello U8g2!"); u8g2.drawFrame(4, 34, 120, 22); u8g2.sendBuffer(); } void loop(void) { }

关键就几行:begin()完成屏幕初始化;setFont()切换字体;clearBuffer()清空缓冲区;drawStr()在指定坐标画字符串;sendBuffer()把缓冲区内容真正推送到屏幕。

这里最低级的错误就是画完东西忘了调sendBuffer(),屏幕会一直是全空或者旧内容。我第一次用的时候就犯过,画了半天调试输出全是黑的,最后发现缓冲区确实画了,就是没送出去。从那以后我养成了先写sendBuffer()再往里填绘图代码的习惯。

3.4 动态刷新:别用 delay,学会用时间戳

如果要在屏幕上做计数器、进度条这类动态效果,不能每画一帧就delay(100),否则其他逻辑全部被卡住。正确的做法是维护一个时间戳:

#include <Arduino.h> #include <Wire.h> #include <U8g2lib.h> U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, SCL, SDA); void setup(void) { u8g2.begin(); u8g2.setFont(u8g2_font_ncenB14_tr); } void loop(void) { static uint32_t lastTime = 0; static int counter = 0; static int percent = 0; if (millis() - lastTime >= 500) { lastTime = millis(); counter++; percent = (percent + 3) % 100; } uint8_t barWidth = percent * 116 / 100; u8g2.clearBuffer(); u8g2.drawStr(4, 20, "Demo"); u8g2.drawStr(60, 20, String(counter).c_str()); u8g2.drawFrame(4, 32, 120, 14); if (barWidth > 0) { u8g2.drawBox(6, 34, barWidth, 10); } u8g2.sendBuffer(); }

这个逻辑是:只有当millis()间隔超过 500 毫秒时才更新计数和进度条,但每轮循环都会刷新画面。这样一来,计数逻辑和显示逻辑分开了,后续想加按键检测、传感器读取都不受显示刷新拖累。

3.5 提速:400kHz I2C 不是玄学

SSD1306 支持最高 400kHz 的 I2C 时钟,而 Arduino 默认通常是 100kHz。在setup()里加一句:

Wire.setClock(400000);

实测刷新一帧全屏从原来大约 30ms 降到十几毫秒,肉眼能明显感觉到流畅。不过如果接线比较长、线上干扰大,400kHz 下可能出现花屏,这种情况降回 100kHz 更稳。先跑 100kHz 确认屏幕稳定,再提速,是稳妥顺序。

4. U8g2 的进阶玩法:中文显示与在线仿真

4.1 自带字体不支持中文,怎么办

U8g2 内置的字体非常多,数字、字母、图标都有,但默认字体里没有 CJK 中文。想要在 OLED 上显示“你好”这类中文,得先有自己的中文字体数组。

常用做法是拿文泉驿点阵字体,转出 U8g2 能用的 C 语言字体文件。流程是:

  1. 下载 BDF 格式的中文字体,比如文泉驿微米黑、文泉驿点阵宋体
  2. 打开 U8g2 源码仓库tools/font/bdfconv目录下的转换工具
  3. 用命令行把 BDF 转成 C 数组

命令行大致是这样的:

bdfconv -v -f -n u8g2_font_wqy12_t_chinese -m "32-127,0x4e00-0x9fff" wqy-microhei-12.bdf -o wqy12_t_chinese.c

参数里-n是给新字体取名字,-m指定要包含的字符范围,-o是输出文件。生成的.c文件放到项目目录里,Arduino IDE 会自动编译进去。代码里这样用:

extern const uint8_t u8g2_font_wqy12_t_chinese[] U8G2_FONT_SECTION("u8g2_font_wqy12_t_chinese"); u8g2.setFont(u8g2_font_wqy12_t_chinese); u8g2.drawUTF8(0, 20, "你好,U8g2");

注意这里用的是drawUTF8,不是drawStr,因为中文字符串是 UTF-8 编码。Arduino IDE 默认源码文件就是 UTF-8 编码,所以直接写中文没问题。

有个坑必须提醒:全量中文转出来的字体文件很大,动辄几百 KB,Arduino Uno 的 Flash 根本放不下。我的习惯是先用脚本把项目里出现的中文字符收集出来,再和常用字表合并去重,只保留需要的那几百个字。这样 12 像素中文字体压缩到几十 KB,Uno 勉强能跑,ESP32 就毫无压力。

4.2 没有硬件也能调 U8g2:在线仿真

热词里有“u8g2 模拟器”和“wokwi 仿真平台 arduino”,这两个确实是好东西。Wokwi 是一个浏览器里的硬件仿真平台,支持 Arduino Uno、ESP32 等板卡,也支持 SSD1306 OLED。

在 Wokwi 新建一个 Arduino Uno 项目,添加 SSD1306 OLED,在项目设置里引入 Arduino 库 U8g2,然后把上面的示例代码贴进去就能直接跑。我经常在没有屏幕在身边的时候用 Wokwi 验证绘图坐标和刷新逻辑,确认没问题再拿到实机上跑,省掉了很多来回烧录的时间。

U8g2 官方仓库的tools目录里其实也有一个模拟器,但要在 PC 上编译运行,需要配 SDL 环境,比 Wokwi 麻烦不少。对多数人来说,Wokwi 在线跑一遍 U8g2 就够用了。

4.3 屏幕自动换行?没有,得自己处理

U8g2 的drawStrdrawUTF8都不自动换行,画到屏幕右边界就直接截断。做多行文本时,需要自己维护行号和每行的像素宽度。12px 字体的行高一般 14~16 像素,第二行起始 y 坐标要比第一行多 16 左右。一个简单的思路是封装一个drawMultiLine()函数,按\n分割字符串,逐行绘制。说实话这个功能不算难,但很多新手第一次遇到时都会觉得“咦,怎么不换行”。

5. 高频问题排查速查表

5.1 现象、原因、解决

现象可能原因解决办法
屏幕完全不亮接线错误、供电不足检查 VCC/GND,先用 5V 供电试试;用 I2C 扫描程序确认设备地址
屏幕上只有白点或花屏I2C 地址错误、总线速度过高常见地址是 0x3C,也有 0x3D;把 Wire 时钟降到 100kHz
显示内容闪烁、有残影用了页面缓冲模式,或刷新频率不固定换成F模式;用millis()固定刷新间隔
中文显示乱码字体没生成正确,或用了drawStr确认字体名称一致,改用drawUTF8
编译报 RAM 不足故意选了完整帧缓冲换成_1_页面缓冲模式,或换 ESP32 等大内存板卡
屏幕亮度很低对比度参数默认偏暗begin()后调用u8g2.setContrast(180)试试

I2C 扫描程序网上到处都是,核心就几行:Wire.begin(),然后循环扫描地址。把所有可能地址都扫一遍,输出到串口,立刻就能知道屏幕挂在哪个地址上。很多模块背面会直接标出地址,但改过地址的模块就得靠扫描确定。

5.2 我踩过的两个大坑

第一个坑是 Uno + F 模式。当时项目要显示动态曲线,我图省事选了_F_HW_I2C,编译能过,上传之后一跑就随机重启。查了半天,纳米级的内存余量经不起字符串拼接的临时变量,最后换成_1_页面缓冲模式才稳定。后来换到 ESP32 上我才重新用回 F 模式。建议 Uno/Nano 用户默认选_1_模式,别挑战 2KB SRAM 的极限。

第二个坑是 Wemos D1 mini 上的接线。我把 SDA 接到了 D2、SCL 接到 D1,但代码里写的是U8G2_SSD1306_128X64_NONAME_F_SW_I2C软件 I2C,没有指定引脚,默认用的还是硬件宏对应的引脚,结果屏幕不亮。软件 I2C 构造函数后面可以传自定义引脚,比如u8g2(U8G2_R0, U8X8_PIN_NONE, /* clock=*/ D1, /* data=*/ D2),但一定要和实际接线对应上。很多“屏幕不亮”的案例,最后查出来都是软硬件引脚对不上。

STM32F103C8T6 用 Arduino 框架跑 U8g2 也是同理,先确认你的 I2C 引脚映射,硬件 I2C1 默认 PB6/PB7,如果你用的是 PB8/PB9,就需要在代码里指定或者用软件 I2C。

5.3 连续刷新时的主题切换技巧

做界面切换时,如果不先clearBuffer()就画新界面,旧内容会残留。虽然 F 模式下sendBuffer()会覆盖整屏,但clearBuffer()仍然建议每次刷新开头都调一次。另外,如果想做局部更新减少刷新时间,可以先只擦除需要改变的区域,比如用一个填满背景色的drawBox覆盖旧内容,再画新的文字。这个技巧在 ESP32 上做仪表盘界面时非常有用。

6. 几个我至今还在用的使用习惯

先说第一个习惯:新建项目永远是先跑官方GraphicsTest示例,再改自己的代码。这个示例把所有基本绘图函数都跑了一遍,屏幕不会显示、旋转方向不对、刷新闪屏之类的问题一眼就能看出来。很多人拿到新屏直接粘贴自己的业务代码,出了问题就分不清是硬件还是代码的问题。

第二个习惯是显示和逻辑分离。我把所有 U8g2 绘图代码封装到updateDisplay()函数里,主循环只负责采集数据和修改状态。这样一来,如果传感器读取阻塞了,屏幕内容不会跟着乱掉;想改界面布局,也只需要动一个函数。

第三个习惯是串口日志先于屏幕调试。很多人一上来就只靠屏幕输出调试信息,屏幕正常还好,屏幕一出问题就完全抓瞎。我在代码里会同时往Serial.println和屏幕写日志,串口能告诉我程序跑到哪一步了,屏幕能告诉我显示效果是否符合预期。两边对照,问题定位速度快得多。

最后再分享一个小技巧:U8g2 的字体选择对画面质感影响很大。u8g2_font_ncenB14_tr适合标题,u8g2_font_6x10_tf适合小号正文,u8g2_font_courB18_tn适合做成大数字显示。我做过一个电子钟,用大数字字体加setContrast调亮度,显示效果比默认字体好一大截。字体列表在 U8g2 源码的tools/font/build/single_font_all.txt里能查到,挑一个顺眼的,慢慢试。

本文还有配套的精品资源,点击获取

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

XMLViewer:一站式解决XML格式化、树形查看与校验问题

简介&#xff1a;这是一款轻量级的 XML 查看器工具&#xff0c;主要面向开发人员、测试人员以及需要频繁检查 XML 数据格式的技术用户。安装后&#xff0c;用户可直接在系统右键菜单中调用 View 功能&#xff0c;快速打开 XML 文件并查看其层级与缩进结构&#xff0c;便于第一时…

作者头像 李华