STM32驱动0.96寸OLED滚动显示:SSD1306硬件与软件滚动实现 📅 发布时间:2026/9/9 6:05:07 👁 浏览次数: 简介基于STM32F103VE的0.96寸OLEDSSD1306滚动显示示例项目基于STM32标准外设库编写为嵌入式开发者提供可直接编译运行的Keil工程。资源核心演示超长文字水平滚动与屏幕垂直水平滚动两种效果适用于信息轮播、仪表盘状态显示等需要动态文本输出的场景。压缩包为RAR格式共283个文件、总大小8.22MB其中.c/.h为程序源码.o/.crf为编译器生成的中间文件.uvprojx则为工程配置入口不同文件类型对应不同编译阶段便于理解工程构建过程。已有1164人学习下载说明其在OLED开发场景中具有普适参考价值。读者可借鉴完整的OLED驱动、初始化和滚动参数配置逻辑快速在自己的项目中实现平滑滚动也可在此基础上移植到其他STM32型号或按需调整滚动方向与速度。 0.96寸OLED屏幕加滚动显示是我最近调一个便携仪表项目时反复折腾的部分。很多朋友拿到OLED模块点亮、显示字符都没问题一到做水平滚动或者垂直水平滚动就开始翻车——不是滚不动就是滚起来花屏再要么滚动方向完全不对。这篇文章我围绕基于STM32F103VE的0.96寸OLED驱动与滚动示例程序把硬件滚动、软件滚动的原理和实测代码完整梳理一遍适合正在做OLED显示、想给屏幕加滚动效果的开发者参考。1. 先把底子打好OLED模块、SSD1306与STM32F103VE的搭配逻辑1.1 0.96寸OLED到底是个什么东西大多数0.96寸OLED模组用的都是SSD1306驱动芯片分辨率固定为128x64像素。这块屏和常见的LCD最大的区别在于它自发光不需要背光所以对比度高、可视角度大功耗还低。SSD1306内部集成了一块1KB的GRAM正好对应128x64个像素点每个像素占用1 bit所以整屏数据量就是1024字节。理解SSD1306的显存结构是后面做滚动的基础。它把64行像素按8行一组分成8页也就是Page0到Page7每一页又横向分成128个字节每个字节的8个bit对应这一页中的8行像素。讲得直白点屏幕可以看成8行“点阵字”每行有128个字节你要显示什么内容就往对应的字节里填0或1。搞明白这个结构你就能理解为什么滚动命令里到处都有“起始页”“结束页”这两个参数——它们指定的是哪些行区域参与滚动。1.2 为什么选STM32F103VE而不是C8T6STM32F103VE是100引脚的增强型芯片512KB Flash、64KB SRAM主频72MHz带I2C、SPI、USART等常用外设。很多初学者手头是STM32F103C8T6也用得好好的那为什么示例工程要基于F103VE最直接的原因是工程可扩展性。OLED滚动本身占的资源很少一个滚动命令也就几个字节的事情。但真正的产品项目不会只做显示往往还要接传感器、按键、串口协议、掉电存储甚至跑个简单的UI框架。C8T6只有64KB Flash和20KB SRAM用HAL库写几个外设就紧张了F103VE的512KB Flash对HAL库工程来说非常宽裕调试版本、日志输出、字库放大这些都可以放得下。100个引脚也让硬件设计有充足的余量。我做个对比表格你自己体会一下型号FlashSRAM引脚数典型场景STM32F103C8T664KB20KB48学习验证、小工具STM32F103VET6512KB64KB100带GUI、协议栈、复杂逻辑的产品原型当然滚动显示这部分代码在C8T6上一样能跑只要你把I2C引脚和时钟配置好寄存器命令完全通用。1.3 滚动显示的两种路线硬件滚动与软件滚动很多人在做滚动时第一时间想到的是自己在代码里移动像素数据这就是软件滚动。软件滚动很灵活但每次移动都需要重新刷新整屏数据I2C带宽、CPU时间都消耗不少。其实SSD1306自带硬件滚动引擎只要发一串配置命令屏幕自己就会滚动CPU完全不用管。硬件滚动的优点是省资源缺点是灵活性差它能做的只是整页范围的循环滚动无法精细控制单个字符或者某个小区域的滚动速度和轨迹。而软件滚动虽然占资源但可以在任意区域滚动可以和动画刷新、内容更新配合。实际项目中两条路线我都会用纯展示用硬件滚动需要交互或者实时更新内容的场景用软件滚动。两块内容后面的章节都会讲到。2. 驱动与初始化滚动功能的地基2.1 硬件连接与I2C地址0.96寸OLED模组常见的有I2C和SPI两种接口I2C版通常是四针VCC、GND、SCL、SDA。示例默认用I2C接口因为接线最少调试最方便。STM32F103VE的I2C1引脚默认在PB6SCL和PB7SDA模块的VCC接3.3VGND共地SCL和SDA分别接到PB6和PB7。这里有个细节多数OLED模块板上已经带了I2C上拉电阻不需要额外接。但不同厂家的模块上拉电阻值不一样有的是4.7k有的是10k如果I2C通信不稳定可以试试把I2C时钟频率降到100kHz。模块的I2C从机地址默认是0x788位写地址换算成7位地址就是0x3C。部分模块背面有地址选择电阻可以改成0x7A改完了代码里的地址也要同步改。关于STM32F103的硬件I2C网上一直有“不稳定”的说法其实主要是早期标准库使用不当导致的。用HAL库驱动时注意设置合理的超时时间并且不要在中断里做阻塞式传输稳定性是够用的。你要是实在不放心也可以用GPIO模拟I2C这个后面在排查部分再说。2.2 初始化序列里的关键命令SSD1306上电后默认是关闭显示状态的很多人OLED接上电源发现不亮第一反应是模块坏了其实不是——必须发送0xAF命令打开显示。初始化序列里最容易被忽略的两个命令一个是0x8D配合0x14开启电荷泵另一个是0x20设置内存寻址模式。电荷泵不开启屏幕就是一片黑寻址模式设置错后面刷新显存的位置会对不上。// oled.c 初始化部分 void OLED_Init(void) { OLED_WriteCmd(0xAE); // 关闭显示进入配置状态 OLED_WriteCmd(0xD5); OLED_WriteCmd(0x80); // 显示时钟分频/振荡频率 OLED_WriteCmd(0xA8); OLED_WriteCmd(0x3F); // 1/64 duty OLED_WriteCmd(0xD3); OLED_WriteCmd(0x00); // 显示偏移 0 OLED_WriteCmd(0x40); // 显示起始行 0 OLED_WriteCmd(0x8D); OLED_WriteCmd(0x14); // 开启电荷泵 OLED_WriteCmd(0x20); OLED_WriteCmd(0x00); // 水平地址模式 OLED_WriteCmd(0xA1); // 段重映射 OLED_WriteCmd(0xC8); // COM扫描方向 OLED_WriteCmd(0xDA); OLED_WriteCmd(0x12); // COM引脚硬件配置 OLED_WriteCmd(0x81); OLED_WriteCmd(0x7F); // 对比度 OLED_WriteCmd(0xD9); OLED_WriteCmd(0xF1); // 预充电周期 OLED_WriteCmd(0xDB); OLED_WriteCmd(0x40); // VCOMH 稳压器 OLED_WriteCmd(0xAF); // 打开显示 }初始化时还有一件事容易踩坑如果屏幕显示内容是镜像的问题出在0xA1和0xC8这两个命令上。0xA1是段重映射0xC8是COM扫描方向这两个命令控制屏幕的左右和上下方向。如果你做的产品没有严格要求显示方向保持默认即可不需要自己去调。2.3 一个干净的HAL库驱动骨架驱动代码的核心就两个函数写命令和写数据。I2C通信时SSD1306通过控制字节来区分后续字节是命令还是数据——控制字节0x00表示命令0x40表示数据。// I2C 写命令 static void OLED_WriteCmd(uint8_t cmd) { uint8_t buf[2] {0x00, cmd}; // 控制字命令 HAL_I2C_Master_Transmit(hi2c1, 0x78, buf, 2, 100); } // I2C 写数据 static void OLED_WriteData(uint8_t data) { uint8_t buf[2] {0x40, data}; // 控制字数据 HAL_I2C_Master_Transmit(hi2c1, 0x78, buf, 2, 100); }实际显示内容时我习惯先在内存里维护一个1024字节的显示缓冲区OLED_Buf所有绘制操作都在缓冲区内完成最后一次性把整个缓冲区刷到屏幕。这样做的好处是避免频繁的单字节写入整屏刷新也就一次I2C传输效率高很多也不容易出现闪烁。uint8_t OLED_Buf[1024]; // 128 * 64 / 8 void OLED_Refresh(void) { OLED_WriteCmd(0x21); OLED_WriteCmd(0); OLED_WriteCmd(127); // 列地址范围 OLED_WriteCmd(0x22); OLED_WriteCmd(0); OLED_WriteCmd(7); // 页地址范围 HAL_I2C_Mem_Write(hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT, OLED_Buf, 1024, 100); }写显存用的是HAL_I2C_Mem_Write相比逐字节发送命令直接把整个缓冲区写入。起始地址0x40是数据模式地址一次最多能写1024字节正好是整屏内容。3. 水平滚动、垂直水平滚动的完整实现3.1 水平滚动寄存器配置与参数计算水平滚动是SSD1306最基础的硬件滚动能力命令只有两组0x26表示向右水平滚动0x27表示向左水平滚动。配置时需要依次发送起始页、帧频率、结束页、水平滚动偏移、结束列等参数。void OLED_HScroll(uint8_t dir, uint8_t startPage, uint8_t endPage, uint8_t freq, uint8_t offset) { OLED_WriteCmd(0x2E); // 必须先停止已有滚动 OLED_WriteCmd(dir); // 0x26右移 / 0x27左移 OLED_WriteCmd(0x00); // 固定保留字节 OLED_WriteCmd(startPage); // 起始页 Page0 OLED_WriteCmd(freq); // 帧频率参数 OLED_WriteCmd(endPage); // 结束页 Page7 OLED_WriteCmd(0x00); // 水平滚动偏移 OLED_WriteCmd(0x7F); // 结束列 127 OLED_WriteCmd(0x2F); // 启动滚动 }滚动前必须先发0x2E停止滚动即使之前没有滚动也建议发一次这是修改滚动参数的安全前提。起始页和结束页这里如果设置成0x00和0x07就是整屏8页都参与滚动。水平滚动偏移这个参数在水平滚动模式下必须填0结束列填0x7F代表滚到屏幕最右端。3.2 垂直水平滚动容易翻车的A3和垂直偏移垂直水平滚动比单纯水平滚动复杂一点对应命令是0x29垂直向右水平滚动和0x2A垂直向左水平滚动。从效果上看内容会一边水平移动一边垂直移动形成斜向滚动。完整配置分两步先设置垂直滚动区域再设置滚动方向和参数。void OLED_VHScroll(uint8_t dir, uint8_t startPage, uint8_t endPage, uint8_t freq, uint8_t vOffset) { OLED_WriteCmd(0x2E); // 停止现有滚动 // 第1步设置垂直滚动区域 OLED_WriteCmd(0xA3); // 垂直滚动区域命令 OLED_WriteCmd(0x00); // 区域起始行 0 OLED_WriteCmd(0x40); // 区域高度 64 行 // 第2步垂直水平滚动设置 OLED_WriteCmd(dir); // 0x29 向右上? 0x2A 向左上? OLED_WriteCmd(0x00); // 保留字节 0 OLED_WriteCmd(startPage); // 起始页 OLED_WriteCmd(freq); // 帧频率 OLED_WriteCmd(endPage); // 结束页 OLED_WriteCmd(vOffset); // 垂直滚动偏移 // 第3步启动滚动 OLED_WriteCmd(0x2F); }这里有个高频翻车点0xA3设置的是“垂直滚动区域”参数是起始行和区域高度而0x29命令序列里的最后一个字节是“垂直滚动偏移”参数是0到63的偏移量。两者是完全不同的东西。A3决定了哪些行范围允许垂直移动最后一个字节决定了每次滚动在垂直方向移动多少行。很多教程混在一起讲导致新手配置出来不是滚不动就是乱滚。我自己实测下来偏移量设2到4效果比较自然设太大会出现内容跳变感。3.3 滚动速度怎么选帧频率映射表滚动命令里的freq参数并不直接是“每秒多少步”而是一个索引值对应的是“每隔多少帧移动一步”。SSD1306内部显示帧率由初始化时的0xD5命令决定典型值在60Hz左右。我可以把常用配置对应成直观速度freq值每步间隔帧数约每秒移动步数0x072约30步/秒0x043约20步/秒0x054约15步/秒0x005约12步/秒0x0625约2.4步/秒0x0164约0.9步/秒0x02128约0.5步/秒0x03256约0.2步/秒做信息展示时我一般选0x00到0x06之间太快看不清文字太慢又感觉卡顿。选好频率后整个滚动过程不需要CPU干预非常适合做待机轮播界面。3.4 什么时候该放弃硬件滚动改用软件方式硬件滚动看着省心但有三个天生的限制第一参与滚动的区域必须是完整的页8像素行的整数倍做不到精确到单行第二硬件滚动期间写入显存内容会导致花屏必须停滚、改内容、再启滚第三滚动方向和速度只能按照寄存器支持的组合来。当你的需求是“某一行字符单独滚动”或者“滚动的同时还要更新其他区域内容”时就得用软件滚动。软件滚动的思路是把显示缓冲区里的数据自己移动然后整屏刷新。下面是一个简单的整页垂直滚动示例void OLED_SoftwareScrollByPage(uint8_t startPage, uint8_t endPage, int8_t direction) { if (direction 0) { // 向下滚动从结束页开始把上一页数据搬到下一页 for (int p endPage; p startPage; p--) { memcpy(OLED_Buf[p * 128], OLED_Buf[(p - 1) * 128], 128); } memset(OLED_Buf[startPage * 128], 0, 128); } else { // 向上滚动从起始页开始把下一页数据搬到上一页 for (int p startPage; p endPage; p) { memcpy(OLED_Buf[p * 128], OLED_Buf[(p 1) * 128], 128); } memset(OLED_Buf[endPage * 128], 0, 128); } OLED_Refresh(); }这样移动的粒度是8个像素适合文字行整体上下滚动。如果需要按单像素滚动就得把相邻两页的数据做移位拼接逻辑会复杂一些但原理是一样的。4. 完整示例程序结构与实测过程4.1 工程结构与函数分工这个示例工程基于STM32CubeMX生成外设包括I2C1、GPIO和Systick代码分三层组织main.c系统时钟、外设初始化、主循环流程oled.c / oled.hOLED驱动、显存缓冲区、初始化、刷新、字符串显示、滚动命令封装字库文件ASCII字符和常用汉字的点阵数据使用HAL库的好处是不用纠结寄存器底层I2C、GPIO这些都可以在CubeMX里图形化配置。生成工程后把oled.c和oled.h加入项目即可不需要改动HAL库本身。4.2 关键代码演示滚动内容更新下面的main函数演示了完整流程先显示固定内容两秒后启动水平滚动五秒后停止并切换到垂直水平滚动。int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); OLED_Init(); OLED_Clear(); OLED_ShowString(0, 0, Hello OLED); OLED_ShowString(0, 2, STM32F103VE); OLED_Refresh(); HAL_Delay(2000); // 水平左移滚动2帧间隔速度较快 OLED_HScroll(0x27, 0, 7, 0x07, 0); HAL_Delay(5000); // 停止滚动重新显示内容然后启动垂直水平滚动 OLED_ScrollStop(); OLED_Clear(); OLED_ShowString(0, 1, VH Scroll Demo); OLED_Refresh(); OLED_VHScroll(0x29, 0, 7, 0x00, 2); while (1) { } }OLED_ShowString函数本质上是把字模数据拷贝到OLED_Buf中对应位置最后调用OLED_Refresh统一上屏。修改显示内容时要先调用OLED_ScrollStop停止滚动否则会出现内容更新和滚动引擎互相干扰导致的花屏。4.3 实测现象与滚动玩法扩展实际跑起来水平滚动时整屏内容会向左循环移动边界处内容从左端重新进入视觉上看就是一条循环滚动字幕。垂直水平滚动时内容边水平移动边垂直偏移形成斜向漂移效果。如果你需要“来回滚动”而不是“循环滚动”硬件滚动做不到需要停滚、改方向、再启动或者直接用软件滚动实现。这个示例后期可以扩展成很多实用功能。比如做一个指针式电子钟时间区域静态显示底部温度区域用水平滚动轮播或者把多条待办信息全部写入显存缓冲区用水平滚动逐条播放。OLED加上硬件滚动后这种轮播功能对CPU的占用几乎为零很适合低功耗待机界面。5. 常见问题与排查心得5.1 常见问题速查表现象可能原因处理方法OLED接上电源不亮SSD1306默认关闭显示初始化时发送0xAF开启显示屏幕有噪点或显示异常电荷泵未开启确认发送0x8D、0x14滚动没反应缺少0x2F启动命令或页范围不对检查配置序列确认最后发送0x2F滚动后花屏滚动状态下写入显存先发0x2E停止再更新内容垂直水平滚动方向不对0xA3和垂直偏移理解错误重新确认A3区域和垂直偏移参数I2C通信不稳定时钟速率过高、接线过长降I2C速率到100kHz缩短杜邦线硬件I2C卡死没有超时或总线错误恢复HAL库增加超时重试或改用模拟I2C烧录时提示device not found调试接口连接问题检查SWD接线、BOOT0电平按住复位再下载汉字显示乱码取模顺序和显示函数不匹配取模软件选阴码逐列式逆向中断里刷屏导致卡顿阻塞式I2C传输占用太长中断只置标志主循环里处理刷新5.2 我在调试中踩过的几个坑第一个坑是改滚动参数前忘记发0x2E。这个看起来小但后果很严重——屏幕上会出现随机花屏而且不是每次都复现排查起来非常痛苦。现在的习惯是封装一个OLED_ScrollStop函数凡是改滚动配置第一句永远是停止滚动。第二个坑是垂直水平滚动的A3和偏移量的关系。我第一次做的时候只发了0x29系列命令没设置A3结果屏幕纹丝不动排查了半天才发现漏了垂直滚动区域的设置。后来我把垂直水平滚动的配置步骤写成了注释模板每次对照着填参数再也没错过。第三个坑是关于中断里刷新屏幕。I2C传输即使只有1024字节在100kHz速率下也要近百毫秒如果在中断里调用阻塞式传输整个系统的实时性都会被拖垮。需要实时响应的事件我用标志位加主循环轮询的方式来处理中断里只置标志主循环检测到标志再刷新显示。最后分享一个我自己的习惯所有滚动相关操作我都封装成独立函数并统一提供OLED_ScrollStop入口因为调试中很大一部分花屏问题都来自滚动状态没清理干净。如果你也准备把滚动功能用到自己的产品上建议先跑通水平滚动再尝试垂直水平滚动每次只改一个参数方便定位问题。OLED滚动这事情原理搞清楚之后剩下的就是耐心调参了。本文还有配套的精品资源点击获取