
简介一套面向STM32F1系列微控制器的OLED显示驱动源码包围绕IIC通信接口提供完整驱动逻辑适用于嵌入式入门开发者或需要在项目中快速点亮OLED屏的工程师。压缩包共4个文件包括3个头文件与1个C源文件.h中声明了函数原型、OLED初始化配置结构体以及汉字字库数组.c则实现了IIC初始化、OLED_Init、OLED_Clear、OLED_SendCommand/SendData等核心函数并对中文显示所需的编码映射与像素数据发送做了示例封装。资源包整体仅6KB代码精简便于研读和移植。目前已有1096人学习。通过该驱动包读者可以直观理解STM32F1的GPIO复用配置、IIC外设时序控制以及OLED命令与数据的组织方式同时可参考其字号定义与取模数据结构灵活适配不同分辨率的屏幕有助于缩短显示模块的开发周期。1. 为什么我还留着这份 OLED 驱动文件不只是点亮一块屏前几年做一个室内环境监测的小项目板子上需要一块小屏幕显示温湿度和空气质量。当时手头最顺手的是一块 0.96 寸 OLED翻遍资料后发现别人分享的 .C/.H 驱动文件比我自己从头写要省事得多。这份 OLED 文件包专为 STM32F1 系列设计通过 IIC 通信方式驱动 SSD1306 控制芯片两个文件丢进工程就能显示字符、汉字、图片和简单图形。本文会拆解它的文件结构、IIC 时序、核心显示函数并给出实测有效的移植与二次开发方法。如果你正在做毕设、小仪表或快速原型又想绕开从底层寄存器开始的心力交瘁这份驱动值得下载。它能让你把精力花在业务逻辑上而不是在一行行配置屏幕的细节里翻车。2. 技术栈与文件结构一份能直接搬走的 .C/.H 驱动包很多 MCU 开发者第一次接触 OLED 驱动文件时第一反应是“里面是不是有复杂的寄存器配置”或“是不是依赖某个厂商库”。实际上这份驱动文件的依赖非常轻它只做三件事初始化屏幕、维护一块显存缓冲区、把缓冲区的数据通过 IIC 定时刷到屏幕上。2.1 驱动对象的正确认识SSD1306 与 IIC 的组合0.96 寸 OLED 常见方案是两种接口SPI 和 IIC。这份文件走的是 IIC好处是只占两根引脚SCL、SDA而且能和其他 IIC 设备挂在同一条总线上。SSD1306 本身是一块带 128×64 像素显存的驱动芯片它不仅驱动 OLED还负责把单片机送来的显示数据映射到每一个像素点上。关键点在于OLED 屏幕的数据不会自动留存你需要持续刷新否则会黑屏。驱动文件内部维护的显存缓冲区大小是 8 页 × 128 字节也就是 128×64 像素一屏数据量不大完全可以在普通 STM32F1 上用 GPIO 模拟 IIC 方式刷屏。硬件 IIC 能解放 CPU但在 F1 上使用模拟 IIC 更容易控制时序所以这份文件选择了后者。2.2 文件清单与依赖关系资源包里通常包含两个核心文件oled.c 和 oled.h。oled.c 里实现全部驱动逻辑包括 IIC 起始、停止、字节发送向 SSD1306 发送命令与数据绘制像素点、显示字符串和数字。oled.h 负责声明这些函数还定义了常用字符宽度、页数和显示方向之类的宏。除了这两个文件还有一份字库表常见做法是把它直接放在 oled.c 里以 const 数组形式保存 ASCII 字符点阵和常用汉字点阵。这样做的好处是移植时只复制两个文件即可不必额外引入资源目录。依赖关系上这份文件只依赖标准 CMSIS 头文件和两个延时函数毫秒级和微秒级延时。如果工程里没有这两个延时函数常见处理方式是直接调用系统滴答定时器或用简单空循环实现。2.3 初始化时序为什么要 8 条指令一组SSD1306 上电后不会自己进入可以显示的状态必须先通过 IIC 向它发送一串初始化命令让它知道接口模式、显示时钟、复用率和对比度等参数。常见的初始化顺序是先关闭显示再设置显示时钟分频和电荷泵然后设置内存地址模式最后打开显示。这份驱动的初始化函数会把几十条命令分成两组第一组是基础配置第二组是清屏和显示开关。如果你只发配置不发清除指令屏幕往往会留下一堆随机噪点。另外初始化后必须至少调用一次清屏函数把显存缓冲区全部置零并刷新否则首次显示时可能会出现花屏。代码里的初始化指令参数不是随便写的比如 0xA8 是设置多路复用比0x3F 对应 64 行0x8D 开启电荷泵时必须先给 0x14 再给 0x14这是 SSD1306 数据手册里写死的步骤。缺少电荷泵开启屏幕会永久保持黑屏这也是新手最容易误解的“编译没错但屏幕不亮”的原因。3. 核心模块拆解从 IIC 底层到 OLED 显示如果把 OLED 驱动看作一条链路最底层是 IIC 引脚操作中层是 SSD1306 协议处理上层才是字符串、数字和图片绘制。这一章从下往上逐层拆解每一步的代码逻辑都会说明。3.1 IIC 底层通信GPIO 模拟还是硬件 IIC模拟 IIC 的核心代码是起始信号、停止信号和字节发送。起始信号是 SCL 保持高电平期间 SDA 由高变低停止信号相反。这两个信号是 IIC 通信的“握手”任何从设备都要靠它们识别一帧数据的开始与结束。void OLED_IIC_Start(void) { OLED_SDA_GPIO_HIGH(); /* SDA 先拉高 */ OLED_SCL_GPIO_HIGH(); /* SCL 拉高 */ delay_us(5); OLED_SDA_GPIO_LOW(); /* SCL 高电平期间 SDA 拉低产生起始条件 */ delay_us(5); OLED_SCL_GPIO_LOW(); /* 拉低 SCL准备发送数据 */ } void OLED_IIC_Stop(void) { OLED_SCL_GPIO_LOW(); /* 确保 SCL 为低 */ OLED_SDA_GPIO_LOW(); /* SDA 拉低 */ delay_us(5); OLED_SCL_GPIO_HIGH(); /* SCL 拉高 */ OLED_SDA_GPIO_HIGH(); /* SDA 在高电平期间拉高产生停止条件 */ delay_us(5); }这段代码里每次切换信号后都加了微秒延时目的是让总线电平稳定。GPIO 速度配置也很关键在 STM32F1 上最好把 SCL 和 SDA 引脚设为开漏输出并外加上拉电阻或者配置为复用推挽输出。如果引脚配置成普通推挽输出在有其他 IIC 设备共用总线时容易造成电平冲突。每次写一个字节是按位把数据放到 SDA 上同时用 SCL 产生时钟脉冲。发送完 8 位数据后从设备会在第 9 个时钟周期拉低 SDA 作为应答因此主机在发送完一个字节后应该释放 SDA然后读取应答位。这份驱动里省略了严格的应答判断因为 OLED 屏通常不会不应答但如果总线接错或地址不对这里就区分不出故障。3.2 驱动核心写指令、写数据与显存刷新在 IIC 底层的上面SSD1306 规定了两个重要的控制字节0x00 表示后续数据是命令0x40 表示后续数据是显示数据。驱动里的 OLED_WR_CMD 和 OLED_WR_DATA 就是基于这两个控制字节实现的。void OLED_WR_CMD(uint8_t cmd) { OLED_IIC_Start(); /* 起始条件 */ OLED_IIC_SendByte(0x78); /* 7 位地址 0x3C 左移一位加上写位得到 0x78 */ OLED_IIC_SendByte(0x00); /* 命令控制字节 */ OLED_IIC_SendByte(cmd); /* 具体命令 */ OLED_IIC_Stop(); /* 停止条件 */ } void OLED_WR_DATA(uint8_t dat) { OLED_IIC_Start(); OLED_IIC_SendByte(0x78); OLED_IIC_SendByte(0x40); /* 数据控制字节 */ OLED_IIC_SendByte(dat); OLED_IIC_Stop(); }参数 0x78 的来源是OLED 屏的 7 位 IIC 地址通常为 0x3C如果板上的地址选择电阻改过可能变成 0x3D。这里左移一位保留最低写位0x3C 左移一位就是 0x78。如果显示器不亮第一件事就是检查这句地址是否与实际硬件匹配。刷新操作是把显存缓冲区 GRAM 里的 1024 个字节通过 IIC 分批发送到屏幕上。SSD1306 的显存布局是按页排列的每页 8 个像素行8 页共 64 行。屏幕刷新时先设置列地址和页地址再连续发送数据。驱动里的 OLED_Refresh 函数会循环 8 个页每页发送 128 个字节。3.3 画点、字符与中文显示所有显示效果最终都要落到像素层面。画点函数的核心是计算一个坐标点属于哪一页、哪一列再通过置位或清零对应缓冲区字节的位来实现。字符显示就是按 8×16 点阵在屏幕上摆放点中文则是 16×16 点阵。void OLED_DrawPoint(uint8_t x, uint8_t y, uint8_t dot) { uint8_t page, column, bit_pos; if (x 127 || y 63) return; /* 坐标越界直接返回 */ page y / 8; /* 每一页包含 8 行像素 */ column x; bit_pos y % 8; if (dot 1) { OLED_GRAM[page][column] | (1 bit_pos); } else { OLED_GRAM[page][column] ~(1 bit_pos); } }这里 OLED_GRAM 是一个二维数组第一维是页第二维是列。画点前必须检查坐标是否超出 128×64否则数组越界会污染其他内存区域轻则显示错乱重则导致单片机死机。画点后不会立即显示在屏幕上必须调用 OLED_Refresh 把缓冲区同步到屏幕这个习惯很重要有不少人改了半天缓存没刷新以为函数坏了。字符显示函数的参数是行和列以及要显示的字符。它会先从字库表中取出 16 字节的点阵再按位调用 OLED_DrawPoint。中文字库通常要额外引入一个较大的数组原驱动里一般只带常用 ASCII 字符二次开发时增加中文字符需要注意 Flash 空间占用。4. 避坑记录OLED 显示异常排查与移植注意OLED 驱动文件不大看起来没什么可踩坑的但真跑起来后问题往往比预想多。这一章整理了几条我在实际调试和带人移植时遇到的高频问题每一条都有现象、原因和解决思路。4.1 上电白屏IIC 地址 0x78 与 0x7A 写错的后果现象程序下载后屏幕完全没有反应背光不亮或者一片均匀的白屏。排查串口和引脚都没发现问题。原因OLED 模块的 IIC 地址并不是固定的。大部分模块默认地址是 0x3C对应发送地址 0x78但部分模块的地址选择电阻默认接到了高电平从机地址变成 0x3D对应发送地址就是 0x7A。驱动文件里写死 0x78如果硬件实际是 0x3D从机根本不会应答。解决先查模块背面的地址电阻标志或者用 IIC 扫描程序在总线上扫一遍看看到底是哪个地址有响应。如果手头没有逻辑分析仪就写一个简单的扫描函数把 0x00 到 0x7F 的地址都发一遍哪个地址从机有应答那个就是实际地址。扫描代码调试一轮后把驱动文件里的 0x78 改成实际值即可。4.2 花屏与残影刷新缓冲区没有清空现象屏幕能显示内容但显示新内容时会出现旧画面的残影或者初次上电时出现雪花一样的随机噪点。无论怎么刷新噪点总在固定区域出现。原因这是典型的显存缓冲区未初始化问题。OLED_GRAM 是全局数组如果不显式清零上电后内容是不可预知的随机值。初始化函数只配置了 SSD1306 的寄存器并没有把所有像素点置零。驱动文件里的清屏函数如果只清屏幕却不同步清 OLED_GRAM也会出现刷新后旧数据被再次搬运上屏的情况。解决初始化完成后强制执行 memset(OLED_GRAM, 0, sizeof(OLED_GRAM))再调用一次 OLED_Refresh。以后每次清屏都先清缓冲区再刷屏幕。有些驱动甚至不让用户直接调用 OLED_Clear而是把清屏逻辑隐藏在初始化里就是为了避免这种问题。我在自己的工程里加了一个规则任何对 OLED_GRAM 的写操作后面必须跟一次 OLED_Refresh不然不允许合入。4.3 引脚接反导致 CRC 校验一样玄学的“假正常”现象程序编译通过SCL 和 SDA 引脚互换后屏幕有时能亮但显示内容错乱偶尔还会伴随整屏闪烁。看起来像时序不稳定其实问题出在物理连接。原因IIC 的 SCL 和 SDA 一旦接反从机无法建立正确的时钟关系驱动发送的数据全部作废。偶尔能亮是因为上电瞬间总线的杂散信号恰好满足了一次假通信。这种故障在示波器上看起来像电平不稳容易让人误以为是延时不够从而去改时序参数越改越乱。解决先用万用表确认模块上的 SCL 和 SDA 引脚编号再对照驱动文件里宏定义的 GPIO 配置。我在驱动文件里加了一个 OLED_GPIO_Config 函数把引脚选择集中管理避免在主函数里到处分散配置。连接正确后再考虑延时和上拉电阻的问题。4.4 上拉电阻与 GPIO 模式模拟 IIC 的隐藏死穴现象单独测试屏幕没问题和另一个 IIC 传感器接在同一对 SCL/SDA 上后屏幕显示卡顿甚至传感器数据也读到一半就出错。单独断开任何一方都正常。原因模拟 IIC 的驱动文件通常把 GPIO 配置成推挽输出这在高电平输出时会把总线强行拉高。如果多个设备共用一个 IIC 总线推挽输出会与其他设备的开漏输出打架。SSD1306 和大多数 IIC 设备都是开漏设计总线必须靠外部上拉电阻才能拉高主机也应使用开漏模式。解决把所有模拟 IIC 的 SCL/SDA 引脚改为开漏输出并在外部加上 4.7KΩ 左右的上拉电阻或者在 STM32F1 内部启用上拉。修改之后总线上多设备通信就稳定了。这个坑在单设备场景下不会暴露直到你尝试扩展第二个 IIC 外设时才发作。4.5 延时函数精度微秒延时被编译器优化掉现象屏幕显示时速度快到出现严重残影有时候刷新一屏内容要花很长时间但像素点仍然歪歪扭扭甚至出现横线缺失。原因驱动文件里用到的 delay_us 如果是空循环实现编译器在开启优化后会把整个循环优化掉导致 IIC 时钟周期过短SSD1306 跟不上。尤其在 Keil 默认 O0 优化时没问题一开 O2 就翻车。解决延时函数不要写成“for (i 0; i n; i);”这种空转形式而要使用 DWT 或 SysTick 计数器做精确延时。或者把延时函数声明为 volatile 参数避免被编译器优化。我在驱动文件里把 OLED_IIC_Delay 统一替换成了基于 SysTick 的 delay_us 实现从此再没出现优化导致的时序问题。5. 二次开发把驱动文件改造成你要的显示方案这段资源最大的价值在于可以当做一个显示底座来扩展。从一个只会显示固定字符的驱动改成能显示实时曲线、菜单页或简易动画都只需要替换或增加少量函数。5.1 从“显示字符串”升级为“带背景的图片绘制”原驱动只提供画点、字符和整屏刷新接口如果你要绘制一张 128×64 的图片最简单的做法是把图片取模后的数据按字节写入 OLED_GRAM。void OLED_ShowPicture(const uint8_t *pic) { uint8_t i, j; for (i 0; i 8; i) { for (j 0; j 128; j) { OLED_GRAM[i][j] pic[i * 128 j]; } } OLED_Refresh(); }这段代码把一张 1024 字节的图片数据直接映射到显存缓冲区。图片取模时建议按“列行式”或“页式”取模否则会出现图片左右颠倒或上下翻转。取模方向与 OLED_GRAM 的页/列布局保持一致是二次开发最容易踩的坑。5.2 扩展菜单页面时的缓冲状态管理做菜单时不要每次更新都把整个屏幕清掉再重绘。比较稳的做法是维护一个“当前页面 ID”只在页面切换时清屏平时仅更新变化区域。这样刷新闪烁会明显减少。OLED 本身刷新率足够快但 IIC 传输 1024 字节仍需要几毫秒频繁整屏刷新会显得拖沓。5.3 验证方法先跑通地址扫描再做功能测试二次开发完成后我习惯先用一个最简单的 demo 验证 IIC 地址和引脚配置再跑点亮程序最后才测复杂显示。如果某一步不正常按照第 4 章的排查步骤从地址、接线、延时、缓冲区四层去找问题不要盲目调时序。从那以后我每次拿到一块陌生 OLED 屏都会强制走一遍“扫描地址 → 点亮单点 → 整屏刷新 → 显示字符”的流程而不是直接套用旧驱动。这样做节省的时间远超想象换屏和换板子都变得非常轻松。希望这份驱动文件的拆解记录能帮你在做显示功能时少走些弯路也祝你一次点亮。本文还有配套的精品资源点击获取