OLED

OLED(Organic Light-Emitting Diode,有机发光二极管)屏幕的每个像素自行发光,不需要背光。
嵌入式项目中常用的是 0.96~1.5 英寸的小尺寸单色模块:接线简单(I2C 接口仅需两根信号线),功耗低、对比度高,适合显示状态信息,也是入门常用的屏幕。
本章介绍 OLED 的成像原理与常见驱动 IC,并重点讲解单色屏事实上的标准图形库 u8g2。
1. 显示原理
OLED 的每个像素是一颗微小的有机发光二极管,通电即发光,亮度随电流变化。这与 LCD 的“背光 + 光阀”结构有本质区别:
- 不发光的像素为纯黑。LCD 显示黑色时背光仍在工作、存在漏光,OLED 的黑色像素直接不通电,因此对比度高,黑色纯粹。
- 功耗取决于画面内容。点亮的像素越多、越亮,功耗越高;深色画面非常省电。
按驱动方式,OLED 分为两类:
- PMOLED(Passive-Matrix OLED,被动矩阵有机发光二极管):像素按行列共用电极,任一时刻只点亮一行,靠逐行快速扫描配合视觉暂留形成完整画面。行数越多,每行分到的点亮时间越短,要维持亮度就得拉高瞬时电流,因此尺寸和分辨率受限,产品集中在 3 英寸以下的小屏。结构简单、成本低,本章讨论的常见 OLED 模块均属此类。
- AMOLED(Active-Matrix OLED,有源矩阵有机发光二极管):每个像素配有独立的 TFT(Thin Film Transistor,薄膜晶体管)开关管,可维持发光状态,支持大尺寸、高分辨率和全彩显示,详见 AMOLED 章节。
PMOLED 是小尺寸屏幕的默认方案,厂商通常不会专门标注,因此产品页如果没有特别标注 AMOLED,一般即为 PMOLED。
2. 特性与局限
优势:
- 对比度高,黑色纯粹,小字清晰。
- 自发光无背光,可视角接近全向。
- 响应快,无拖影。
- 显示深色内容时功耗低(黑色像素不耗电)。
- 模块接口简单,I2C 两根线即可驱动,适合引脚有限的场合。
局限:
- 尺寸小,产品范围约 0.42~2.42 英寸,主流为 0.96~1.5 英寸;分辨率低,多为 128×64 上下。
- 以单色(白、蓝、黄)为主,部分为双色或灰度屏,无真彩。
- 有烧屏风险:有机材料会随累计点亮时间衰减,长期显示固定内容会留下痕迹。长时间运行的设备应避免完全静止的画面(如定期移动内容、降低亮度或熄屏)。
- 阳光直射下可读性一般。
3. 常见驱动 IC 与接口
| 驱动 IC | 分辨率 | 特点 | 常见屏幕 |
|---|---|---|---|
| SSD1306 | 128×64 | 最常见、资料最多 | 0.91 / 0.96 英寸 |
| SH1106 | 132×64 | 与 SSD1306 类似,但显存为 132 列(宽 4 列),误用 SSD1306 构造器会导致画面偏移 | 1.3 英寸 |
| SSD1309 | 128×64 | SSD1306 的大屏版本 | 2.42 英寸 |
| SSD1327 | 128×128 | 16 级灰度 | 1.5 英寸 |
接口方面,上述驱动 IC 均同时支持 I2C 和 SPI,模块实际引出哪种接口取决于产品设计。部分模块(如微雪 OLED 模块)可通过板上的电阻焊位切换,出厂默认接口请见对应产品页;也有不少模块在出厂时固定为其中一种,不可更改。两种接口的取舍如下:
- I2C:只需 SDA、SCL 两根线,还可与其他 I2C 器件共享总线,是小屏最常用的接法;缺点是带宽低,整屏刷新率有限。
- SPI:引脚多(DIN、CLK、CS、DC、RST),但速度显著更快,适合需要流畅动画的场合。
SSD1306、SH1106 等 1bpp(1 bit per pixel,每像素 1 位,即单色)驱动 IC 的显存按页寻址组织:每页为 8 行像素,一个字节对应一列中垂直排列的 8 个点。SSD1327 这类灰度屏则不同,显存为 4bpp(4 bits per pixel,每像素 4 位),一个字节存放相邻两个像素的灰度值。无论哪种布局,直接操作显存都需要按对应格式组织字节,较为繁琐,这也是使用 u8g2 等图形库的原因:库在内存中维护常规布局的缓冲区,绘图后统一转换并发送,无需关心显存细节。
4. Arduino + u8g2 驱动示例
u8g2 是开源社区维护的单色屏图形库,支持 SSD1306、SH1106、SSD1327 等几乎所有常见单色驱动 IC,内置数百套字体,是 Arduino 下驱动 OLED 的首选。RLCD 章节 中微雪官方示例同样基于 u8g2。
本节以 微雪 1.5 寸 OLED 模块 为例,该模块驱动 IC 为 SSD1327,分辨率 128×128,16 级灰度,通过 I2C 接线,可搭配任意 ESP32 开发板使用。如果使用其他 OLED 模块,只需替换引脚定义和对应的构造器即可。
4.1 构造器与缓冲模式
u8g2 通过构造器类名选择驱动 IC、屏幕型号、缓冲模式和接口,命名规则为:
U8G2_<驱动IC>_<屏幕型号>_<缓冲模式>_<接口>
例如 U8G2_SSD1327_WS_128X128_F_HW_I2C 表示:SSD1327 驱动 IC、微雪(WS)128×128 屏、全缓冲(F)、硬件 I2C。构造器必须与屏幕完全对应,这是 u8g2 使用中最关键的一步,完整列表见 u8g2 官方文档。
缓冲模式决定内存占用与绘图方式:
| 模式 | 代号 | 内存占用(128×64 为例) | 绘图方式 |
|---|---|---|---|
| 全缓冲 | F | 整帧 1 KB | 任意绘制,sendBuffer() 一次刷新,简单直观 |
| 页缓冲 | 1 / 2 | 128 / 256 字节 | 在 firstPage()/nextPage() 循环中重复绘制,省内存但代码受约束 |
页缓冲是为内存极小的单片机(如 AVR)设计的;ESP32 内存充足,使用全缓冲(F)即可。
u8g2 本质是 1bpp(单色)图形库,drawStr()、drawBox() 等绘图 API 只区分“亮”与“灭”,不提供逐像素设置灰度值的接口。SSD1327 虽然支持 16 级灰度(4bpp 显存),但 u8g2 在其上仍固定将“亮”像素映射为最大灰度,因此画面只会呈现全亮或全黑,不会有中间深浅。真正需要利用 SSD1327 的 16 级灰度(灰阶渐变、抗锯齿字体等)时,需要绕开 u8g2 的绘图层直接操作 4bpp 显存,或改用支持灰度缓冲区的其他库。
4.2 安装库
在库管理器(工具 > 管理库)中搜索 U8g2 并安装,其他安装方式参考 Arduino 库管理教程。
4.3 确认引脚
本节接线以 微雪 ESP32-S3-Zero 迷你开发板 为例,与 Arduino 教程第 7 节 I2C 通信 相同:
| 开发板引脚 | OLED 模块 | 说明 |
|---|---|---|
| GPIO 1 | DIN(SDA) | I2C 数据线 |
| GPIO 2 | CLK(SCL) | I2C 时钟线 |
| 3.3V | VCC | 电源正极 |
| GND | GND | 电源负极 |
使用其他 ESP32 开发板时,请选择该板可用的 I2C 引脚,并同步修改代码中的 SDA_PIN / SCL_PIN;注意部分引脚在某些板型上有默认功能(如 UART、ADC),不宜占用。
使用其他 OLED 模块时只需更换对应构造器,其余代码不变。
4.4 示例代码
#include <U8g2lib.h>
#define SDA_PIN 1
#define SCL_PIN 2
// 1. 构造器:SSD1327、微雪 128×128 屏、全缓冲、硬件 I2C
// U8G2_R0 表示不旋转;ESP32 可在构造器中直接指定 I2C 引脚
U8G2_SSD1327_WS_128X128_F_HW_I2C u8g2(U8G2_R0, /* reset= */ U8X8_PIN_NONE,
/* clock= */ SCL_PIN, /* data= */ SDA_PIN);
void setup() {
// 2. 模块默认 I2C 地址为 0x3D,u8g2 使用左移一位后的 8 位地址
u8g2.setI2CAddress(0x3D << 1);
u8g2.begin();
// 3. 清空缓冲区
u8g2.clearBuffer();
// 4. 顶部居中显示标题:字体较窄,按实际文字宽度居中,避免超出屏宽
u8g2.setFont(u8g2_font_7x14B_tr);
const char *title = "Hello, OLED!";
int titleW = u8g2.getStrWidth(title);
u8g2.drawStr((128 - titleW) / 2, 13, title);
// 5. 标题下方左右并排绘制空心矩形与空心圆,二者顶部对齐、互不重叠
u8g2.drawFrame(8, 24, 50, 50);
u8g2.drawCircle(94, 49, 25);
// 6. 底部绘制实心矩形
u8g2.drawBox(28, 90, 72, 24);
// 7. 把缓冲区内容刷新到屏幕
u8g2.sendBuffer();
}
void loop() {
// 静态画面,无需重复刷新
}
烧录后屏幕自上而下依次显示居中标题、一行空心矩形与空心圆、底部一个实心矩形,四者间留有间距。几个关键点:
- 先绘制、后
sendBuffer():所有绘图 API 只修改内存缓冲区,调用sendBuffer()才真正通过 I2C 发送到屏幕显存。更新画面的固定流程为clearBuffer()→ 绘制 →sendBuffer()。 - 字体决定文字大小,且不会自动换行或缩放:
u8g2_font_7x14B_tr中的7x14即字符宽、高的像素数,选择字体时需自行估算文字总宽度是否超出屏幕,例如本例中 128 像素宽的屏幕最多能容纳 128÷7≈18 个该字体的字符。更多字体见 官方字体列表。 - 用
getStrWidth()计算实际宽度再居中:不同字符宽度可能不同(非等宽字体尤其明显),绘制前调用getStrWidth()获取像素宽度,再据此计算起始坐标,才能保证居中精确、不超出屏幕边界。 - 图形之间预留间距:
drawFrame()、drawCircle()、drawBox()等只按给定坐标绘制,彼此及与文字是否重叠完全由坐标决定,规划布局时应逐一核算各元素的边界范围。 - I2C 地址:微雪 1.5 寸模块默认地址为 0x3D。如果初始化后无显示,可先用 I2C 扫描程序 确认设备地址。
- 本例为演示用的静态画面:实际长期运行的产品应避免长时间显示完全静止的内容,以降低烧屏风险(见特性与局限)。
4.5 常见问题
| 现象 | 常见原因 | 处理 |
|---|---|---|
| 屏幕无显示 | I2C 地址不符;接线错误 | 用 I2C 扫描程序确认地址;核对 SDA/SCL |
| 画面整体偏移 2 像素、边缘有噪点 | 1.3 英寸屏误用 SSD1306 构造器 | SH1106 屏必须使用 SH1106 构造器 |
| 只显示了一部分内容 | 页缓冲模式下没有使用 firstPage/nextPage 循环 | 改用全缓冲(F)构造器 |
| 文字不显示 | 绘制后未调用 sendBuffer();字体未设置 | 确认已调用 setFont();检查 clearBuffer → 绘制 → sendBuffer 流程 |
| 文字超出屏幕或与图形重叠 | 未按字体实际宽高计算坐标 | 用 getStrWidth() 取宽度居中,并核算各元素边界留出间距 |
| 长期使用后有残像 | 烧屏 | 避免长时间显示完全静止的内容 |