简介:Arduino_u8g2库是面向嵌入式初学者与物联网开发者的高性能图形显示解决方案,专为Arduino平台优化,解决OLED、LCD及ePaper等多类显示屏在资源受限微控制器上的驱动与图形渲染难题,广泛适用于智能硬件原型、交互艺术装置、低功耗电子纸项目等场景。资源包共160个文件,含96个C源码(实现底层驱动与字体渲染)、53个INO示例程序(覆盖ST7565/SSD1327/UC1611等主流屏的初始化与绘图实操)、4个头文件(定义核心API与设备结构),辅以CPP、LICENSE、README等支撑文件,整体7.44MB,结构完整、即装即用。已有2379人学习下载,资源内容深度契合u8g2官方架构,包含u8g2_fonts.c、u8x8_d_st75256.c等关键驱动模块及内存管理(u8g2_d_memory.c)与页面模式实现,可帮助开发者快速掌握跨屏适配、多语言文本显示、位图动画及低RAM绘图等核心能力。
1. Arduino U8g2 库:不是“另一个 OLED 驱动”,而是你绕不开的跨屏兼容黑匣子
你手头有块 SSD1306 的 0.96 寸 OLED,接上 Arduino Uno,用 Wire.h 写了十几行 I²C 初始化,结果屏幕只闪一下就黑屏;换块 SH1106 的 1.3 寸屏,连编译都报SSD1306 not defined;更玄学的是——同一份代码,在 Nano 上跑得飞起,在 Mega2560 上却显示错位、字符重叠。这不是你的接线问题,也不是电源不稳,而是你正站在 U8g2 的门槛外反复试探,却没意识到:U8g2 不是“库”,它是一套屏驱动抽象层 + 字体渲染引擎 + 硬件时序调度器的三合一内核。它专治各种 OLED/ST7735/PCD8544 屏的“认不出、点不亮、画不对”三大顽疾,尤其适合需要在多个硬件平台(Uno/Nano/Mega/ESP32/STM32)间快速迁移显示逻辑的嵌入式项目。如果你正在做带本地 UI 的传感器节点、便携调试终端或教学实验板,又不想为每块新屏重写底层驱动,那 U8g2 就是你此刻最该拆开、看懂、亲手编译进固件里的那个“后悔药”。
2. 为什么非得是 U8g2?从选型逻辑到核心架构拆解
2.1 U8g2 vs U8g1 vs Adafruit_SSD1306:三者根本不在一个维度上
很多人误以为 U8g2 是 U8g1 的“升级版”,其实它们是两条技术路线:
- U8g1是纯 C 实现的单色图形库,无字体缓存、无抗锯齿、无旋转支持,所有绘图操作直通硬件寄存器,内存占用极低(<2KB RAM),但开发体验接近裸机编程;
- Adafruit_SSD1306是面向单一芯片(SSD1306)的 Arduino 封装,封装了 I²C/SPI 初始化和基础绘图函数,但一旦换屏(比如换成 SH1106),就得重写初始化序列、修改对比度寄存器地址、手动适配页地址模式——本质是“一屏一库”;
- U8g2则采用“设备描述符 + 绘图缓冲区 + 字体字形表”三层架构:
- 第一层:
u8g2_t结构体存储当前屏的控制器类型(如U8G2_R0)、总线类型(I²C/SPI)、引脚映射、时序参数; - 第二层:可选 16/64/128 字节的帧缓冲区(
u8g2_SetBufferPtr()),决定是否启用双缓冲防闪烁; - 第三层:内置 22 种可缩放字体(
u8g2_font_*_tf),全部预编译为位图数组,调用u8g2_DrawStr()时直接查表输出,不依赖printf或String类,零动态内存分配。
- 第一层:
提示:U8g2 的“2”不是版本号,而是指“第二代通用图形库”(Universal Graphics Library 2nd generation),其设计目标就是让
u8g2_FirstPage()→u8g2_DrawStr()→u8g2_NextPage()这三步成为所有单色屏的统一入口,屏蔽硬件差异。
2.2 U8g2 的真实能力边界:它能做什么,又坚决不做什么
U8g2 的能力必须放在嵌入式资源约束下理解:
- ✅能做:
- 支持 120+ 种控制器(SSD1306/SH1106/PCD8544/ST7735/LCD1602 等),覆盖 99% 常见单色/灰度/彩色小尺寸屏;
- 提供 8 种旋转模式(
U8G2_R0~U8G2_R3+U8G2_MIRROR变体),无需修改底层驱动即可翻转 UI; - 内置 16 位灰度模拟(通过抖动算法),在单色屏上实现伪灰度图像;
- 支持自定义字体(
.bdf转.c工具链完整),可生成任意大小、任意字符集的字体数组。
- ❌坚决不做:
- 不支持真彩色(RGB565/RGB888),所有彩色屏(如 ST7735)仅支持 16 级灰度或 3 位色深;
- 不提供触摸屏驱动,不集成 SPI Flash 文件系统,不处理 USB CDC 串口协议;
- 不自动检测屏型号——你必须在代码里明确写出
U8G2_SSD1306_128X64_NONAME_F_HW_I2C这类超长构造器名,名字即配置。
2.3 构造器命名规则:读懂那一长串下划线,你就赢了一半
U8g2 所有构造器名都遵循固定模式:U8G2_<CONTROLLER>_<RESOLUTION>_<VARIANT>_<TYPE>_<BUS>
<CONTROLLER>:控制器芯片名(SSD1306,SH1106,PCD8544);<RESOLUTION>:分辨率(128X64,128X32,64X48);<VARIANT>:硬件变种(NONAME表示无品牌公版,WEMOS表示 Wemos D1 Mini 专用,160X80表示高分辨率变体);<TYPE>:功能类型(F= full buffer,N= no buffer,L= low memory,TWI= Two Wire I²C);<BUS>:总线类型(HW_I2C= 硬件 I²C,SW_I2C= 软件模拟 I²C,HW_SPI= 硬件 SPI)。
例如:
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);这行代码声明了一个使用硬件 I²C、全缓冲、0 度旋转、无复位引脚的 SSD1306 屏对象。其中U8G2_R0是旋转参数,U8X8_PIN_NONE表示不使用硬件复位(靠软件复位),这些参数一旦写错,编译不报错,但运行时必黑屏。
3. 从零部署:Arduino IDE 下 U8g2 安装、验证与最小可运行例程
3.1 官方安装路径:避开 GitHub ZIP 直接解压的坑
U8g2 官方维护两个渠道:
- Arduino Library Manager(推荐):打开 IDE →
工具→管理库→ 搜索U8g2→ 选择U8g2 by Oliver Kraus→ 点击安装。此方式自动处理依赖(U8x8子库),且更新及时; - GitHub 手动安装(慎用):若需特定 commit(如修复某屏的时序 bug),应下载
master分支 ZIP,解压后重命名为U8g2,放入Arduino/libraries/目录。切勿解压出U8g2-master文件夹——IDE 会因文件夹名含-而拒绝加载。
注意:安装后重启 IDE,否则
#include <U8g2lib.h>仍会报错。某些旧版 IDE(<1.6.12)需手动删除U8g2/src/clib/下的u8g2_d_memory.c(存在编译冲突),新版已修复。
3.2 最小可运行例程:5 行代码点亮 OLED
以下代码适用于绝大多数 I²C OLED(SSD1306/SH1106),经实测在 Uno/Nano/ESP32 上均通过:
#include <U8g2lib.h> #ifdef U8X8_HAVE_HW_I2C #include <Wire.h> #endif U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); void setup() { u8g2.begin(); // 必须调用:初始化 I²C 并发送屏复位序列 u8g2.setFont(u8g2_font_ncenB08_tr); // 加载 8px 高无衬线字体(tr = terminal) u8g2.drawStr(0, 10, "Hello U8g2!"); // (x, y) 中 y 是基线位置,非像素行号 } void loop() { u8g2.firstPage(); // 开启帧缓冲,准备绘制 do { u8g2.drawStr(0, 10, "Running..."); // 绘制内容 } while (u8g2.nextPage()); // 切换到下一帧并刷新屏幕 }关键参数说明:
u8g2.begin():内部执行Wire.begin()+ 控制器初始化序列(如 SSD1306 的0xAE关闭显示 →0xAF开启显示);u8g2.setFont():字体名ncenB08_tr中ncen= new century schoolbook,B= bold,08= 8px 高,tr= terminal(等宽字符);u8g2.drawStr(x, y, str):y是文字基线(baseline)纵坐标,不是第一行像素。例如y=10时,字母 'p' 的下降部分会延伸到 y=10 以下,实际可见区域从 y=2 开始;u8g2.firstPage()/u8g2.nextPage():U8g2 的双缓冲机制。firstPage()清空缓冲区并准备绘制,nextPage()将缓冲区内容批量写入屏显存并触发刷新。漏掉nextPage()就永远不显示。
3.3 验证是否真点亮:用u8g2_DrawBox()替代drawStr()
文字显示失败常因字体未加载或坐标越界,此时用绘图指令验证硬件链路:
void setup() { u8g2.begin(); // 绘制一个 20x20 像素的白色方块,位于屏幕中心 u8g2.firstPage(); do { u8g2.drawBox(54, 22, 20, 20); // (x, y, w, h) —— x,y 是左上角坐标 } while (u8g2.nextPage()); }若看到方块,证明 I²C 通信、屏供电、复位时序全部正常,问题一定出在字体或字符串绘制逻辑上。
4. 避坑指南:U8g2 开发中 5 个血泪经验换来的高频翻车点
4.1 现象:屏幕完全不亮,Serial 输出无报错
原因:I²C 地址错误或硬件连接问题。U8g2 默认使用0x3C(SSD1306)或0x3D(SH1106),但部分国产模块地址被硬编码为0x78(7-bit 地址左移一位后的 8-bit 值)。
解决:用 I²C 扫描程序确认真实地址:
#include <Wire.h> void setup() { Wire.begin(); Serial.begin(9600); Serial.println("Scanning I2C bus..."); byte error, address; int nDevices; for(address = 1; address < 127; address++ ) { Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { Serial.print("Found device at 0x"); Serial.println(address, HEX); } } }若扫描到0x78,则需改用U8G2_SSD1306_128X64_NONAME_F_SW_I2C构造器,并手动指定 SDA/SCL 引脚及地址:
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /*sda=*/A4, /*scl=*/A5, /*reset=*/U8X8_PIN_NONE); u8g2.setI2CAddress(0x78); // 强制设置 I²C 地址4.2 现象:屏幕显示乱码、字符错位、部分区域空白
原因:分辨率与构造器不匹配。例如用U8G2_SSD1306_128X64驱动一块物理分辨率为128X32的屏,U8g2 会按 64 行分配缓冲区,导致后 32 行数据溢出到非法内存。
解决:严格核对屏规格书中的Width和Height,选用对应构造器。常见混淆:
| 物理屏型号 | 实际分辨率 | 正确构造器名 |
|---|---|---|
| 0.96" SSD1306 | 128×64 | U8G2_SSD1306_128X64_NONAME_F_HW_I2C |
| 0.49" SSD1306 | 64×32 | U8G2_SSD1306_64X32_UNIVISION_F_HW_I2C |
| 1.3" SH1106 | 128×64 | U8G2_SH1106_128X64_WINSTAR_F_HW_I2C |
4.3 现象:ESP32 上编译报错‘u8g2_uint_t’ does not name a type
原因:ESP32 核心库版本过低(<2.0.0),其stdint.h未正确定义uint8_t等类型,与 U8g2 的类型别名冲突。
解决:升级 ESP32 开发板管理器至最新版(目前为 2.0.16),或在U8g2/src/clib/u8g2.h开头添加:
#ifndef __UINT8_T typedef unsigned char uint8_t; typedef signed char int8_t; #endif4.4 现象:使用u8g2.setFontMode(1)启用反色后,整个屏幕变黑
原因:setFontMode(1)是“反色模式”,但 U8g2 的反色是针对字体前景/背景的局部反色,不是全局屏反色。若当前背景为黑色(默认),开启反色后字体变黑,与背景同色即不可见。
解决:反色需配合u8g2.setColorIndex(0)(设为黑色)和u8g2.setColorIndex(1)(设为白色)使用,或直接用u8g2.setInverseFont(1)对单个字体启用反色。
4.5 现象:SPI 屏(如 ST7735)显示图像严重偏移、颜色错乱
原因:SPI 总线速率过高导致时序紊乱。U8g2 默认 SPI 时钟为 4MHz,但部分廉价 ST7735 模块只能稳定工作在 1MHz 以下。
解决:在u8g2.begin()后立即降低 SPI 速率:
#include <SPI.h> void setup() { u8g2.begin(); #ifdef U8X8_HAVE_HW_SPI SPI.setFrequency(1000000); // 强制设为 1MHz #endif }5. 进阶实战:用 Python 脚本批量生成自定义中文字体,彻底摆脱英文屏
U8g2 内置字体全是 ASCII,要显示中文必须自己造字。网上流传的手动取模法(用 Photoshop 截图 → 用在线工具转 HEX)效率极低且易出错。我用 Python 写了个自动化流程,10 分钟生成可用的 GB2312 字体数组:
5.1 准备工作:安装依赖与获取字库
pip install Pillow fonttools # 下载开源中文字体(推荐 NotoSansCJKsc-Regular.otf,Google 免费授权) wget https://noto-website-2.storage.googleapis.com/pkgs/NotoSansCJKsc-hinted.zip unzip NotoSansCJKsc-hinted.zip5.2 核心脚本:gen_chinese_font.py
from PIL import Image, ImageDraw, ImageFont import sys def char_to_bitmap(char, font_path, size=16): """将单个汉字转为 16x16 位图数组""" font = ImageFont.truetype(font_path, size) img = Image.new('1', (size, size), color=0) # 黑底 draw = ImageDraw.Draw(img) draw.text((0, 0), char, font=font, fill=1) # 白字 pixels = list(img.getdata()) # 转为字节:每 8 像素为 1 字节,MSB 在前 bytes_data = [] for i in range(0, len(pixels), 8): byte_val = 0 for j in range(8): if i+j < len(pixels) and pixels[i+j]: byte_val |= (1 << (7-j)) bytes_data.append(byte_val) return bytes_data if __name__ == "__main__": chars = "你好世界ArduinoU8g2" # 要生成的字符列表 font_path = "./NotoSansCJKsc-Regular.otf" print("#include <U8g2lib.h>") print("static const uint8_t chinese_font[] U8G2_FONT_SECTION(\"chinese_font\") = {") for i, c in enumerate(chars): bitmap = char_to_bitmap(c, font_path, 16) print(f" // '{c}' (U+{ord(c):04X})") print(" " + ", ".join(f"0x{b:02X}" for b in bitmap) + ",") print("};")5.3 在 Arduino 中调用自定义字体
将脚本输出保存为chinese_font.h,在主程序中:
#include "chinese_font.h" // 注册字体(必须在 setup() 中调用) u8g2.setFont(chinese_font); u8g2.setFontDirection(0); // 字体方向:0=水平,1=垂直 u8g2.drawStr(0, 20, "你好世界"); // 注意:中文字体无 kerning,需手动计算间距提示:U8g2 的中文字体是“位图字体”,每个字固定 16×16 像素。若想显示更清晰,可生成 24×24 版本,但内存占用翻倍(每个字 48 字节)。从那以后我每次做中文 UI 项目,都强制走一遍这个 Python 脚本——它比手动取模快 20 倍,且零出错。希望帮到你。
本文还有配套的精品资源,点击获取