简介:Arduino_u8g2库是面向嵌入式初学者与IoT开发者的高性能图形显示解决方案,专为Arduino平台优化,解决OLED、LCD及ePaper等多类显示屏在资源受限MCU上的驱动与图形渲染难题,广泛适用于智能硬件原型、交互装置、物联网终端等项目。资源包共160个文件,含96个C源码(实现底层驱动与字体渲染)、53个INO示例(覆盖ST75256、SSD1327、UC1611等主流屏的初始化与绘图)、4个头文件(定义核心API与设备结构),以及License、README等辅助文档,整体7.44MB,结构完整、即装即用。已有2379人学习下载,说明其在实践社区中具备高参考价值。用户可直接复用数百个跨屏兼容的驱动模块、调用成熟封装的drawString/drawRect/display等API快速实现文本多语言显示、矢量图形绘制与页面刷新,还可深入研究u8g2_fonts.c等关键源码理解位图压缩与内存分页机制,是掌握嵌入式GUI开发不可多得的实战型代码基座。
1. 这不是“又一个Arduino库”:U8g2 是 OLED 屏幕的「通用遥控器」,零基础也能点亮第一行字
你刚拆开一块 OLED 屏幕模块,插上 Arduino,打开 IDE,却卡在第一步:屏幕不亮、乱码、或者干脆黑着——不是板子坏了,也不是接线错了,而是你手里的这块屏,可能用的是 SSD1306 芯片,隔壁同学那块是 SH1106,还有一块是 ST7735(虽然它其实是彩屏)……每种芯片都像一台不同品牌的空调,遥控器互不通用。而 U8g2,就是那个能兼容几十种“空调型号”的万能遥控器:它不绑定某一块屏、不依赖某一种接线方式(I²C 或 SPI 都行),也不挑 Arduino 型号(Uno、Nano、ESP32、甚至树莓派 Pico 都能跑)。它把底层芯片差异全藏起来,只留给你一组简单命令:drawStr()写字、drawBox()画框、sendBuffer()刷屏。对新手来说,这意味着——你不用先啃完三页芯片手册,就能让屏幕显示“Hello, World!”。适合谁?就是那个连 I²C 和 SPI 都分不清、但想明天就做出带显示的温湿度计、电子时钟或小游戏的人。它不教你怎么造遥控器,而是直接把遥控器塞进你手里,还附赠说明书(而且是中文注释版)。
2. 为什么非得是 U8g2?不是 U8g,也不是 Adafruit_SSD1306
2.1 先搞清一个事实:U8g 和 U8g2 不是“升级版”,而是“重写版”
很多新手第一次搜“Arduino OLED”,会撞见两个名字:U8g 和 U8g2。看起来像 1.0 和 2.0,但真相是:U8g2 是从头写的全新库,和 U8g 没有代码继承关系。U8g 是十多年前的老将,稳定但功能封顶;U8g2 是为现代需求重构的——比如支持更多新屏幕(像常见的 128x64、128x32、甚至 64x48 小尺寸)、更省内存(这对 Uno 这类只有 2KB RAM 的板子至关重要)、原生支持多种字体(包括中文字体)、还有关键一点:它把“字体”和“图形”彻底解耦。什么意思?以前你想显示中文,得自己做点阵图、存进 Flash,一不小心就爆内存;U8g2 允许你只加载需要的字符(比如只载“温”“度”“:”“25”),而不是整套 GB2312 字库。这就像你装 App:U8g 是打包好的整套 Office(Word+Excel+PPT 全占内存),U8g2 是按需下载——要写文档才下 Word,要算数才下 Excel。
2.2 对比 Adafruit_SSD1306:专精 vs 通用,选哪个取决于你“下一步想干啥”
Adafruit_SSD1306 是官方出品、针对 SSD1306 芯片优化的库,画圆、画线、显示图片非常顺滑,文档也漂亮。但它有个硬伤:只认 SSD1306。如果你换一块 SH1106 屏(外观一模一样,价格便宜一半),代码编译通过,屏幕却只闪一下就黑——因为寄存器地址不同,它根本没发对指令。U8g2 则像一位老司机:你告诉它“我要开一辆车”,它不问品牌,自动匹配钥匙、调座椅、设导航。你换屏?只需改一行初始化代码里的设备名(比如U8G2_SSD1306_128X64_NONAME_F_SW_I2C→U8G2_SH1106_128X64_NONAME_F_SW_I2C),其余所有drawStr()、drawLine()全部照用。这不是偷懒,而是把“适配硬件”的精力,腾出来做真正有意思的事:设计界面、加交互逻辑、连传感器。我第一次做电子秤项目,中途换了三块不同来源的 OLED,U8g2 只改了三行初始化,其他代码一行没动——这种确定性,对新手就是最大的安全感。
2.3 它到底支持哪些屏幕?别背列表,记住三个“锚点”就够了
网上常列几十种支持型号,新手一看就晕。其实你只需要盯住三个真实存在的“锚点”,就能覆盖 90% 场景:
- 锚点 1:128x64 分辨率—— 最常见,淘宝搜“OLED 0.96 寸”基本都是它,芯片多为 SSD1306 或 SH1106;
- 锚点 2:128x32 分辨率—— 短条形屏,常用于仪器状态栏、小型信息提示,芯片多为 SSD1306;
- 锚点 3:64x48 分辨率—— 超迷你屏,适合可穿戴或嵌入式小设备,芯片多为 PCF8812。
U8g2 的命名规则很直白:U8G2_[芯片型号]_[分辨率]_[屏型号标识]_[模式]_[接口]。例如U8G2_SSD1306_128X64_NONAME_F_SW_I2C拆解就是:
SSD1306:驱动芯片128X64:分辨率NONAME:通用无品牌屏(不是某家定制)F:Full buffer 模式(推荐新手用,图像最稳)SW_I2C:软件模拟 I²C(不用硬件 I²C 引脚,接线更自由)
提示:新手第一次用,直接选
NONAME开头的型号,它不挑屏,兼容性最强;等你熟悉了,再研究MINI12864或UC1701_ELECTRONIC_CUBES这类定制型号。
2.4 内存友好:为什么 Uno 能跑,而有些库一编译就报“全局变量溢出”
Arduino Uno 只有 2KB RAM,但很多图形库默认建一个 128×64=8192 字节的帧缓冲区(8KB!),直接超载。U8g2 的解法很务实:它提供三种缓冲模式,新手只需知道两个:
F(Full buffer):建完整 8KB 缓冲区,画图快、刷新稳,适合 Uno 以外的板子(如 Mega、ESP32);L(Page buffer):只建 128 字节一页缓冲(128×1 像素),内存省到极致,但画复杂图形要多次刷屏,稍慢;U8G2_R0(无缓冲):不建缓冲,直接发指令,内存零占用,但无法画任意位置像素,仅限简单文本。
对 Uno 新手,我们不硬扛F模式,而是用U8G2_SSD1306_128X64_NONAME_1_SW_I2C(注意末尾是1,代表单页缓冲)。它只占约 130 字节 RAM,足够显示多行文字+简单图标,且代码和F模式几乎一样,以后换大板子,只需改型号名,不用重写逻辑。
3. 第一步:下载、安装、接线,三分钟点亮你的 OLED
3.1 下载哪里找?别去 GitHub 翻源码,用 Arduino IDE 内置库管理器最稳
U8g2 官方维护的 Arduino 版本,已收录进 Arduino IDE 的官方库索引。你不需要手动下载 ZIP、解压、放进 libraries 文件夹——那种方式容易路径错、版本混,新手三天内必翻车。正确姿势:
- 打开 Arduino IDE(建议 1.8.19 或 2.x 版本);
- 点顶部菜单工具 → 管理库…(快捷键 Ctrl+Shift+I);
- 在搜索框输入
u8g2(注意是小写 u8g2,不是 U8G2); - 找到作者为olikraus的
U8g2库(最新版通常是 2.39.x); - 点右侧「安装」按钮,等待进度条走完。
提示:安装完成后,重启 IDE。别信“已安装”就完事——重启后,你才能在示例菜单里看到它。这是血泪经验:我第一次教 A 同学,他跳过重启,死活找不到示例,折腾两小时才发现 IDE 没刷新库缓存。
3.2 接线:I²C 是新手首选,四根线搞定,不用纠结 MOSI/MISO
OLED 模块通常有 4~7 个引脚。新手请只接 4 根线,用 I²C 模式(最省事、干扰最小):
| OLED 引脚 | Arduino Uno 引脚 | 说明 |
|---|---|---|
VCC | 5V | 供电(注意:部分 OLED 标 3.3V,但多数 0.96 寸兼容 5V) |
GND | GND | 公共地 |
SCL | A5(或SCL) | I²C 时钟线(Uno 的硬件 I²C SCL 是 A5) |
SDA | A4(或SDA) | I²C 数据线(Uno 的硬件 I²C SDA 是 A4) |
注意:如果模块标着
3.3V,而你只有 5V 电源,别慌——绝大多数国产 OLED 模块内部有稳压,接 5V 也能正常工作。实在不放心,可用 Uno 的3.3V引脚供电(但亮度略低)。
3.3 第一个程序:复制粘贴就能跑,重点看懂这 5 行核心代码
打开 IDE,菜单文件 → 示例 → U8g2 → hello world,你会看到一个叫u8g2_hello_world的例子。别急着上传,我们先精读关键段落(已删减无关注释,保留最简结构):
#include <Arduino.h> #include <U8g2lib.h> // ① 必须包含这个头文件 // ② 创建 U8g2 对象:指定芯片、分辨率、屏型号、缓冲模式、接口 U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* rotation: 0° */ U8X8_PIN_NONE, /* reset pin: not used */ /* I²C address: 0x3C (default for most OLEDs) */ U8X8_PIN_NONE /* clock: not used (hardware I²C) */ ); void setup() { u8g2.begin(); // ③ 初始化屏幕,必须放在 setup() 里 } void loop() { u8g2.clearBuffer(); // ④ 清空画布(像擦黑板) u8g2.setFont(u8g2_font_ncenB08_tr); // ⑤ 选字体:8号粗宋体(带中文支持) u8g2.drawStr(0, 10, "Hello World!"); // ⑥ 在坐标(0,10)写字 u8g2.sendBuffer(); // ⑦ 把画好的内容刷到屏幕(像按“确认”键) delay(1000); }逐行解释:
- ①
<U8g2lib.h>是总入口,所有 U8g2 功能都从这里导出; - ②这行定义了
u8g2对象,括号里U8G2_R0表示屏幕不旋转(0°),后面U8X8_PIN_NONE是占位符(因为我们用硬件 I²C,不需软件模拟,所以 reset、clock 引脚设为“不用”); - ③
u8g2.begin()是启动命令,没有它,屏幕永远黑着; - ④
clearBuffer()很关键:每次画新内容前必须清屏,否则旧字会残留(像没擦干净的黑板); - ⑤
setFont()指定字体。u8g2_font_ncenB08_tr是 U8g2 自带的中英双语字体(tr= terminal raster,终端点阵),8 号大小,清晰不糊。新手别碰_cn结尾的字体(那是纯中文,体积大,易爆内存); - ⑥
drawStr(x, y, "text"):x 是横向像素位置(0 最左),y 是纵向基线位置(不是左上角!y=10 表示文字底部离顶边 10 像素,所以实际显示在第 2 行); - ⑦
sendBuffer()是最后一步:把内存里画好的图,真正发给屏幕显示。漏掉这句,屏幕永远不更新。
3.4 上传与验证:如果屏幕没亮,先查这三处物理信号
上传成功不代表屏幕一定亮。新手常见“编译绿、上传蓝、屏幕黑”。按顺序排查:
- 看串口监视器有没有报错:IDE 底部黑色窗口,如果出现
U8G2: init failed或I2C: no device found,说明通信失败; - 用万用表测 SDA/SCL 是否有电压:正常 I²C 空闲时,SDA/SCL 都应为高电平(约 5V 或 3.3V)。如果量出来是 0V,检查 VCC/GND 是否接反(反接会烧模块!);
- 用 Arduino 自带 I²C 扫描器确认地址:
- 新建草稿,粘贴 Arduino 官方 I²C Scanner 代码;
- 上传后打开串口监视器(115200 波特率);
- 如果看到
Found address: 0x3C或0x3D,说明屏在线;如果什么都没,检查接线是否松动(尤其 GND!90% 的“找不到设备”是 GND 没接牢)。
4. 常见问题排查:新手踩坑TOP5,现象、原因、解法全说透
4.1 现象:屏幕全白/全黑/闪一下就灭
原因:I²C 地址错配。多数 OLED 默认地址是0x3C,但部分模块(尤其山寨版)出厂设为0x3D。U8g2 初始化时用错地址,发指令无响应,屏幕保持上电状态(白或黑)。
解决:修改初始化对象中的地址参数。原代码:
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, U8X8_PIN_NONE);改为显式指定地址:
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, U8X8_PIN_NONE, /* I2C address */ 0x3D);提示:地址只改这一处,
0x3C和0x3D二选一,试一次就知道。
4.2 现象:文字显示为方块、乱码、或只显示 ASCII
原因:字体未正确加载,或用了不支持中文的字体。U8g2 默认字体(如u8g2_font_helvB08_tf)是纯英文,遇到中文字符自动显示为方块。
解决:换用带tr(terminal raster)或cn(chinese)后缀的字体,并确保drawStr()中文字符串用 UTF-8 编码(Arduino IDE 默认保存为 UTF-8,无需额外设置):
u8g2.setFont(u8g2_font_wqy12_t_chinese2); // 更粗的中文,但体积大,慎用 // 或更轻量的: u8g2.setFont(u8g2_font_ncenB08_tr); // 推荐!中英混排清晰,体积小 u8g2.drawStr(0, 20, "温度:25℃"); // 直接写中文,IDE 会自动转 UTF-84.3 现象:编译报错 “'U8G2_SSD1306_128X64_NONAME_F_SW_I2C' does not name a type”
原因:库未正确安装,或 IDE 未重启导致头文件未索引。也可能复制代码时,把U8G2_开头的型号名拼错了(比如少个2,写成U8G_)。
解决:
- 关闭 IDE,重新打开;
- 菜单草稿 → 包含库 → U8g2,确认能点开;
- 复制型号名时,不要手打,从示例代码里 Ctrl+C / Ctrl+V;
- 检查
#include <U8g2lib.h>是否在第一行,且没有拼成<U8g2.h>或<u8g2lib.h>(大小写敏感)。
4.4 现象:屏幕显示错位、文字被截断、Y 坐标不准
原因:U8g2 的 Y 坐标是“文字基线”位置,不是左上角。例如drawStr(0, 10)中的10,是指文字底部离屏幕顶边 10 像素,而字母“p”、“g”有下降部,会超出。新手常误以为y=0是顶边,结果字全挤在顶上被切掉。
解决:用u8g2.getStrWidth("测试")测宽,用u8g2.getAscent()获取字体上升高度(即基线到顶边距离)来精确定位:
int baseline_y = 20; // 设定基线位置 u8g2.drawStr(0, baseline_y, "Hello"); // 若想让文字整体居中,可用: int ascent = u8g2.getAscent(); // 通常为 8~12 int descent = u8g2.getDescent(); // 通常为 -2~-4 int text_height = ascent - descent; // 总高度 int y_center = 32 + (text_height / 2); // 128x64 屏,垂直居中 y≈32 u8g2.drawStr(0, y_center, "居中");4.5 现象:用 ESP32 板子,编译报错 “'Wire' was not declared in this scope”
原因:ESP32 的 I²C 库名不是Wire,而是TwoWire,且需手动创建实例。U8g2 默认用Wire,在 ESP32 上不识别。
解决:为 ESP32 显式声明 I²C 实例,并传入构造函数:
#include <Wire.h> TwoWire I2Cone = TwoWire(0); // 创建 I²C 实例(0 表示默认 I²C) U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2( U8G2_R0, U8X8_PIN_NONE, U8X8_PIN_NONE, &I2Cone // 关键!把自定义 I²C 实例传进去 ); void setup() { I2Cone.begin(21, 22); // SDA=21, SCL=22(ESP32 默认引脚) u8g2.begin(); }5. 从“点亮”到“做出东西”:三个可立即上手的进阶技巧
5.1 技巧一:用drawBox()和drawFrame()快速搭界面框架,告别“裸字”
纯文字堆砌看着廉价,加个边框、分个区域,立刻专业感拉满。U8g2 的drawBox()(实心矩形)和drawFrame()(空心边框)是零成本美化神器:
void loop() { u8g2.clearBuffer(); // ① 画一个 120x50 的主内容框(留边) u8g2.drawFrame(4, 4, 120, 50); // x=4,y=4,w=120,h=50 // ② 在框内写标题(用大号字体) u8g2.setFont(u8g2_font_ncenB10_tr); u8g2.drawStr(10, 20, "温湿度监测"); // ③ 画分割线,分上下区 u8g2.drawLine(4, 32, 124, 32); // 横线 y=32 // ④ 下半区显示数据(用小号字体,右对齐更清爽) u8g2.setFont(u8g2_font_ncenB08_tr); u8g2.drawStr(10, 45, "温度:"); u8g2.drawStr(70, 45, "25.3℃"); // 右对齐:70 = 124 - 文字宽(可预估) u8g2.sendBuffer(); delay(2000); }提示:
drawFrame()比drawBox()更省资源(只画四条线),适合做边框;drawBox()适合做背景色块(如警告红底)。所有坐标单位都是像素,128x64 屏,X 范围 0~127,Y 范围 0~63。
5.2 技巧二:用u8g2_uint_t u8g2_GetStrWidth(const char *s)实现动态右对齐
传感器数值长度会变(“25℃” vs “102℃”),固定 X 坐标会导致数字跑出屏幕。U8g2 提供getStrWidth()精确测宽,实现真·右对齐:
String temp_str = "25.3℃"; u8g2.setFont(u8g2_font_ncenB08_tr); uint8_t width = u8g2.getStrWidth(temp_str.c_str()); // 返回像素宽度 int x_right = 128 - width - 4; // 128=屏宽,-4 是右留边 u8g2.drawStr(x_right, 45, temp_str.c_str());血泪经验:我第一次做时钟,时间从 "12:05" 变成 "12:55",数字突然右移一格,还以为代码错了。后来发现是
getStrWidth()没用,硬编码 X 坐标导致的。
5.3 技巧三:用u8g2.setFontDirection(1)让文字竖排,做极简状态栏
某些场景(如电池电量、信号强度)用竖排图标更省空间。U8g2 支持字体旋转:setFontDirection(0)水平(默认),1顺时针转 90°(竖排从上到下),2180°,3270°:
u8g2.setFont(u8g2_font_unifont_t_symbols); // 符号字体 u8g2.setFontDirection(1); // 设为竖排 u8g2.drawStr(120, 10, "📶"); // 信号图标,从 (120,10) 开始向下写 u8g2.setFontDirection(0); // 记得切回水平,否则后续文字全竖着!注意:竖排时,
drawStr(x,y)的y变成起始行位置,文字向下延伸。务必在画完竖排内容后,用setFontDirection(0)切回,否则整个界面崩溃。
6. 下一步学什么?一条不绕弯的实战学习路径
从点亮第一行字,到做出能用的小项目,中间不是靠“学完所有函数”,而是靠“在具体任务里,精准调用 3~5 个函数”。我给自己和学员规划的路径,就是围绕三个真实需求展开,每个需求只深挖最相关的 U8g2 能力,绝不贪多:
6.1 需求一:做一个实时温湿度显示器(传感器 + OLED)
这是新手第一个闭环项目:读数据 → 处理 → 显示。U8g2 在这里只需掌握:
drawStr()显示数值(已会);drawFrame()画边框分区域(已会);clearBuffer()+sendBuffer()控制刷新节奏(已会);- 新增重点:
drawXBM()显示小图标(如温度计、水滴图标)。
XBM(X BitMap)是 U8g2 原生支持的极简位图格式,一张 16x16 图标仅占 32 字节内存。你不用学图像处理,直接用在线工具(如 xbm.io )把 PNG 转成 C 数组,粘贴进代码:
// 生成的温度计图标数组(16x16) static const unsigned char thermometer_xbm[] U8G2_PROGMEM = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,...... // 实际 32 字节 }; // 在 loop() 中调用: u8g2.drawXBM(5, 15, 16, 16, thermometer_xbm); // x=5,y=15,宽16,高16,数据提示:XBM 图标必须是黑白(1bit),尺寸建议 16x16 或 24x24,太大内存吃不消。先做温度计、湿度滴、电池框三个图标,界面立刻有灵魂。
6.2 需求二:做一个可交互的菜单系统(按钮 + OLED)
加一个按键,就能切换显示“温度”、“湿度”、“时间”,这才是真正的产品感。U8g2 本身不处理按键,但和digitalRead()结合极简单:
const int buttonPin = 2; int menuIndex = 0; // 0=温度,1=湿度,2=时间 void loop() { u8g2.clearBuffer(); if (digitalRead(buttonPin) == LOW) { // 按下按钮(低电平触发) delay(20); // 消抖 if (digitalRead(buttonPin) == LOW) { menuIndex = (menuIndex + 1) % 3; // 循环切换 while (digitalRead(buttonPin) == LOW) delay(10); // 等松手 } } switch(menuIndex) { case 0: u8g2.drawStr(0,20,"温度:25.3℃"); break; case 1: u8g2.drawStr(0,20,"湿度:65%RH"); break; case 2: u8g2.drawStr(0,20,"时间:14:30"); break; } u8g2.sendBuffer(); delay(100); }关键点:
delay(20)消抖是后悔药,没它你会按一次跳三页;while (digitalRead(...) == LOW)是防长按误触发,确保只算一次点击。
6.3 需求三:做一个带动画的小游戏(如贪吃蛇雏形)
用 U8g2 做动画,核心就两个函数:drawBox()画蛇身(方块)、clearBuffer()清上一帧。不需要多线程,用millis()控制节奏即可:
unsigned long lastMove = 0; const unsigned long moveInterval = 300; // 300ms 移动一次 void loop() { unsigned long now = millis(); if (now - lastMove > moveInterval) { lastMove = now; // ① 清屏(擦掉旧蛇) u8g2.clearBuffer(); // ② 画新蛇(简化版:只画一个移动的方块) static int x = 10, y = 30; x += 2; // 向右移动 if (x > 120) x = 0; // 到边回绕 u8g2.drawBox(x, y, 4, 4); // 4x4 的蛇头 } u8g2.sendBuffer(); }这就是贪吃蛇最简内核:清屏 → 计算新位置 → 画新图形 → 刷屏。后续加食物、碰撞检测、长度增长,全是这个循环的扩展。记住:动画不是“画得快”,而是“逻辑更新+画面刷新”的节奏一致。
从那以后我每次教新人,都强制走一遍这三步:先用drawStr显示传感器值,再加drawFrame搭框架,最后用drawXBM加图标——三小时,一个能拿出去展示的温湿度仪就出来了。没有玄学,没有黑匣子,只有清晰的输入(传感器读数)、确定的处理(U8g2 函数调用)、可见的输出(屏幕变化)。希望帮到你。
本文还有配套的精品资源,点击获取