u8g2驱动128x64 OLED全攻略:安装、字体、显存与排错
简介U8g2 2.27.6是一份用于Arduino IDE的图形显示库压缩包面向需要驱动OLED、LCD及电子纸屏幕的开发者能统一解决不同屏幕接口和字体显示问题。包内共159个文件以C语言源码、INO示例、H/CPP头文件为主并附带TXT/MD文档、许可证及版本变更说明压缩后约10MB结构清晰便于解压后直接引用。功能上该版本内置上百种字体支持I2C、SPI、并行接口和200余款显示屏提供画点、线、图形、滚动、旋转等API且针对Arduino有限内存做了优化在UNO、ESP8266、ESP32上均可流畅运行。资源已有3296人学习下载既能用于学习屏幕驱动原理也能作为项目底包快速调用减少底层驱动调试时间快速实现界面显示。1. 一块 128×64 的屏为什么值得专门装一个 u8g2 zip 包拿到一块 128×64 的 OLED很多人以为最难的部分是画曲线、做动画实际上第一个晚上的大部分时间都花在“让屏幕上出现一个像素”上。u8g2 库做的是把 SSD1306、SH1106 这类单色显示控制器接到 Arduino IDE 这条路上最省事的一段而 U8g2-2.27.6.zip 这个安装包是这条路最常见的起点下载、导入、编译然后屏幕上终于有字。这篇笔记用一次完整的“安装到出图”过程讲清楚 u8g2 怎么装、怎么接、怎么调以及那些让新手反复翻车的细节。适合正在给 Arduino、ESP32 接单色屏的开发者也适合把旧屏翻出来想复用的人。装一次库不难难的是后续十几个问题的排查思路这部分会按踩坑顺序写完整。2. 把 U8g2-2.27.6.zip 装进 Arduino IDE手动安装、验证与版本锁死2.1 为什么不用库管理器zip 安装的两个硬理由Arduino IDE 自带的库管理器能搜到 u8g2点一下 Install 就能用看起来没有必要碰 zip。但实际工程里有两个场景会让库管理器变成黑匣子第一库管理器拉到的永远是当前渠道里最新的版本你没法精确锁死“我用的就是 2.27.6”同一个项目换一台电脑编译库版本漂了行为可能跟着漂第二很多开发环境是离线的实验室、车间、工控现场没有外网库管理器直接不可用。这两个理由凑在一起“下载一个 zip 包插到任何一台机器上都能复现”就变成了最稳的交付方式。我一般会优先把 zip 包留一份在项目目录里和源码放一起并在 README 里写清楚用的版本。这样同事拿到项目不需要先上网搜库直接 Add .ZIP Library 就能编。版本锁死的价值在 u8g2 这种持续更新的库上尤其明显构造宏、字体对象、方法名偶尔会调整旧代码换新库不一定编译得过就算编译得过显示行为也可能变。2.2 从 zip 到“库已导入”Add .ZIP Library 与手工解压两条路最常见做法是用 IDE 的导入功能路径是 Sketch → Include Library → Add .ZIP Library然后选中 U8g2-2.27.6.zip。IDE 会把 zip 里的内容解压到当前用户目录下的 libraries 文件夹并在左下角状态区提示“Library added to your libraries”。注意这里有个隐藏要求zip 解压后第一层必须直接是 U8g2 目录目录里是 U8g2.h、library.properties、src 这些内容。如果你下载的 zip 带了一层外层文件夹比如“U8g2-2.27.6/U8g2/U8g2.h”IDE 会导入失败或者导入进去但编译时找不到头文件。我自己的习惯是两条路都准备大多数时候用 Add .ZIP Library如果 IDE 版本太老、或者批量部署到多台机器就直接手工解压到 libraries 目录。手工解压的路径比较好定位Windows 一般在“文档我的文档/Arduino/libraries”macOS 在 ~/Documents/Arduino/librariesLinux 在 ~/Arduino/libraries。解压完把文件夹名字改成 U8g2确保里面没有嵌套的深层目录这一步就完成了。# 在 Arduino 的 libraries 目录下确认 u8g2 已经就位 cd ~/Documents/Arduino/libraries ls -d U8g2* # 期待输出: U8g2 test -f U8g2/U8g2.h echo header OK test -f U8g2/library.properties echo metadata OK这段命令做的事情很简单先看目录里有没有 U8g2 文件夹再检查两个关键文件是否存在。U8g2.h 是库的主头文件编译时 include 的就是它library.properties 是库管理器识别库用的元数据文件IDE 判断“这个目录里装的是什么库、什么版本”全靠它。如果test -f输出的是 OK说明目录结构是对的可以往下走。2.3 确认安装成功的三个检查点目录、属性文件与编译日志装完库真正能确认“它已经准备好”的检查点有三个。第一个是目录结构也就是上面命令已经验证过的libraries 下有一个 U8g2 目录U8g2.h 在目录根部。第二个是 library.properties 里的版本信息用编辑器打开这个文件能看到 nameU8g2、version2.27.6 这样的字段。版本号这段我一般会对一下 zip 包名如果包名是 U8g2-2.27.6.zipproperties 里也应该是 2.27.6出入太大说明文件来源有问题。第三个检查点是编译日志。新建一个空 sketch写两行代码#include U8g2lib.h void setup() {} void loop() {}编译这个最小程序如果 IDE 的编译输出里出现类似Sketch uses ... bytes的字样且没有报错说明头文件已经找到库能被编译。如果这里就报“U8g2lib.h: No such file or directory”优先检查是不是把库解压到了错误目录或者文件夹嵌套层数不对。安装阶段的问题九成出在这两个地方跟代码本身没关系。提示导入 zip 之后最好重启一次 Arduino IDE 再编译。某些版本的 IDE 不会在导入后立刻刷新库索引不重启可能报找不到库容易误判成安装失败。3. 跑通 u8g2 最小 Demo接线、构造函数与两行绘图 API3.1 接线先看引脚Uno、Mega、ESP32 的 SDA/SCL 对照表u8g2 支持很多接口最常见的还是 I2C。I2C 只需要两根数据线接线少而且 u8g2 对 I2C 的支持最顺。先记住一个原则SDA 是数据线SCL 是时钟线不同主控板上的引脚位置完全不同不要照着网上某一张图就接。主控板SDASCL供电Uno / Nano / MiniA4A55VMega 256020215VESP32 DevKitGPIO21GPIO223.3VESP8266 NodeMCUGPIO4D2GPIO5D13.3VOLED 模块的 VCC 和 GND 也要接对很多模块上丝印写的是 VCC 和 GND有的写着 3.3V 和 5V 双电源。屏幕的逻辑电压以模块上的稳压芯片为准如果是纯 3.3V 模块接到 5V 上运气好能亮但长时间跑会发热我建议直接按模块标注接。还有一个细节I2C 总线需要上拉电阻绝大多数成品模块上已经焊好了不需要自己加如果是裸屏接出来的就要在 SDA 和 SCL 上各接一个 4.7kΩ 上拉到 VCC。3.2 最小 sketchbegin() 之后只有 setFont 和 drawStr接线完成先把最简代码跑起来。下面这段就是 u8g2 的“Hello World”所有功能都可以从这里扩展#include U8g2lib.h // 128x64 OLED, SSD1306 控制器, I2C 接口, 全缓冲(F)模式 U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); void setup(void) { u8g2.begin(); // 初始化屏幕, 必须先调用 u8g2.setFont(u8g2_font_ncenB08_tr); // 选择内置英文字体 u8g2.setFontPosTop(); // 字形按顶部对齐绘制 } void loop(void) { u8g2.firstPage(); // 开始第一页 do { u8g2.drawStr(0, 0, Hello u8g2!); // 在 (0,0) 处画字符串 u8g2.drawFrame(0, 12, 128, 20); // 画一个带边框的矩形 } while (u8g2.nextPage()); // 换下一页, 直到全部刷新完成 delay(200); }逻辑拆开看构造函数的第一个参数 U8G2_R0 表示不做旋转第二个参数 U8X8_PIN_NONE 表示不使用 Reset 引脚。setup 里的 begin() 完成屏幕时序初始化和清屏必须在任何绘制之前调用setFont 决定 drawStr 画出来的字长什么样setFontPosTop 是可选的它让文字顶部对齐方便在固定坐标排布 UI。loop 里的 firstPage/nextPage 是 u8g2 最重要的循环模式firstPage 进入绘制流程nextPage 返回 true 就继续画返回 false 表示这一帧画完把缓冲内容一次性交给屏幕。参数上drawStr(0, 0, Hello u8g2!) 的前两个数字是 x、y 坐标左上角为原点向右向下增加。drawFrame(0, 12, 128, 20) 四个参数分别是左上角 x、y、宽度、高度画出来的是一个空心矩形。这行画在文字下方用来确认绘图坐标没有问题。3.3 看懂那个超长构造函数型号、缓冲、接口三个字段拆解u8g2 的构造函数一眼看过去很吓人长这样U8G2_SSD1306_128X64_NONAME_F_HW_I2C。拆开就是五段信息控制器型号SSD1306面板尺寸128X64面板变体NONAME缓冲模式F 或 1接口类型HW_I2C。控制器型号必须和屏幕里的芯片一致SSD1306 和 SH1106 的命令集有差异混用会出现显示异常甚至完全不亮面板尺寸要匹配128X64 就是 128 列、64 行像素。缓冲模式是另一个容易踩坑的地方_F_表示 full buffer也就是全缓冲在 RAM 里保留整帧画面绘制完一次性刷新_1_表示 page buffer也就是页缓冲只留 1/8 屏的缓冲u8g2 自动分页刷新。全缓冲画起来自由但占用 RAM 大AVR 上经常爆内存页缓冲省内存适合 Uno、Nano 这种 RAM 只有 2KB 的板子。接口类型 HW_I2C 表示硬件 I2C走 Wire 库接线只需要 SDA/SCL 两根线SW_I2C 是软件模拟 I2C可以任意指定引脚但速度慢、占用 CPU。选型建议板子有硬件 I2C 就用 HW_I2CUno、Mega、ESP32 都有没必要用软件模拟。构造函数实例化时的参数同样有讲究U8g2(Rotation, Reset_pin)是标准写法第一个参数 U8G2_R0 表示正常方向不旋转如果是竖屏使用可以改成 U8G2_R2 或 U8G2_R1、U8G2_R3对应 180°、90°、270° 旋转。第二个参数 U8X8_PIN_NONE 表示 Reset 脚不接。如果你的屏有 RES 引脚强烈建议接上并在构造函数里填入实际引脚号比如 U8G2(UNO, A3)这样上电时序更可靠能减少很多花屏和初始化失败的问题。4. 中文、字体与显存u8g2 里最容易被卡住的三件事4.1 中文为什么默认画不出来字体匹配的真相u8g2 和 PC 上的图形库有一个根本差异它没有“系统字体”这个概念所有字符都以字模数组的形式固化在 Flash 里drawStr 拿到字符串后按每个字节在字体表里查字形。默认选择的字体只覆盖 ASCII 字符集drawStr(0, 0, 温度) 画出来的只会是乱码或空白因为字体表里根本没有“温”这个字的字形索引。这不是 u8g2 的缺陷而是嵌入式环境的现实中文字形数据量太大不做选择就全塞进去Flash 先扛不住。u8g2 把字体分成很多对象每个对象对应一种字符集覆盖范围。英文字体一个字符只占几十字节而一个 16×16 的汉字字模要 32 字节常用汉字按 3000 个算就是近 100KB8 位 AVR 显然装不下。所以 u8g2 的策略是“按需加载”你画什么语言setFont 就选什么字库。4.2 字体选型与自建小字库unifont 与 bdfconv 的取舍如果你用的是 ESP32 或 STM32 这类 Flash 和 RAM 都充裕的平台u8g2 自带一个覆盖中文的字体对象u8g2_font_unifont_t_chinese。它基于 GNU Unifont覆盖了 CJK 基本区直接 setFont 就能画中文#include U8g2lib.h // 页缓冲模式, 省 RAM; 如果 RAM 充足也可以换 _F_ U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); void setup(void) { u8g2.begin(); u8g2.setFont(u8g2_font_unifont_t_chinese); // 支持 CJK 的字体 u8g2.setFontPosTop(); } void loop(void) { u8g2.firstPage(); do { u8g2.drawStr(0, 0, 温度: 25.5C); // 中英文混排 } while (u8g2.nextPage()); delay(1000); }这里用_1_模式而不是_F_原因是 unifont 字体本身很大但真正吃内存的是全缓冲。_1_模式下 u8g2 只需要一小块页缓冲Flash 里放字体RAM 压力小很多。中英文混排时drawStr 的字符串必须是 UTF-8 编码Arduino IDE 默认保存为 UTF-8所以源码里直接写中文一般没问题。如果你从别的地方复制代码文件编码被转成了 GB2312 或 GBK画出来就会是乱码这个问题在第 5 章还会提到。如果平台是 Uno 或 Nanounifont 就太奢侈了自建小字库是常见做法。u8g2 自带一个字体转换工具思路是把 BDF 格式的字体文件转成 C 语言数组然后再编译进项目。我一般会只保留项目里用到的那几十个汉字做一个 16×16 的点阵字库Flash 占用能压到几 KB 以内。流程大致是先准备好 BDF 字体文件用转换工具生成 .c 文件把这个文件放进 U8g2 的 src 目录引用后 setFont 就能使用新字体。这个路线对不熟悉字体格式的人来说有点门槛但它是 AVR 上跑中文的唯一现实方案。4.3 全缓冲、页缓冲与 u8x8先把 8KB SRAM 算清楚u8g2 在实际使用中经常被问“为什么编译通过烧录后屏幕没反应”或“为什么加了几行代码就编译报错”大部分答案在显存这关。看对比表模式RAM 占用绘制方式适合场景U8G2 全缓冲F约 1KB128×64/8整帧绘制完成后一次刷新图形复杂、RAM 充裕的板子U8G2 页缓冲1约 128 字节1/8 屏分页绘制逐页刷新AVR、RAM 紧张的板子U8X8无缓冲0无绘图缓冲直接写屏幕只能文本纯文字显示、极省 RAMUno 的 SRAM 只有 2KB光 u8g2 全缓冲吃掉 1KB再加上 Wire 库、字符串缓冲区、程序里的全局变量堆栈很容易爆。所以我的默认选择是AVR 平台一律用_1_或 u8x8ESP32 才放开使用_F_。u8x8 是 u8g2 的同门双胞胎不支持图形绘制只能显示固定字体的文本但零缓冲RAM 占用几乎可以忽略。如果你只需要显示传感器数值、状态信息u8x8 是性价比最高的方案。页缓冲模式对绘制逻辑有一个硬约束每次 firstPage/nextPage 循环里重新画整帧内容不能依赖“上一帧残留”。因为页缓冲只有一部分内容在内存里u8g2 会分多次刷新屏幕你无法准确知道哪一页正在被发送。所以绘制函数里所有状态都要在循环体内完成不要在循环外画一部分、循环内画另一部分否则会出现缺行、错位。5. u8g2 踩坑手册黑屏、乱码、花屏与编译爆内存的 5 个排查5.1 黑屏无反应先查 I2C 地址和 Reset 引脚现象是代码编译烧录都正常但屏幕一点反应都没有背光都不亮。这个坑排在第一位因为它的排查顺序很固定先查电源再查 I2C 地址最后查 Reset。很多模块的 I2C 地址是 0x3C但也有 0x3D 的出厂设置不同构造宏默认按 0x3C 工作如果屏是 0x3D就必须换构造版本。先跑一个 I2C 扫描程序确认地址#include Wire.h void setup() { Wire.begin(); Serial.begin(9600); for (byte addr 8; addr 120; addr) { Wire.beginTransmission(addr); if (Wire.endTransmission() 0) { Serial.print(found: 0x); Serial.println(addr, HEX); } } } void loop() {}这个程序扫描总线上所有可能的 7 位地址把有设备响应的地址打印出来。如果串口里一个地址都没有说明接线或电源有问题如果打印出来的地址是 0x3D就把构造函数换成 U8G2_SSD1306_128X64_NONAME_F_HW_I2C 的 3D 版本产品页通常标注。Reset 引脚也很关键有些模块的 RES 悬空上电时序不稳表现就是偶尔亮偶尔不亮。解决方法是把 RES 接到主控的一个数字引脚上比如 D9并在构造函数里传入引脚号比如 U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, 9)。5.2 屏亮了但全是方块乱码编码与字体不匹配现象是屏幕能显示但中文全是乱码英文和数字正常。这基本不是屏的问题而是编码问题源码文件不是 UTF-8或字体对象不含中文字形。Arduino IDE 默认保存 UTF-8但如果代码是从别的编辑器复制来的或者 Arduino IDE 的语言设置被改过字符串的编码就可能变成 GB2312。u8g2 的 unifont 字库是按 UTF-8 编码匹配字符的GB2312 编码喂进去查不到对应字形就会乱码。解决方法是统一编码把源码文件另存为 UTF-8 格式或者重新在 IDE 里新建文件、粘贴代码。另一个前提是字体必须选对如果 setFont 用的是 u8g2_font_ncenB08_tr 这种英文字体就算编码是 UTF-8照样画不出中文必须换成 unifont_t_chinese 或自建中文字库。还有一个隐秘细节有的中文字体对象只覆盖了部分字符集比如只做了 GB2312 一级汉字你用的某个生僻字不在里面就会出现“大部分正常、个别字乱码”的诡异现象查字体覆盖范围才能定位。5.3 刷新时花屏带残影供电和线长的问题现象是屏幕能亮但刷新时出现噪点、残影或者画面越刷越花放一会儿又正常。常见原因有两个一是供电不足OLED 模块动态刷新时电流波动如果和主控共用一根细供电线电压被拉低屏幕就会发花二是 I2C 线太长超过了 20cm信号反射和干扰导致数据传输错误。这问题玄学味道很重因为有时候换一根线就好有时候把线缩短就好有时候加个电容就好。解决思路按成本从低到高试先给模块的 VCC 和 GND 之间加一个 100uF 电解电容位置尽量靠近屏幕电源脚然后把 I2C 导线缩短到 20cm 以内并让 SDA 和 SCL 尽量平行走线如果还花把 I2C 时钟频率降下来。u8g2 在一些板子上可以用 setBusClock 调整总线频率降到 100kHz 后稳定性会明显改善。最后才是考虑独立供电不跟主控的电源稳压器抢电流。5.4 编译报“内存不足”一个字符数组把 AVR 打死现象是代码写了一半编译时 IDE 直接报“Not enough memory”或者“内存不足”通常还伴随一堆变量地址溢出的提示。u8g2 在全缓冲模式下默认分配约 1KB 的静态 RAM 给显存Uno 总共 2KB SRAM剩下的空间给全局变量、局部变量和堆栈。如果程序里再放几个 String、几个大的 char 数组编译器就会明确告诉你装不下。解决方法是换模式全缓冲_F_改页缓冲_1_显存占用从 1KB 降到 128 字节绝大多数 AVR 项目都能活下来。如果还缺就把所有只读数据挪进 PROGMEM比如中文字库、日志字符串、配置表。另一个常见诱因是在循环里用 String 拼接String 在 AVR 上会制造大量内存碎片换成 char 数组能省很多。记住一个检查习惯全局变量总量加显存占用不要超过板子 SRAM 的一半剩下的一半留给堆栈。5.5 换了一块同尺寸屏反而不亮控制器型号和构造宏要同步换现象是原来 128×64 的 SSD1306 屏一切正常换成另一块同样是 128×64 的屏后黑屏或者显示错位。很多卖家标注“SSD1306 兼容”但实际用的可能是 SH1106。SSD1306 和 SH1106 虽然都是 128×64 单色 OLED 驱动但内部显存结构和初始化命令有差异构造宏用错就会出现白屏、下半屏空白、画面偏移。u8g2 对两种控制器都有专门构造SSD1306 用 U8G2_SSD1306_128X64_NONAME_F_HW_I2CSH1106 用 U8G2_SH1106_128X64_NONAME_F_HW_I2C换屏时要同步换这一整条宏只改尺寸或只改控制器都会出问题。排查方法很简单看屏幕主控芯片丝印或者直接看卖家给的型号描述然后对照 u8g2 的构造列表选择。如果没有丝印可以试几个常见的构造宏编译烧录后能正常显示的那个就是对的。还有一个坑是“兼容屏”的初始化地址不同有些兼容屏默认 I2C 地址不是标准地址用第 5.1 节的扫描程序确认一次最保险。6. 从 Demo 到界面页面函数、局部刷新和 u8g2 的显存瘦身习惯Demo 跑通之后下一步是把屏幕当成一个真正的界面来用。最值得掌握的技巧是“页面函数封装”把每个界面的绘制逻辑收进一个函数配合 switch 实现页面切换。这样代码结构清楚也方便后续加动画状态。uint8_t currentPage 0; void drawStatusBar() { u8g2.drawStr(0, 0, TEMP: 25.5C); u8g2.drawHLine(0, 10, 128); } void drawMainPage() { drawStatusBar(); u8g2.drawFrame(0, 16, 128, 32); u8g2.drawStr(4, 20, DATA OK); } void drawPage(uint8_t page) { switch (page) { case 0: drawMainPage(); break; case 1: drawStatusBar(); break; } } void loop() { u8g2.firstPage(); do { drawPage(currentPage); } while (u8g2.nextPage()); delay(50); }局部刷新是另一个值得用起来的功能。u8g2 整帧刷新在页缓冲模式下已经很省内存但如果你只需要更新一个数值区域可以用 setClipWindow 限定绘制范围减少数据量u8g2.setClipWindow(40, 32, 88, 56); // 只更新 40,32 到 88,56 区域 u8g2.drawBox(40, 32, 48, 24); // 先清掉旧内容 u8g2.drawStr(42, 40, new value); u8g2.undoClipWindow(); // 恢复全屏裁剪这套代码的思路是先把要更新的区域用 drawBox 填成背景色再画新内容最后恢复裁剪。这样避免全屏刷新时的闪烁感也更省时间。u8g2 的 API 设计让我最舒服的一点是所有绘制调用都往缓冲里画并不直接上屏所以你怎么组合都不会有撕裂。最后分享一个我自己的固定习惯。最初做带屏项目时为了省事直接全缓冲加 unifont 中文字库Uno 编译当场爆内存后来就养成了“先算显存、再选缓冲模式”的习惯拿到一块新屏先查控制器型号和 I2C 地址再决定_F_还是_1_最后才写绘制代码。这三步能省掉后面八成的排查时间。u8g2 本身是个成熟可靠的库出错多发生在使用方式上把这几个关键参数守住屏幕基本不会给你制造惊喜。希望帮到你。本文还有配套的精品资源点击获取