简介:一份面向嵌入式开发者的OLED12864驱动例程合集,覆盖多种常见单片机平台,帮助解决不同硬件环境下屏幕驱动和功能移植的难题。压缩包内含890个文件,以C源文件、头文件、工程配置文件及hex固件为主,同时包含.o等编译中间文件与lst列表文件,整体约11.8MB,既有完整程序工程,也提供了便于对比验证的构建中间产物。目前已有757人学习下载。这些例程均采用软件模拟SPI方式与屏幕通信,不依赖硬件SPI外设,通过普通GPIO引脚模拟时钟和数据线时序,并可根据实际接线修改宏定义的引脚映射,尤其适合资源受限或非标准引脚的MCU。阅读代码可以学习OLED初始化、绘图、文本显示等基本操作,掌握针对具体单片机型号调整宏定义、重映射引脚的方法;对于理解SPI协议、MCU外设配置也有直接的实践参考价值。
1. OLED12864多MCU例程:一套驱动代码打通五种单片机的显示方案
我以前做某设备改版,需要把一块OLED12864小屏接到一颗国产M0内核芯片上,原以为照着控制器手册配一下寄存器就行,结果连点三个晚上才弄明白:屏不亮不是硬件问题,而是初始化命令的先后顺序错了。那段时间我把各种型号的单片机例程都翻了一遍,拼拼凑凑总算跑通,也攒下不少血泪经验。这份 OLED12864 各种单片机例程,要解决的正是这种“换一颗芯片就抓瞎”的移植问题。它把常用的显示驱动按芯片分类打包,每个目录下都是能直接编译烧录的工程,从初始化、画点、画线到显示字符和汉字都齐了。适合手上有OLED屏需要快速点亮的人,尤其是做嵌入式开发、毕业设计或小批产品验证的同学,省去从底层时序开始啃手册的时间。
2. 例程全貌:目录结构、通信方式与移植分层的设计逻辑
拿到资源第一步不是看代码,而是看目录怎么规划的。这套例程的目录组织方式,决定了你把它往自己工程里搬的时候要动多少刀。我按实际经验拆开讲。
2.1 例程覆盖的芯片类型与移植粒度
标题里说的“各种单片机”并不是同一套代码改改引脚那么简单。资源按芯片内核和厂商分了多个子目录,覆盖了常见的三种架构:传统8位51内核芯片、基于Cortex-M0和M3内核的主流ARM芯片、以及最近很热的RISC-V内核芯片。每一类目录里又按开发板或最小系统板的名字建了子目录,例程的工程文件就是针对那块板子的引脚定义好的,拿到手上电就能试。
移植粒度分三层:第一层是底层驱动,也就是 I2C 或者 SPI 的读写函数,换成新芯片时主要改这里;第二层是屏幕控制层,包括初始化、显存刷新、设置坐标这些,和硬件基本无关,代码可以原样拷贝;第三层是应用层,比如显示字符串、图片、菜单动画,这部分直接调第二层的接口就够了。这种分层思路不是所有例程都有,很多网上的“OLED例程”都是单一文件全部写死,换芯片等于重写。这套例子里至少做到了驱动和应用分开,二次开发时不至于翻车。
2.2 目录结构与文件职责
我打开资源看了一遍,大致整理出实际项目里常见的一种目录布局,这也是多数硬件例程包组织文件的标准方式。每个芯片型号目录下,都包含工程文件、核心驱动源文件、字库文件、以及一份引脚接线说明。以其中一个STM32类芯片目录为例,结构大概是这样:
oled_12864/ ├── mcu_m0_demo/ # M0内核芯片例程 │ ├── Hardware/ │ │ ├── oled.h # 屏幕驱动头文件,接口与宏定义 │ │ ├── oled.c # 初始化、显存、画点、字符显示 │ │ ├── font.h # ASCII码字库,含常用汉字点阵数据 │ │ └── bsp_i2c.c # 硬件I2C底层,引脚配置在这里 │ ├── MDK-ARM/ # MDK工程文件所在目录 │ └── User/ │ └── main.c # 例程入口,演示菜单和图片轮播 ├── mcu_51_demo/ # 8位51内核芯片例程 │ └── src/ │ ├── soft_i2c.c # 51上没有硬件I2C,用GPIO模拟 │ └── oled.c # 与上层共用的屏幕驱动 ├── mcu_riscv_demo/ # RISC-V内核芯片例程 └── docs/ └── wiring_guide.md # 各种芯片与屏幕的接线对照说明可以看到,两个芯片目录的 oled.c 内容几乎一致,区别只在底层 I2C 部分。这就是分层的价值:屏幕驱动代码只和控制器打交道,不关心控制器的 I2C 是硬件外设还是软件模拟。移植时只需要把 bsp_i2c.c 换成新芯片的实现,再检查一下引脚宏定义。docs 目录里的接线说明也很关键,不同芯片的复位引脚接法有差异,照着文档连线能少踩一半的坑。
2.3 驱动 IC 的兼容性:同一协议,不同初始化细节
OLED12864 屏体本身是同一类规格,但背后的控制 IC 有兼容差异,这一点例程里特别标注了。市面上流通的屏常见两种驱动方案,出厂时某些寄存器默认值不一样,初始化命令的偏移量也略有不同:一种方案按每页 8 像素扫描,另一种方案在显示起始行和段重映射上需要额外配置。如果拿 A 方案屏的初始化代码去跑 B 方案的屏,现象通常是“能亮但内容整体错位”,比如上半屏的内容显示到了下半屏。
例程里在初始化序列部分做了条件编译开关,通过一个宏来选择对应驱动 IC,并且注释里写了如何用放大镜观察屏幕 IC 封装上的丝印来判断屏属于哪一类。这类兼容性问题在网上经常被当作“花屏”提问,但其实原因与刷屏率、接线都没关系,纯粹是初始化命令不匹配。建议拿到屏先做一件事:看丝印,确认驱动 IC 版本,再打开例程里对应的预处理宏。
2.4 I2C 与 SPI 的取舍,以及为什么默认推荐 I2C
例程里大部分芯片默认使用 I2C 通信,原因不只是节省引脚。OLED12864 的 I2C 模式只需要两根线(SCL 时钟线、SDA 数据线),如果放在一个引脚紧张的项目里,还能把这组信号复用在其他芯片的软件模拟口上。而 SPI 模式通常要占用 4 根线,外加数据/命令切换线 DC,虽然带宽高适合刷视频动画,但对大多数显示静态菜单的应用来说性能过剩。
当然 I2C 也不是没有代价。I2C 是开漏结构,需要外部上拉电阻,有些现成的 OLED 模块板上已经焊好上拉,直接接就行。如果自己单独买裸屏,就得在 SCL 和 SDA 上加 4.7kΩ 左右的上拉电阻到 VCC,否则通信不稳定,表现为偶发性显示缺笔画。例程里对每种芯片都给了引脚接线表,但上拉这个硬件细节多数新人容易忽略,我在后面避坑章节还会展开说。
3. 核心驱动拆解:从 I2C 时序到画点画字的完整链路
这一章直接进代码。我挑了一套例程里最典型的 M0 内核芯片例程来讲,因为它的工程结构比较干净。理解三层结构之后,另一颗芯片的代码就只是硬件函数换个体积的问题。
3.1 底层 I2C 通信:起始、停止和 ACK 的时序逻辑
OLED12864 在 I2C 模式下,通信帧结构很简单:一个起始信号、一个设备地址字节、一个控制字节表示后续是命令还是数据,然后是内容字节。例程的 bsp_i2c.c 里实现了最核心的几个静态函数,关键代码逻辑如下:
// bsp_i2c.c —— 以 GPIO 模拟 I2C 为例,适配任意芯片 #define OLED_SCL_PIN GPIO_PIN_6 // 时钟线,自行改成所用引脚 #define OLED_SDA_PIN GPIO_PIN_7 // 数据线 #define SCL_H() HAL_GPIO_WritePin(GPIOB, OLED_SCL_PIN, GPIO_PIN_SET) #define SCL_L() HAL_GPIO_WritePin(GPIOB, OLED_SCL_PIN, GPIO_PIN_RESET) #define SDA_H() HAL_GPIO_WritePin(GPIOB, OLED_SDA_PIN, GPIO_PIN_SET) #define SDA_L() HAL_GPIO_WritePin(GPIOB, OLED_SDA_PIN, GPIO_PIN_RESET) static void i2c_start(void) { SDA_H(); SCL_H(); SDA_L(); // SCL 为高时 SDA 由高变低,产生起始条件 SCL_L(); } static void i2c_stop(void) { SDA_L(); SCL_H(); SDA_H(); // SCL 为高时 SDA 由低变高,产生停止条件 } static void i2c_write_byte(uint8_t dat) { for (uint8_t i = 0; i < 8; i++) { if (dat & 0x80) SDA_H(); else SDA_L(); dat <<= 1; SCL_H(); // 拉高 SCL,从机在上升沿采样数据 SCL_L(); } SDA_H(); // 释放 SDA,等待从机 ACK SCL_H(); // 这里可以读取 SDA 判断 ACK,例程简化处理 SCL_L(); }这段代码最核心的点是“SCL 高电平期间 SDA 不能变化”这个 I2C 铁律。起始、停止条件都发生在 SCL 为高的时刻,而数据传输则要求在 SCL 上升沿之前先把数据摆到 SDA 上。很多新人写的模拟 I2C 会在发送数据位时同时翻转 SCL 和 SDA,导致从机采到乱码,看起来就是屏幕随机花屏。 例程里还有一个细节:每写完一个字节后,要把 SDA 释放为高,然后拉高 SCL 再拉低,这一步就是等待从机的 ACK。严谨的工程做法会在这一时刻读取 SDA 电平,低电平表示从机应答成功。例程为了精简直接忽略了 ACK 判断,这在屏线较短、上拉正常的场景下问题不大,但如果你的项目要量产,建议在这里补上 ACK 超时检测。
3.2 初始化序列:为什么命令必须按这个顺序写
拿到一块新 OLED12864,我一般先把例程里的初始化函数原封不动跑通,再考虑优化。初始化其实是一串固定顺序的寄存器配置命令,顺序不对或者命令间隔不够,都会导致屏幕无法正常点亮。例程中的初始化核心部分如下:
// oled.c —— 屏幕初始化,命令按数据手册要求顺序写入 void oled_init(void) { hw_delay_ms(100); // 上电稳定,等电源和内部复位完成 oled_write_cmd(0xAE); // 关闭显示,配置期间防止画面乱跳 oled_write_cmd(0xD5); oled_write_cmd(0x80); // 设置时钟分频 oled_write_cmd(0xA8); oled_write_cmd(0x3F); // 设置行列数 64 行 oled_write_cmd(0xD3); oled_write_cmd(0x00); // 显示偏移 0 oled_write_cmd(0x40); // 起始行地址 0 oled_write_cmd(0x8D); oled_write_cmd(0x14); // 开启内部电荷泵,必须打开,否则屏不亮 oled_write_cmd(0x20); oled_write_cmd(0x00); // 设置内存寻址模式为水平 oled_write_cmd(0xA1); // 段重映射,决定左右是否镜像 oled_write_cmd(0xC8); // 上下扫描方向,决定画面是否倒置 oled_write_cmd(0xAF); // 开启显示,此时屏幕应当点亮 }几个容易踩的坑我按经验标一下:第三条命令“打开内部电荷泵”是这类屏幕显示的关键,很多移植失败的根因就是漏掉它,或者把它写到了 0xAF 之后。电荷泵要等屏幕供电稳定后尽快开启,拖太久内部电容充不上电就会白屏。另外要注意例程里每两个命令之间没有额外延时,因为 I2C 本身的速度足够慢,命令间隔满足寄存器建立时间要求;如果你把 I2C 提高到 1MHz 以上,就得在命令之间主动插入微秒级延时,否则会出现偶发性初始化失败。注释里提到的段重映射和上下扫描方向,决定了你的画面是否左右镜像或上下颠倒,改装到特殊安装角度的屏幕上时,改这两条比改代码取模重做要划算得多。
3.3 显存缓冲与画点函数:一次刷新要传多少数据
OLED12864 的分辨率是 128 像素 × 64 像素,控制 IC 内部显存按 8 像素一页组织,一共 8 页,每页 128 字节。整个显存大小为 128×64/8 字节,正好 1KB。例程在 oled.c 里维护了一个和屏幕显存一一对应的本地缓冲数组,画点、画线、写字符都先改这个数组,最后统一刷到屏幕上。核心代码如下:
// oled.c —— 全屏缓冲与画点函数 uint8_t framebuffer[128 * 64 / 8]; // 本地显存,按页排列 // x:0~127,y:0~63,on=1 点亮,on=0 熄灭 void oled_set_pixel(uint8_t x, uint8_t y, uint8_t on) { uint16_t page_index = (y / 8) * 128 + x; // 先定位到哪一页 uint8_t bit_mask = 1 << (y % 8); // 再定位到页内第几位 if (on) { framebuffer[page_index] |= bit_mask; // 置位:点亮 } else { framebuffer[page_index] &= ~bit_mask; // 清位:熄灭 } } // 把本地显存全部推送至屏幕 void oled_refresh(void) { for (uint8_t page = 0; page < 8; page++) { oled_write_cmd(0xB0 + page); // 设置页地址 0~7 oled_write_cmd(0x00); // 设置列地址低 4 位 oled_write_cmd(0x10); // 设置列地址高 4 位 for (uint8_t col = 0; col < 128; col++) { oled_write_data(framebuffer[page * 128 + col]); } } }这段代码的设计思路是典型的“先画后刷”:任何图形操作都直接改 framebuffer,最后统一刷新,避免了边画边刷导致的屏幕闪烁。 我特别想说一下 page_index 的计算,这是新人容易写错的地方。OLED 控制 IC 的显存布局是横向按列扫描的,同一页内 8 个 bit 对应屏幕上一列中的 8 行像素,而不是常规二维数组的按行排列。如果理解成普通矩阵就会导致画出来的点位置错乱。例程的公式等于先把 y 折叠到页内行号,再乘 128 跳到正确页,最后加 x 定位到列。这个思路清楚后,后面做圆形、波形图都顺手。
3.4 字符显示与汉字取模:字体数据结构怎么定义
字符显示是应用层最常用的功能。例程的 font.h 里存放了两套字库:一套 5×8 ASCII 字符集,一套 16×16 中文常见字。字符显示的代码实现把每个字符当成一个矩形点阵,逐列取出字节写入 framebuffer。例如 ASCII 字符的绘制逻辑:
// oled.c —— 显示一个 ASCII 字符,font_5x8[] 定义在 font.h void oled_show_char(uint8_t x, uint8_t y, char c) { uint8_t idx = c - ' '; // 字体表从空格字符开始排列 for (uint8_t col = 0; col < 5; col++) { uint8_t line = font_5x8[idx][col]; // 每一列对应一个字节 for (uint8_t row = 0; row < 8; row++) { if (line & (1 << row)) { oled_set_pixel(x + col, y + row, 1); } else { oled_set_pixel(x + col, y + row, 0); } } } oled_refresh(); }这段代码看起来简单,但它隐含了一个关键设计:字符字体是“列扫描”还是“行扫描”格式,必须和你前面画点函数的坐标方向一一对应。 例程里的字体数据采用列扫描,一列 8 个像素压缩成一个字节,低位在上。网上常见的取模软件默认输出的是行扫描格式,直接贴进来会导致字符整体旋转 90 度。如果你换成自己的字库,务必先确认取模设置是“逐列式、低位在前”,或者在画点函数里做一次坐标转置。16×16 汉字的绘制逻辑基本相同,只是列数从 5 变成 16,每次要画两字节宽的点阵。例程里还附带了一个小工具目录,用 PC 端工具生成字模后替换 font.h 即可扩充字库。
4. 运行实战:从接线、编译到点亮屏幕的完整路径
代码解构完,接下来就是把例程跑起来。这一步看起来枯燥,但接线错误导致的硬件故障比代码 bug 更难查,所以我把操作步骤写细一点。
4.1 接线准备:引脚分配与上拉电阻
OLED12864 模块最常见引出 4 个引脚:VCC、GND、SCL、SDA,有些模块还有一个 RES 复位引脚。拿到例程先打开对应芯片目录下的接线文档,把宏定义的引脚和实际物理引脚对上。 我按一个 M0 芯片的例程列一份典型对照,注意不同芯片的引脚编号完全不同,下表只是说明逻辑关系:
| 屏幕模块引脚 | 功能 | 例程中映射位置 | 接线要点 |
|---|---|---|---|
| VCC | 电源正极 3.3V/5V | 板载 3.3V 或 5V 输出 | 确认屏模块电平,双电源供电时必须共地 |
| GND | 电源地 | GND 公共地 | 与单片机共地,否则 I2C 电平无参考 |
| SCL | 时钟线 | 例程宏定义对应引脚 | 模块已带上拉可直接连,裸屏需外接 4.7kΩ 上拉 |
| SDA | 数据线 | 例程宏定义对应引脚 | 同上,注意总线空闲时为高 |
| RES | 复位(可选) | 部分例程接到 GPIO | 悬空有可能导致初始化失败,建议接一个 GPIO 或直接接 VCC |
关于供电电压,多数 OLED12864 模块板载稳压,主控 3.3V 或 5V 都能用,但裸屏的控制 IC 逻辑电压一般 3.3V,接到 5V 的单片机 I/O 上时,稳妥做法还是加电平转换或者选择 5V 兼容的模块。 我见过不少例子是屏幕能亮但通信偶尔异常,最后查出来是 I/O 电平不匹配导致 SCL 高电平不够驱动。例程文档里对每块板卡都标注了建议供电方式,按那个接线就行。
4.2 打开工程与编译烧录
例程包里的工程文件是按芯片配套的 IDE 准备的。M0 和 M3 芯片的目录下是 MDK 工程,51 芯片目录下是 Keil C51 工程,RISC-V 目录下一般是对应的 GCC 工程。在 MDK 中打开工程后,第一步打开 bsp_i2c.c,确认引脚宏定义与你的实际接线一致,不要直接编译下载,因为例程作者用的是他那块板子的引脚定义,你的板子不一定相同。
编译前还需要检查两处晶振配置和延时函数:例程里的 hw_delay_ms 可能是基于系统主频的软件延时,如果工程默认主频不同,延时会成倍偏短或偏长。 我自己的习惯是把这些延时做成基于 SysTick 的版本,或者至少用一个全局的 tick 计数来保证跨平台一致性。编译中常见的报错也集中在头文件路径上,MDK 工程如果提示找不到 oled.h,在 C/C++ 编译选项里的 Include Paths 中把 Hardware 目录加进去就行。烧录用 IDE 自带的下载器即可,注意芯片型号选择要和板子实际型号一致,否则下载器能识别但烧完程序跑不起来。
4.3 点亮验证:从白屏到图形的判断标准
烧录成功后,屏幕上电默认可能是白屏或满屏雪花点,这都正常,因为控制器刚上电没有收到任何初始化命令。按一下复位键,或者把程序重新跑一遍,正常情况下屏幕应该在几百毫秒内亮起来,显示例程的初始界面。 我判断屏幕是否真正工作的标准有三条:背景均匀、没有残留拖影;刷新时无明显闪烁;显示字符的笔画完整、位置不偏移。 如果屏幕亮了但显示内容错位,优先怀疑初始化里的段重映射和扫描方向命令被改动过。如果屏幕完全无反应,不进初始化函数,第一步应该检查接线顺序是否把 SCL 和 SDA 接反了,这是所有魔幻故障里概率最高的一条。
4.4 例程自带的演示功能测试
把例程烧进去后,不要着急改代码,先把演示程序完整跑一遍。例程的 main.c 里通常有一个循环演示序列:先依次点亮四个角的像素,从第一页刷到最后一页,然后显示整屏黑白反色,再展示 ASCII 字符串、汉字字符串和一张嵌入式小图。跑完这套流程,相当于对屏幕硬件、底层 I2C、显存刷新、字库数据做了全方位的体检。
比如“四角像素点亮”这一步,如果有一个角始终不亮,说明那页的显存地址映射有问题,或者控制 IC 的扫描范围与物理屏幕偏移存在差异。整屏反色如果出现色块残留,则要怀疑显存读回时的地址连续性或者 I2C 速率过高导致的丢字节。我在实际操作中会专门用逻辑分析仪挂在 SCL 上,抓一段刷新数据,确认发送命令地址 0x00 和数据地址 0x40 的切换是否正确。没有逻辑分析仪也没关系,把 I2C 速度降低一半重新跑一遍,如果故障消失,基本可以断定是速率问题。
5. 移植避坑:五个最容易翻车的显示问题与排查方法
这章是我最想写的部分。例程虽然能跑,但移植到自己的板子上时,硬件环境一变,问题就出来了。我整理了五条出现频率最高的踩坑记录,每一条都是实际遇到过的。
5.1 现象:上电后屏幕完全不亮,背光也没有
这个现象最常见的原因是供电和复位顺序问题。我在一个项目里遇到过一次,屏幕背光都正常,但内容怎么都不出来,万用表一量才发现 RES 引脚被悬空了。这种屏幕的控制 IC 内部有上电复位电路,但悬空的 RES 容易受到干扰,导致状态机卡在未知状态。
解决方法是把 RES 引脚接到一个 GPIO 上,并在初始化函数最开始执行一次硬件复位:拉低 10ms、拉高 20ms,然后再发送初始化命令。例程里的 oled_init 开头有 100ms 延时,就是为了等上电稳定。如果你用的模块没有引出 RES,直接跳过这一步,但要确保 VCC 供电纹波不要太大,否则会影响内部复位电路判断。
5.2 现象:屏幕能亮,但显示内容整体错位或上下颠倒
这个现象通常不是通信问题,而是初始化里的扫描方向配置与屏幕硬件不匹配。原因在于不同厂商的屏封装工艺不同,有些屏的物理第一列对应控制 IC 显存最后一列,有些屏的物理第一行对应显存最后一行。例程里默认按标准方向配置,换了贴片方向或者使用不同供应商的屏时就会错位。
解决方法是修改初始化序列里的段重映射和上下扫描方向这两条命令,分别改成相反的值并重新编译测试。我把这称为“翻面调试法”:先改一个方向的参数,看画面是否整体镜像,再改另一个方向。在例程的代码注释里,这两条命令都标注了默认值和相反值,照着切换就行,完全不需要改上层绘图代码。
5.3 现象:屏幕显示雪花噪点,初始化后部分像素随机亮
这个现象的特征是屏幕能亮,但亮点随机分布,看起来像有人在撒雪花。我排查过多次,最常见原因是 I2C 总线缺少上拉电阻,导致数据线上的电平建立时间不够,从机采到了错误的 bit 位。 我用示波器看过这种场景下的 SDA 波形,上升沿非常平缓,高电平幅度勉强达到阈值,判决结果就变得随机。
解决方法是检查 SCL 和 SDA 两根线上的上拉电阻。裸屏模块没有板载上拉时,自己加上 4.7kΩ 电阻到 VCC 即可。另外一个容易被忽略的原因是线缆过长,超过 20cm 的杜邦线在 400kHz I2C 速率下就有信号完整性问题,降速到 100kHz 能临时缓解,但根治还是缩短线缆并加上拉。
5.4 现象:字符显示正常,但汉字区域完全错乱
这个现象和通信无关,是字库取模数据不匹配。例程自带的汉字取模是 16×16 点阵,逐列排列低位在前,但如果你用 PC 端取模软件重新生成了汉字,默认设置可能是逐行排列,甚至每个字节的数据顺序是高位在前。 取模设置不同,显示出来的汉字就完全不是字形,而是乱七八糟的点状图案。
解决方法是把我前面讲过的取模参数核对一遍:取模方式选逐列式,每字 16×16,字节内低位在前、高位在后,每行两个字节,共 16 行。例程的 font.h 末尾附了一段注释,写明了取模软件的推荐设置,生成后覆盖原数组即可。还有一个懒人办法:不自己取模,直接调用例程自带的字库数组,只是覆盖的字符集有限。
5.5 现象:画面偶尔闪一下或整屏一抖
这个现象最隐蔽。我遇到一次是屏幕每次刷新到某一页时,画面轻微闪动,频率不高但非常影响体验。排查到最后发现是刷新函数里命令序列没有闭合:写完最后一页数据后,没有把列地址指针重置。控制 IC 的显存地址指针在写完 128 列后停在末尾,下一次刷新又从页 0 开始写,但列指针还停在第 127 列,导致第一页的前几个字节写到了错误的位置。
解决方法是保证刷新函数每次都完整地设置页地址和列地址,例程里的 oled_refresh 已经这样做了。如果你自己写刷新逻辑,注意在每次循环开头都要重新发送列地址的低 4 位和高 4 位命令,不能依赖上一次的状态。另外,刷新整屏是 1KB 数据,在 I2C 400kHz 速率下大约需要 30ms,如果这个过程中被中断打扰,也会造成撕裂感。把刷新放在一个不被打断的临界区内,或者用 DMA 传输,能彻底消除这类闪动。
6. 二次开发:把这套例程改造成自己的轻量显示框架
例程跑通只是第一步,把它改成真正属于自己的显示框架,才算榨干了这份资源的价值。我拿到这类例程后,习惯先做一次结构整理:把 bsp_i2c.c 里与芯片寄存器直接相关的函数封装成一组接口,命名为 i2c_init、i2c_write_cmd、i2c_write_data;中间层 oled.c 只调用这组接口,不直接操作 GPIO。这样以后换芯片,只需要重写十几个函数的实现即可。我的做法是建一个display_port.h文件,声明这些接口,同时在上面标注好实现者需要提供的底层依赖,例如“需要提供 1ms 精度的毫秒延时函数”。
画点和刷新函数不要动,它们的通用性很强,唯一的个性化需求是提高刷新效率。比如在刷新函数里我通常把页面循环改成只刷“脏行”:在上层代码里维护一个 dirty_page 标记,任何画点操作都会把对应页的标记置位,刷新时只把标记为脏的页发送出去,而不是整屏推一遍。这个优化对静态菜单特别有效,刷新时间能从 30ms 降到 5ms 左右。要做到这一点,只需要把 oled_set_pixel 里的页号记录下来,然后修改 oled_refresh 函数,让它遍历 8 个页号并跳过未标记的页。
字体系统是另一个值得改造的模块。例程的 font_5x8 只支持 ASCII,如果产品需要中英文混排,我会在应用层建立一张 Unicode 映射表,把 16×16 的汉字按 GB2312 编码索引,再写一个简单的字符宽度计算函数,实现自动换行。这些应用层的改造完全可以建立在例程现有的 oled_show_char 之上,不会侵入底层代码。移植前我给自己定的验收标准是这样一句话:换一块不同控制芯片的屏,只修改 bsp_i2c.c 和一个初始化参数宏,不碰任何上层调用代码。从那以后,我每做一个带屏幕的项目都强制走一遍这个过程,先把例程按三层拆好、把底层接口列出来,再开始写业务逻辑。经验告诉我,这一步多花半小时,后面换屏、换芯片能省一整天。希望帮到你。
本文还有配套的精品资源,点击获取