u8g2 OLED图形库完全指南:安装、选型、中文显示与排错

发布时间:2026/10/16 15:00:07
u8g2 OLED图形库完全指南:安装、选型、中文显示与排错 简介这是面向 Arduino IDE 的 U8g2 图形库 2.27.6 完整压缩包专为驱动 I2C、SPI 及并行接口的 OLED、LCD 和电子纸屏幕而设计适合在 Arduino、ESP8266、ESP32 平台上快速加入图形界面或信息展示。包内共 159 个文件约 10.03MB以 C 源文件和头文件为核心包含屏幕驱动、字库与绘图函数实现另有 53 个 .ino 示例程序、说明文档、许可协议及库配置文件便于安装后直接参照调用。该库支持超过 200 种显示控制器提供点、线、矩形、圆、位图和多种字体绘制能力并具备分页缓冲、旋转镜像、滚动区等功能有助于降低内存占用解压后即可被 Arduino 开发环境正常调用配套示例能帮助初学者快速点亮屏幕并迁移到实际项目。目前已有 3296 人学习下载对希望为硬件作品增加显示输出或交互界面的开发者来说是一份可直接使用的依赖包。1. 先回答一个问题u8g2 到底是来干什么的很多第一次见到 U8g2-2.27.6.zip 的人会以为这是一颗屏幕驱动芯片的固件包或者是某个 OLED 模块的出厂 SDK。其实它是 Arduino IDE 里一个纯 C 编写的单色图形库覆盖 SSD1306、SH1106、ST7920 这类最常见的 OLED/LCD 控制 IC帮你在不关心底层显存协议的情况下直接把画点、画线、画中文、显示图片这些操作变成一次函数调用。u8g2 是 u8g 的分支重写版从 2.27.x 这一代开始API 接口已经稳定很多人打包分享的 U8g2-2.27.6.zip 就是离线安装用的源码压缩包。我之所以开头就讲结论这个库真正劝退新手的不是绘图 API 有多复杂而是第一步就出错——构造函数选错、I2C 地址对不上、下载进去屏幕不亮然后误以为是库坏了。这篇文章会把安装、选型、跑通、调中文字体、避坑这几段完整走一遍适合刚拿到 OLED 模块、想快点看到画面的初学者也适合之前用过 u8g 老库想迁移的老手。你会看到每个坑都有现象、原因和解决方法。2. 安装这个库ZIP 手动装和库管理器装选哪条路2.1 为什么推荐 ZIP 手动安装而不是只靠库管理器搜索在 Arduino IDE 里打开库管理器输入 u8g2 通常能搜到一个由作者维护的稳定版本。那为什么还要保留 U8g2-2.27.6.zip 这种离线 ZIP 包两个理由第一有些开发环境没有外网或者公司内网做了库索引屏蔽只有 ZIP 包能直接塞进去第二别人发给你的项目如果明确锁定了 2.27.6而你库管理器里装的是更新的版本某些函数的默认参数和字体定义会有细微差别结果就是别人编译通过你编译报错。ZIP 手动装最大的价值是版本可控。具体路径是菜单栏选择 项目 - 加载库 - 添加 .ZIP 库然后在文件对话框里选中 U8g2-2.27.6.zip。Arduino IDE 会自动解压到库目录。这里有个细节值得说明IDE 不会做任何校验它只看 ZIP 根目录里是否有一个 src 文件夹或者 .h 头文件。如果压缩包外层多套了一层同名文件夹IDE 也能识别但解压出来的目录名会变成 U8g2-2.27.6在项目里依然用#include U8g2lib.h引入不需要管文件夹实际叫什么。2.2 安装完成后怎么确认这个库真的被用上了很多人的库装完之后编译时还是用了旧版本因为一个项目的库搜索路径优先级是项目 src 子目录 用户目录的 libraries IDE 自带的 libraries。常见做法是打开 Arduino IDE 的 文件 - 首选项找到项目文件夹里的 libraries 目录手动把 ZIP 解压进去路径形如libraries/U8g2lib。这样做的好处是全局默认就是 2.27.6不会哪天 IDE 自动更新又给顶掉。确认生效的最快方式不是看库管理器列表而是编译任意一个包含U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(...)的小程序然后观察控制台输出里编译内核打印的路径如果库文件来自你手动放的目录说明用的就是它。#include Arduino.h #include U8g2lib.h U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset*/ U8X8_PIN_NONE, /* clock*/ SCL, /* data*/ SDA); void setup() { u8g2.begin(); } void loop() { // 留空这里只验证库能不能编过 }这段代码意义不在功能而是最快暴露问题如果编译报错fatal error: U8g2lib.h: No such file or directory说明库没装进搜索路径优先检查是不是把整个 ZIP 解压后的外层文件夹又套了一层。如果编译通过但下载后屏幕没反应那不是库的问题看后面构造函数和接线。2.3 关于离线包版本2.27.6 和更新版本的区别从工程实践看2.27.x 这一代最值得注意的变化是构造函数家族补齐了对于 128x32、128x64、256x64 尺寸的几组专用模板这直接决定了你能不能直接用u8g2.drawStr()而不手动处理地址分页。往后的小版本更新大多是增加新板型和修正个别字体表的错误不涉及大的 API 破坏。对稳定项目而言一个建议固件代码仓库里直接放一个 lib 目录里面保存那个固定的 ZIP。团队其他成员从 git 拉下来用脚本统一解压到本地 libraries 目录。这样就不会出现A 同学用库管理器装的是新版B 同学装的是旧版两个人代码一样但显示效果不同的玄学问题。3. 跑通最小 Demo构造函数选不对后面全是黑匣子3.1 构造函数里的每个字段到底在干什么我把 u8g2 的构造函数拆开讲这一部分比画图更值得花时间。以你手里常见的 0.96 寸 OLED 为例驱动 IC 是 SSD1306分辨率 128x64U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2( U8G2_R0, // 旋转方向R0 表示不旋转 U8X8_PIN_NONE, // reset 引脚不用就填 U8X8_PIN_NONE SCL, // I2C 时钟引脚 SDA // I2C 数据引脚 );这里有几个容易混的点。U8G2_SSD1306_128X64_NONAME_F_HW_I2C是一长串模板名拆开看SSD1306 是控制 IC128X64 是像素NONAME 表示不区分品牌型号的通用版F 表示全缓冲Full BufferHW_I2C 表示使用硬件 I2C 控制器而不是代码模拟。最后一个参数SDA对应的是硬件 I2C 的数据脚你在构造参数里写 SCL/SDAArduino Uno 上其实是 A5 和 A4这是 IDE 预定义的宏不需要自己写引脚数字。软件 I2C 版本的构造函数长这样U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2( U8G2_R0, U8X8_PIN_NONE, /* clock*/ 4, // 任选数字引脚 /* data*/ 5 );区别只在 HW 和 SW 两个单词HW 用芯片内置的外设传输快且不占 CPUSW 用引脚翻转模拟时序适合引脚被占用的情况代价是刷新速度明显下降。传 128x64 这种尺寸SW 模式跑动画会看到可感知的闪烁。3.2 让屏幕亮起来并画出一行字符装好库、选对构造函数之后下面是最小 Demo这段代码只要烧进去就能在 OLED 上显示一行 Hello u8g2#include Wire.h #include U8g2lib.h // 0.96 寸 SSD1306I2C 地址默认 0x3C // 如果屏幕模块把 SA0 引脚拉高地址会变成 0x3D这时需要改 U8G2_R0 下面的构造参数。 U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, SCL, SDA); void setup() { u8g2.begin(); // 初始化屏幕会执行内部 reset 和配置序列 u8g2.clear(); // 清空显存 u8g2.setFont(u8g2_font_ncenB08_tr); // 选择一个内置字体 u8g2.drawStr(4, 20, Hello u8g2!); // 在坐标 (4,20) 绘制字符串 u8g2.sendBuffer(); // 把内存里的内容一次性推到屏幕 } void loop() { // 空循环 }注意u8g2.drawStr()和u8g2.sendBuffer()之间没有调用firstPage()因为 F 后缀的全缓冲模式下显存是一个完整的 1024 字节数组你可以像操作普通缓冲区一样随便画最后一次性送显。如果你用的是非 F 后缀的构造函数比如U8G2_SSD1306_128X64_NONAME_1_HW_I2C就必须用firstPage() nextPage()这种分页循环直接drawStr()后不写 nextPage 序列屏幕会空白。这是个非常经典的翻车点。3.3 从全缓冲改成页缓冲为什么代码结构要跟着变页缓冲模式构造函数里那个数字是 1 或 2只用一块很小的 RAM 暂存一页数据画布被分成多个条带CPU 每次只能访问当前页。典型写法是// 注意构造函数里的 1 U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, SCL, SDA); void loop() { u8g2.firstPage(); do { u8g2.setFont(u8g2_font_ncenB08_tr); u8g2.drawStr(4, 30, Page Buffer Demo); u8g2.drawLine(0, 40, 127, 40); } while (u8g2.nextPage()); }这里firstPage()把页指针归零nextPage()在绘制完当前页后返回是否还有下一页。如果没有循环包裹只有第一页的内容会被绘制上下边缘会显示残缺。选择哪种模式取决于芯片 RAMATmega328P 有 2KB RAM用 F 模式占 1KB还能接受但如果用 ATtiny85 这类只有 512 字节 RAM 的芯片F 模式直接编译不过必须用1_HW_I2C这种页缓冲模式。原则是RAM 大于显存两倍以上才考虑 F否则老老实实用页缓冲。4. 显示进阶中文、图标和字体的选择4.1 u8g2 的字体体系不是你想显示什么字就能显示什么字u8g2 内部自带的字体是查表字符每个字体文件覆盖的字符集不同。默认大部分字体只覆盖 ASCII 范围直接用drawStr()传中文 UTF-8 字符串屏幕上只会出现一排方块。这不是库坏了而是字体表里根本没有中文字形。想让屏幕显示中文有几条路线可选按工程可靠度排第一条是选用自带中文的字体例如u8g2_font_wqy12_t_gb2312、u8g2_font_wqy14_t_gb2312它是文泉驿点阵字体的移植版GB2312 编码覆盖常用简体字。使用时先u8g2.setFont(u8g2_font_wqy14_t_gb2312)再调用u8g2.drawUTF8(0, 20, 你好)。注意函数名是drawUTF8不是drawStr因为内部要解析多字节编码用的是 UTF-8 输入。第二条是自造点阵字库用取模软件批量生成 C 数组。这种方式适合字少的情况比如菜单项只有十几个词把每个字的点阵数据放进 PROGMEM绘制时手动drawXBM()。优点是不依赖外部字体文件的长度限制缺点是排版麻烦两个字之间要自己算间距。代码里常见做法是u8g2.setFont(u8g2_font_wqy12_t_gb2312); // 中文字体12px 点阵 u8g2.drawUTF8(0, 20, 电量: 85%);参数说明u8g2_font_wqy12_t_gb2312这个字体名的尾缀t表示透明模式gb2312表示字符编码集drawUTF8接受的是 UTF-8 字符串而不是 GB2312 字符串写入源代码时编辑器要保存为 UTF-8 无 BOM 格式否则中文字符串在编译时会映射成乱码字节屏幕显示的仍然可能不对。4.2 图标和图片的显示思路XBM 格式比 Base64 更直接很多项目需要显示电池图标、WiFi 信号图标而不是整张照片。u8g2 官方推荐的流程是在电脑上用图片转 C 数组工具把一张 16x16 或 32x32 的单色 BMP 转成 XBM 格式然后用drawXBM()绘制。128x64 只能显示单色不要试图传 RGB 图像直接色调到 1 bit 深度不然转出来的数组体积巨大而且显示效果非常脏。// 一个 16x16 的箭头图标用取模工具生成 static const unsigned char arrow_right_bits[] U8X8_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 }; void drawIcon() { u8g2.drawXBM(64, 30, 16, 16, arrow_right_bits); // x, y, 宽, 高, 数据 }逻辑说明drawXBM()的坐标是图标左上角所有位数据按行排列每行字节数向上取整U8X8_PROGMEM宏在 AVR 上对应 PROGMEM在 ESP32 这类平台则是普通 const。如果绘制出来的图标是左右镜像的说明取模时设了逐列式扫描把扫描方式改成逐行式重新导出即可。4.3 中文字体太多放不进 Flash用子集化或者分屏加载一个常见项目场景是显示几十个汉字的状态菜单但如果直接用了完整 GB2312 字库16px 字体的 Flash 占用会到几十 KB。ATmega328P 只有 32KB Flash很可能编译不过。此时有两种取舍一个是选 12px 的小字号字库体积明显下降另一个是只提取自己用到的几十个汉字做成自定义小字库。后者做法是用取模软件导出单个汉字的点阵数组然后定义一张查找表struct GlyphItem { const char* utf8; // 比如 电 const uint8_t* bits; // 点阵数据 }; const GlyphItem myGlyphs[] { {电, glyph_dian_bits}, {量, glyph_liang_bits}, }; void drawChineseAt(u8g2_t* u8g2, uint8_t x, uint8_t y, const char* text) { // 自定义查询并调用 drawXBM 逐字绘制注意每次只画一个字 }这种方案的灵活性在于用户能精确控制 Flash 占用代价是所有自绘字形都不支持drawUTF8()的自动换行和混合排版。如果你只是做个固定界面这样反而可控。5. 常见问题与排查屏幕不亮、花屏、乱码的根因5.1 屏幕完全不亮测量 SDA/SCL 有波形但无显示现象I2C 扫描能识别到 0x3C 设备程序运行正常但屏幕背光不亮或者只有微弱亮光。原因绝大多数 0.96 寸 OLED 模块的 VCC 和 GND 接反不会烧毁但模块的稳压电路处于非正常工作状态芯片不上电I2C 引脚只是被单片机拉高模块内部没有响应。少数模块还需要确认 I2C 地址跳线模块背面如果有 SA0 焊盘默认是 0x3C你把它短路成高电平后地址变成 0x3D代码里必须同步改。解决先断开所有接线用万用表量 VCC 与 GND 之间的电压正常应在 3.0V 到 3.6V 之间模块带稳压则兼容 5V。确认供电没问题后跑一个 I2C 地址扫描程序打印出实际地址再把构造函数里的 U8G2_R0 不变把库内部默认地址改到扫描到的地址。千万不要直接靠运气反复烧录。5.2 能显示但全是乱码或错位方块现象屏幕亮能看见字符轮廓但内容完全不对中文位置出现一排实心方块。原因很典型的分两步看。第一如果画全 ASCII 字符都花屏多半是构造函数里的屏幕尺寸和实际分辨率不匹配比如 128x32 的屏幕用了 128x64 的模板SSD1306 驱动 IC 会根据初始化序列进入错误的扫描模式。第二中文字体乱码则是编码问题源码文件保存成了 GB2312drawUTF8()拿到的是 GB2312 字节流它会按 UTF-8 规则去解析自然解析出乱序。解决把屏幕尺寸模板改成实际分辨率。如果模块是 0.96 寸 128x64就用 128X64如果是 1.3 寸 128x64 的 SH1106构造函数要换成U8G2_SH1106_128X64_NONAME_F_HW_I2C因为 SH1106 和 SSD1306 的显存寻址方式不同强行用 SSD1306 模板初始化 SH1106虽然也能亮但边缘会出现一行竖向的乱点。文本文件统一转存为 UTF-8 无 BOM 格式后再编译。5.3 使用页缓冲模式时编译器提示某个函数未声明现象代码里调用了u8g2.drawFrame()或者u8g2.drawRFrame()编译报错not declared。同样代码换成 F 模式的构造函数又正常。原因u8g2 库对不同缓冲模式做了编译期裁剪页缓冲下只能使用绘制像素、水平线、垂直线、字符串这类函数需要随机访问整个显存的函数比如 frame、box、三角形填充在页缓冲模式下没有声明。这是库作者有意的取舍因为这些函数在分页模式下画出来的内容会被下一页覆盖。解决把需要复杂图形的那部分代码放到firstPage()循环内部并确认函数本身在当前构造函数模式下有声明。一个更省事的方案换用 F 模式构造函数用 RAM 换功能。如果 RAM 不允许就把复杂图形拆分到单页内绘制不要跨页依赖。5.4 程序下载正常但复位后偶尔白屏现象烧录完成后第一次运行正常按一下复位键屏幕就变白再按几次有时恢复有时不能。原因这是硬件复位时序问题。OLED 模块的 reset 引脚悬空时片内上电复位和单片机的 I2C 初始化存在竞争条件。SSD1306 需要至少在 VCC 稳定后等待若干毫秒才能接受初始化命令单片机跑得比屏幕快。解决不要使用U8X8_PIN_NONE把屏幕模块的 RES 引脚接到单片机的数字引脚例如 D9然后在构造函数里填上这个引脚号#define OLED_RESET 9 U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, OLED_RESET, SCL, SDA);同时在u8g2.begin()之前加一段delay(100)让屏内稳压彻底稳定下来。如果模块根本不引出 RES 引脚就保持U8X8_PIN_NONE但在begin()之后立刻执行一次u8g2.clear()加sendBuffer()人为强制一次同步。6. 用终端排错法代替反复烧录先确认屏幕地址再谈显示效果我发现很多人在 OLED 调不通时第一反应是改代码参数再烧录一次几分钟可以接受半小时就太浪费时间了。我自己的习惯是先在终端里做一次 I2C 扫描把结果显示在串口监视器上确认物理层通了再看上层逻辑。#include Wire.h void setup() { Serial.begin(115200); Wire.begin(); Serial.println(I2C Scanner); } void loop() { byte error, address; int nDevices 0; for (address 1; address 127; address) { Wire.beginTransmission(address); error Wire.endTransmission(); if (error 0) { Serial.print(Found address 0x); if (address 0x10) Serial.print(0); Serial.println(address, HEX); nDevices; } else if (error 4) { Serial.print(Error at 0x); if (address 0x10) Serial.print(0); Serial.println(address, HEX); } } if (nDevices 0) Serial.println(No device found); delay(2000); }如果扫描结果里找不到 0x3C也别急着怀疑屏幕。先检查上拉电阻——很多模块内部已经有 10K 上拉但有些精简版模块没有此时需要在 SDA 和 SCL 上分别外接 4.7K 电阻到 VCC。还有一种情况是接线氧化导致的接触电阻过大用杜邦线时摇晃一下屏幕接口看扫描结果是否间歇性出现地址。只有在扫描稳定看到地址后再去调构造函数里的 I2C 地址和旋转方向参数。确认设备存在后我通常还会做一个坐标系验证先把屏幕四周边框画出来设置一个明确的坐标原点位置再显示一个纯字符串。这能同时验证旋转方向和字体是否正常。这一套做完再复杂的问题也只剩显存操作层面的逻辑错误而不是硬件玄学。如果屏幕能亮、能显示文字但刷新速度明显慢这里还有一条调试习惯供参考把sendBuffer()挪到loop()的末尾并且只在数据变化时才调用不要每个循环都清屏重发。很多卡顿和闪烁问题不是库的问题而是每帧都做了多余的全屏刷新。把绘制逻辑改成脏矩形更新也就是只重画变化区域OLED 的响应速度会明显改善。这也是我在多个项目里反复用到的经验希望你用 u8g2 时少走我走过的弯路希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询