news 2026/10/16 14:59:53

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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,分辨率 128x64:

U8G2_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 是控制 IC,128X64 是像素,NONAME 表示不区分品牌型号的通用版,F 表示全缓冲(Full Buffer),HW_I2C 表示使用硬件 I2C 控制器而不是代码模拟。最后一个参数SDA对应的是硬件 I2C 的数据脚,你在构造参数里写 SCL/SDA,Arduino 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 用芯片内置的外设,传输快且不占 CPU;SW 用引脚翻转模拟时序,适合引脚被占用的情况,代价是刷新速度明显下降。传 128x64 这种尺寸,SW 模式跑动画会看到可感知的闪烁。

3.2 让屏幕亮起来并画出一行字符

装好库、选对构造函数之后,下面是最小 Demo,这段代码只要烧进去就能在 OLED 上显示一行 "Hello u8g2":

#include <Wire.h> #include <U8g2lib.h> // 0.96 寸 SSD1306,I2C 地址默认 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()在绘制完当前页后返回是否还有下一页。如果没有循环包裹,只有第一页的内容会被绘制,上下边缘会显示残缺。选择哪种模式取决于芯片 RAM:ATmega328P 有 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 会根据初始化序列进入错误的扫描模式。第二,中文字体乱码则是编码问题,源码文件保存成了 GB2312,drawUTF8()拿到的是 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 时少走我走过的弯路,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 14:59:45

Docker部署Hadoop持久化:命名卷与绑定挂载实战指南

开篇&#xff1a;一个把集群“重启没了”的典型案例某天&#xff0c;我照常在搭一套基于Docker的Hadoop集群环境&#xff0c;用于内部跑一些离线统计任务。因为一切配置都已经编排好了&#xff0c;我顺手执行了docker-compose down清理环境&#xff0c;打算重新构建一次做个完整…

作者头像 李华
网站建设 2026/10/16 14:57:37

用C++把eVTOL六自由度动力学仿真跑通:建模、积分与控制调试

简介&#xff1a;eVTOL_Simulation 是一份基于 C 编写的 eVTOL 飞行器运营调度模拟程序&#xff0c;面向对离散事件仿真、面向对象编程感兴趣的开发者。程序内置 5 家公司的飞机参数&#xff0c;涵盖巡航速度、电池容量、充电时间、能耗及故障概率&#xff0c;模拟 20 架飞机同…

作者头像 李华
网站建设 2026/10/16 14:56:01

MATLAB Simulink定子匝间短路仿真:建模、特征提取与排坑指南

搞电机故障诊断这些年&#xff0c;“定子匝间短路”一直是我最不愿意看到却又躲不开的故障。感应电机&#xff0c;也就是常说的异步电机&#xff0c;在工业现场跑得好好的&#xff0c;突然某相电流往上蹿、噪音变大、机身发烫&#xff0c;十有八九就是它在作怪。借着MATLAB Sim…

作者头像 李华
网站建设 2026/10/16 14:56:00

DeFlock内容管理进阶:用Directus扩展+GPT-4o-mini自动提取新闻数据

【免费下载链接】deflock Crowdsourced tool for locating and reporting ALPRs 项目地址&#xff1a; https://gitcode.com/gh_mirrors/de/deflock 点击查看 免费下载 DeFlock 是一个开源的群众参与式工具&#xff0c;用于定位和举报 ALPR&#xff08;自动车牌识别&#xff0…

作者头像 李华
网站建设 2026/10/16 14:53:14

Python入门教程之Python的安装下载配置

前言 对刚开始学 Python 的人来说&#xff0c;第一道坎往往不是语法&#xff0c;而是「怎么把 Python 装到自己的电脑上&#xff0c;并让它能在命令行里跑起来」。这一步会牵扯几个概念&#xff1a;下载哪个版本、安装时勾什么选项、命令行里为什么敲 python 没反应、为什么有时…

作者头像 李华
网站建设 2026/10/16 14:52:25

Kotlin空安全全面解析:从类型系统到工程实践

直接说个我观察到的现象&#xff1a;很多人是从Java切到Kotlin的&#xff0c;刚上手时对?和!!这套符号特别不适应&#xff0c;甚至有人觉得“Kotlin的空安全也就是编译期帮你加几个空判断”。这个理解不能说错&#xff0c;但真的把它用明白、用在项目里不踩坑&#xff0c;你会…

作者头像 李华