简介:这套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 驱动芯片都收编了。我列几个比较常见的:
| 驱动芯片 | 常见屏幕 | 典型分辨率 |
|---|---|---|
| SSD1306 | 0.96 寸 OLED | 128x64 / 128x32 |
| SH1106 | 1.3 寸 OLED | 128x64 |
| SSD1309 | 2.42 寸 OLED | 128x64 |
| ST7920 | 经典 12864 LCD | 128x64 |
| PCD8544 | Nokia 5110 屏 | 84x48 |
| ST7565 | 绘图 LCD | 128x64 |
你只要记住一个 API 风格,换屏幕的时候改一下构造函数前缀就能跑起来。比如我现在用的屏幕是 SSD1306,如果哪天换成 SH1106 控制的 1.3 寸屏,代码里只需要把U8G2_SSD1306_128X64_NONAME_F_HW_I2C改成U8G2_SH1106_128X64_NONAME_F_HW_I2C,其他绘图代码一行都不用动。这种解耦能力,是自己封装驱动时很难快速做到的。
1.2 双缓冲机制:U8g2 不闪屏的秘密
U8g2 构造函数里有个F或1的标识,很多人不知道这是什么意思。这其实是缓冲模式:
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 的构造函数时,系统宏SDA和SCL会根据所选板子自动替换,所以写代码时直接写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_I2CSSD1306:屏幕的主控芯片,按实际情况选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是屏幕旋转方向,还有R1、R2、R3,分别对应 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 语言字体文件。流程是:
- 下载 BDF 格式的中文字体,比如文泉驿微米黑、文泉驿点阵宋体
- 打开 U8g2 源码仓库
tools/font/bdfconv目录下的转换工具 - 用命令行把 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 的drawStr和drawUTF8都不自动换行,画到屏幕右边界就直接截断。做多行文本时,需要自己维护行号和每行的像素宽度。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里能查到,挑一个顺眼的,慢慢试。
本文还有配套的精品资源,点击获取