资讯动态

0.91寸OLED屏IIC驱动详解:从SSD1306原理到字符显示实战

发布时间:2026/9/7 8:19:49 来源:尧图企业网站定制
简介面向嵌入式初学者的OLED驱动资源包专门解决0.91英寸128×32分辨率IIC接口模块的字符显示问题。资料基于两线IIC通信仅需SDA与SCL两个引脚即可驱动屏幕适合Arduino、STM32等微控制器平台快速集成。代码内预置6×8与8×16两套ASCII字库并封装了初始化、光标定位、字符写入、清屏等完整显示函数同时也提供了文本颜色、背景色、方向控制与滚动显示等功能扩展思路。压缩包共5个文件由3个.h头文件和2个.c源文件组成覆盖驱动实现、接口声明及字模映射数据整体仅9KB轻量精简、便于移植和二次修改。目前已有1603人学习下载适合正在做小型交互界面或可穿戴设备的开发者可借此快速上手IIC OLED字符显示节省底层驱动调试时间。 玩过单片机的人基本都绕不开 OLED 屏。尤其是 0.91 寸的 12832 模组体积比指甲盖还小用来显示个传感器数值、菜单选项、调试状态简直是嵌入开发里的“万金油”。我这阵子刚好在一个小项目里又用到它顺手把整套字符显示驱动重新理了一遍发现很多细节如果不注意真能把人折腾到怀疑人生。这篇就把我的实际经验和踩过的坑一次性说清楚从 IIC 通信原理到显示函数封装再到各种疑难杂症排查都给你安排得明明白白。OLED 0.91 模组核心就是一块分辨率为 128x32 的显示面板内部控制器是 SSD1306通信接口这里我们选 IIC也就是 I2C。它传输速率比不上 SPI但胜在接线少两根线就能搞定通信对绝大多数显示场景来说完全够用。这块屏适合谁用从刚学 STM32 的新手到做智能家居、DIY 仪器仪表的进阶玩家基本都能用得上。我写这篇的定位就是给手里有这块屏但还没真正吃透驱动原理的朋友把“显示字符”这件事做到极致。1. 为什么选 IIC 接口驱动思路先想清楚1.1 IIC 和 SPI我用下来到底怎么选不少新手纠结这个问题我的看法很明确如果是追求刷新率、要播放动画上 SPI如果只是显示静态字符或低频更新IIC 完全够用还能省下 GPIO。0.91 寸这块屏分辨率只有 128x32数据量满打满算就 512 字节IIC 在 400KHz 模式下刷一帧大概也就 10ms 左右显示普通文本你根本感知不到延迟。IIC 还有个好处是抗干扰能力强于 SPI 的非标准接线方式。因为 IIC 是开漏结构加上拉电阻设备地址和时序都是协议层的不像 SPI 那样虽然有 CS 引脚但缺少严格的握手机制。之前我在一块有电机驱动的板子上同时用 SPI 和 IIC 接两块屏SPI 那根线一靠近电机线就花屏IIC 却没出过问题这就是总线信号完整性差异在工程上的直观反馈。1.2 SSD1306 的显存结构与 IIC 命令的关系SSD1306 内部有 128x64 bit 的显存GDDRAM可以映射到外部但 0.91 寸 12832 这个模组只暴露了其中 128x32 的区域。它的显存是按页Page组织的共 8 页Page0~Page7每页高 8 像素。我们用的 12832 屏一般只用 Page0~Page3 即前 4 页。IIC 接口对屏幕的操作本质上就是往这个 GDDRAM 里写数据或者给它发命令。具体来说IIC 传输的每个字节前面都有个控制字节Co0, D/C#0x00表示后面的字节是命令0x40表示后面的字节是数据。这一点是理解驱动代码的核心阀门很多人看初始化代码头晕就是因为没理清什么该走命令通道、什么该走数据通道——地址、列起始、页选择都是命令像素数据全是数据。2. 核心细节拆解IIC 时序与 SSD1306 初始化2.1 读写时序你真正需要关心的几个电平区间很多教程直接让你调库但从没解释过时序图。IIC 有四个基本信号起始START、停止STOP、数据位、应答ACK/NACK。起始信号是 SCL 高电平期间 SDA 由高变低停止信号是 SCL 高电平期间 SDA 由低变高。数据有效性则是 SDA 必须在 SCL 高电平期间保持稳定在 SCL 低电平期间才允许变化。实操中你不需要自己用 GPIO 模拟这些时序的话可以调 HAL 库或裸机发送但必须清楚一点OLED 的 IIC 从机地址通常是0x3C写方向有些模块是0x3DSA0 引脚接高。读出来是 0x3D写是 0x3D 左移 1 即 0x7A。实际上STM32 HAL 库的地址参数是 8 位地址0x3C 1才是0x78这就是坑王之王后面章节细说。2.2 上拉电阻选多大这里有经验公式IIC 总线上拉电阻太小会导致灌电流过大信号摆幅受限太大会让上升沿变缓高速通信时出错。推荐值一般 2.2kΩ 到 4.7kΩ 之间。我的习惯是如果总线只有这一块屏且 IIC 速率不超过 400KHz选 4.7kΩ如果总线上挂了多个设备或线长超过 20cm选 2.2kΩ 更稳。之前在一个项目里屏和单片机之间用杜邦线拉开了 30 多厘米原厂模块上的 10kΩ 上拉电阻导致波形上升沿严重畸变跑 400KHz 直接花屏。后来把屏那边的 10kΩ 拆掉在主控这边用 2.2kΩ问题就消失了。所以发现花屏先别怀疑代码用示波器看一眼波形上升沿是否缓坡十有八九是上拉问题。2.3 SSD1306 初始化序列详解SSD1306 上电后必须经过初始化才能正常显示序列本身不复杂但顺序不能乱。我最常用的一套关闭显示0xAE设置显示时钟分频/振荡器频率0xD5, 0x80设置 multiplex 比率0xA8, 0x1F128x32 屏填 0x1F设置显示偏移0xD3, 0x00设置显示起始行0x40 | 0x00设置 segment 重映射0xA0或0xA1方向不同设置 COM/scan 方向0xC0或0xC8设置 COM 引脚硬件配置0xDA, 0x02128x32 屏填 0x02128x64 屏填 0x12设置对比度0x81, 0x7F设置预充电周期0xD9, 0xF1设置 VCOMH 电压0xDB, 0x40设置显示模式为正常0xA4开显示0xAF这里注意两只大坑一是0xDA那行用于 128x64 屏是0x12如果 128x32 屏错填0x12会出现只有下半屏亮或整体显示错乱的现象。二是0xA8的0x1F对应 32 行如果写成了0x3F屏幕的显示区就映射到了 64 行字符会被截断。3. 全套显示函数实现从底层到应用3.1 用 STM32 HAL 库封装底层 IIC 写命令/写数据底层的操作无非是两个函数写命令CMD和写数据DATA。我用的 MCU 是常见的 STM32F103C8T6HAL 库环境。具体代码如下void OLED_WriteCmd(uint8_t cmd) { uint8_t buf[2] {0x00, cmd}; // 控制字节 0x00 表示命令 HAL_I2C_Master_Transmit(hi2c1, 0x3C 1, buf, 2, 10); } void OLED_WriteData(uint8_t data) { uint8_t buf[2] {0x40, data}; // 控制字节 0x40 表示数据 HAL_I2C_Master_Transmit(hi2c1, 0x3C 1, buf, 2, 10); }底层就这么短但要注意HAL_I2C_Master_Transmit的超时参数不要设得太短10代表 10ms正常一帧数据在高速 I2C 下用不到 1ms但初始化阶段或总线忙时超时太短容易返回 HAL_BUSY导致后续命令没发出去。我一般给它 20ms稳妥。如果你是裸机开发不依赖 HAL 库那就自己模拟 IIC 时序。原理也很简单具体代码不在这里展开核心就是上面提到的 SCL/SDA 电平变化顺序。差别在于软件模拟灵活性高可以放到任意 GPIO 上速度不受硬件 IIC 外设限制也不容易跟其他外设抢资源。缺点就是 CPU 占用略高但这块屏刷新频率低无所谓。3.2 坐标与页面管理函数128x32 的屏SSD1306 内部结构是 4 页Page0~Page3每页覆盖 8 行像素。列地址则覆盖 128 个点。要让一个像素显示在正确位置必须先设置页地址和列地址然后再写数据。所以我又封装了这两个函数void OLED_SetPos(uint8_t x, uint8_t page) { OLED_WriteCmd(0xB0 page); // 设置页地址 0~3 OLED_WriteCmd(x 0x0F); // 设置列地址低四位 OLED_WriteCmd(0x10 | (x 4)); // 设置列地址高四位 }注意SSD1306 的列地址设置比较怪是分为两段低 4 位 高 4 位的而且实际写入后列地址会自动增加也就是说你不用每次写完一个字节都重设地址连续写 128 个字节就自动铺满一行。这个特性对字符串连续显示特别友好但前提是你要保证写入的数据字节数和当前行的剩余列数严格匹配否则会串行到下一页。我见过有人在这儿栽跟头字符边界就乱了。3.3 显示一个字符核心原理是字模映射字符显示的底层原理就是把字符的像素点阵从字模数组里取出来一个字节代表一列或一行的 8 个像素点。以经典的 6x8 字体为例6 表示字宽 6 列8 表示字高 8 行恰好对应 SSD1306 一页的 8 像素高度所以每个字符由 6 个字节组成一列一个字节。我们建立一个 ASCII 字模表从空格0x20开始到}0x7D一共 94 个字符。每个字符 6 个字节的数据。我通常用取模软件一键生成格式选择 C51 格式、列行式、阴码。生成的数组大概长这样const uint8_t F6x8[][6] { {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 空格 {0x00, 0x00, 0x5F, 0x00, 0x00, 0x00}, // ! // ... 其他字符 };OLED_ShowChar函数的逻辑根据字符编码计算出相对字模表的偏移(char - 0x20)把 6 个字节的数据依次写入当前位置。每写一个数据列地址自动加 1。代码如下void OLED_ShowChar(uint8_t x, uint8_t page, char ch) { uint8_t i; OLED_SetPos(x, page); for (i 0; i 6; i) { OLED_WriteData(F6x8[ch - 0x20][i]); } }注意x 坐标单位是像素不是字符数。如果你要在页的中间显示一个字符x 可以设 60实际列地址就是 60~65。而页只有 0~3共 4 页相当于把屏幕纵向分成 4 个 8 像素高的横条。3.4 显示字符串循环与换行控制字符串显示就简单了循环调用OLED_ShowChar同时累加 x 坐标。我的实现里还加了分页和换行逻辑超过屏幕宽度 128减掉字符宽度 6就换到下一页void OLED_ShowString(uint8_t x, uint8_t page, char *str) { while (*str ! \0) { if (x (128 - 6)) { x 0; page; if (page 3) page 0; } OLED_ShowChar(x, page, *str); x 6; } }这段逻辑在实际使用中有一个问题如果同一页上已经写满了字符你直接换到下一页没问题但如果你要在一行显示固定位置的数值比如只更新数字不更新整个字符串就需要先用空格清掉旧内容再写新内容否则数字从 9 变 10 时个位后面会残留一个 0。这里我给个建议在做数值刷新时别偷懒只更新变量部分把整个固定前缀值的字符串都重新显示一遍。3.5 中文字符显示16x16 字模的思路128x32 屏幕高度只有 32 像素16x16 的汉字正好可以放两行。中文显示原理跟 ASCII 类似但字模数组要用 16x16 点阵。一个汉字占 32 字节显示时需要同时操作两个页Page0~Page1 或 Page2~Page3。先写上半部分 16 字节再设置到下一页写下半部分 16 字节具体实现void OLED_ShowChinese(uint8_t x, uint8_t page, uint8_t index) { uint8_t i; OLED_SetPos(x, page); for (i 0; i 16; i) { OLED_WriteData(Hz16[index][i]); } OLED_SetPos(x, page 1); for (i 0; i 16; i) { OLED_WriteData(Hz16[index][16 i]); } }中文显示最大的坑在于取模方式必须和显示函数匹配。选择“列行式”时先逐列取上半部分 8 行再逐列取下半部分 8 行这种字模正好对应上面的循环方式。如果你用了“行列式”显示出来会逆时针旋转 90 度肉眼看着就是变形字体。我建议你把取模软件的设置固定下来每次生成前都拍照或截图保存一份设置不然过两个月重新生成字模十有八九会忘记当初用的什么格式。4. 实操总结与常见疑难杂症速查4.1 常见问题列表这里整理了我在实际项目中以及帮群友排查时碰到的高频问题直接做成速查表现象根本原因解决方案上电后屏幕完全不亮IIC 从机地址错0x3C 没左移确认 HAL 库用0x3C 1裸机 7 位地址时用 0x3C花屏/乱码尤其闪烁上拉电阻过大或线太长换 2.2kΩ~4.7kΩ缩短杜邦线或换屏蔽线显示内容整体偏色/对比度低对比度寄存器0x81后面参数太小提高至0x7F或检查 VCOMH 设置字符上下错位/有拖影页地址设置错误检查OLED_SetPos中页是否超出 0~3初始化后白屏没有内容初始化序列中0xA8参数写 0x3F128x32 屏必须写0x1F刷新数字时出现残影没有清掉旧字符先输出空格覆盖再输出新值屏幕亮度异常且功耗大预充电周期0xD9设置不合理使用0xF1或调高至0xCC观察效果用模拟 IIC 有时卡死缺少超时机制代码里加一个计数器超时退出避免死循环4.2 排查技巧先硬件后软件的顺序我调试这类屏的固定流程是这样的先量上电瞬间 VCC 电流3.3V 供电下全亮整屏大概 15~25mA不亮/电流为零就查供电和接线。然后量 SCL/SDA 对地电压正常情况应该在 1.8V 到 3.3V 之间如果接近 0V 或没有波形就是上拉或线序问题。再然后才用示波器或逻辑分析仪抓 IIC 波形确认地址字节是不是 0x78、命令字节是不是 0x00、数据字节是不是 0x40。最后才看代码逻辑。特别注意每逢遇到“屏幕亮一半”的情况优先怀疑0xDA寄存器设的0x02/0x12而不是整屏重刷。这个问题我遇到过不下三次每次只要看到只有上半屏或下半屏亮我直接先改这个参数。4.3 HAL 库 vs 模拟 IIC各自适用场景最后谈谈代码层面的选择。如果用的 MCU 自带硬件 IIC比如 STM32F103C8T6HAL 库直接调用代码干净不占用 CPU 等待时序。但硬件 IIC 有时会在总线上遇到死锁SCL 正常、SDA 一直为低这是某个从机拉死了总线。处理办法是复位时对 SCL 连续翻转 9 次让从机释放 SDA或者干脆改用软件模拟。软件模拟 IIC 的解决方案就是代码里初始化时把 SDA 配置为开漏输出配合上拉这样不存在拉死总线的问题。坏处是 SDA 引脚在输出低电平时如果程序跑飞成输入模式总线状态不可控。所以我的项目里除非硬件 IIC 被其他外设占用了引脚否则默认用硬件 IIC只有在 GPIO 实在抽不出来时才上软件模拟。5. 最后一步完整示例与我的个人实操心得5.1 一个可直接复制的示例工程片段把上面所有函数串起来一个最小可用的 Demo 长这样#include stm32f1xx_hal.h extern I2C_HandleTypeDef hi2c1; void OLED_Init(void) { HAL_Delay(100); OLED_WriteCmd(0xAE); OLED_WriteCmd(0xD5); OLED_WriteCmd(0x80); OLED_WriteCmd(0xA8); OLED_WriteCmd(0x1F); // 128x32 OLED_WriteCmd(0xD3); OLED_WriteCmd(0x00); OLED_WriteCmd(0x40 | 0x00); OLED_WriteCmd(0xA0); OLED_WriteCmd(0xC0); OLED_WriteCmd(0xDA); OLED_WriteCmd(0x02); // 128x32 用 0x02 OLED_WriteCmd(0x81); OLED_WriteCmd(0x7F); OLED_WriteCmd(0xD9); OLED_WriteCmd(0xF1); OLED_WriteCmd(0xDB); OLED_WriteCmd(0x40); OLED_WriteCmd(0xA4); OLED_WriteCmd(0xAF); } int main(void) { HAL_Init(); // 系统时钟配置、I2C1 GPIO 和 I2C1 初始化略 OLED_Init(); OLED_ShowString(0, 0, Hello OLED!); OLED_ShowString(0, 1, 128x32 IIC); OLED_ShowString(0, 2, STM32 HAL); OLED_ShowChinese(0, 3, 0); // 假设字模表索引 0 是 欢 while (1) { // 主循环可做其他事 } }这个 Demo 编译烧录后你应看到四行内容前两行各占一页第三行在第三页第四行中文在第四页。如果字符挤在一起或者缺行先检查是不是忘了把0xDA的0x12改成0x02。5.2 我在实际使用中踩过的坑和体会前阵子给一个实验室设备做状态面板要求在 OLED 上同时显示温度、湿度、运行时间和 WiFi 连接状态。我用了 128x32 分四页布局结果发现一个奇葩问题每次更新温度数值时页面上其他区域偶尔会出现像素噪点。排查了两天最后发现是 IIC 中断优先级问题——屏幕上新数据到达时恰好系统在响应 UART 中断而 HAL 的 IIC 传输函数本身不是中断安全的我在中断里改了屏幕数据导致传输被撕裂。解决方案是要么给屏幕数据传输加互斥锁要么把屏幕刷新放到主循环的轮询里改动方案我用的是后者因为 UI 刷新频率不高轮询完全够用还省去了一堆线程安全问题。这是我特别想分享的一点OLED 显示函数写完后先别急着加各种特效把任务调度和数据更新机制想清楚不然越到后面bug 越难查。另外一个小技巧用 IIC 驱动 OLED 时尽量别把刷新函数放到定时器中断里。中断一多优先级处理不当波形毛刺可能导致 IIC 时序不满足得不偿失。主循环里刷肉眼根本看不出来刷新过程。5.3 这个项目后续还能怎么扩展基础字符、汉字显示跑通后你可以沿三个方向继续深入一是加上数值显示函数把整数、浮点数转成字符串再显示注意组好 sprintf 缓冲区防止缓冲区溢出二是实现反色显示、左右滑动、菜单循环这些 UI 交互效果核心都是对页和列地址的灵活操作三是把 IIC 地址改成可配置的做成驱动库的形式方便以后换 SSD1306 的其他尺寸屏比如 128x64直接套用。我当时就是把这套驱动封装成了一个独立模块供三个项目复用每次迁移新工程只需要改一下 IIC 句柄。如果你也经常做小玩意儿强烈建议一开始就把驱动和业务逻辑分离别把显示函数散落在 main.c 里否则后续维护会非常痛苦。这套 0.91 寸 OLED 驱动方案稳定、简洁、够用希望能帮你在 IIC 显示这条路上少走几个弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价