简介:本资源是一套面向嵌入式初学者与8051开发者的0.96英寸OLED显示屏驱动实践资料,聚焦C51单片机通过I²C协议控制SSD1306/SH1106等主流OLED驱动芯片的核心技术,解决显示初始化、命令/数据切换、像素缓冲管理及I²C时序精准控制等典型问题。压缩包含123个文件(960KB),以100个C源文件为主,涵盖底层I²C模拟驱动、U8glib图形库适配代码(如u8g_dev_ssd1351_128x128.c、u8g_ll_api.c等)、字体数据与设备驱动模块;辅以16个Arduino平台INO例程便于对比学习,3个说明文档与2个头文件提供接口定义与配置参考。已有263人学习下载,资源结构完整、模块解耦清晰,可直接移植到Keil C51环境,配套代码包含逐行注释的初始化流程、I²C起停信号实现、显示缓冲区刷新逻辑及常见显示效果(文本、图形、动画)示例,是掌握嵌入式显示驱动与I²C底层通信的实用入门套件。 Arduino 相关的社群里,每隔几天就会有人发同一个问题:“我的 0.96 寸 OLED 屏白屏了,I2C 扫描也扫不到地址,是不是屏坏了?”
说实话,这块屏我从 Arduino Uno 玩到 ESP8266,又从 51 单片机折腾到 STM32,整套链路早已摸透。大多数所谓的“坏屏”,根本不是屏的问题,而是接线、地址、时序、驱动库这几个环节没对齐。
简单说,这块 0.96 寸 OLED 用的是 SSD1306 控制器,通过 I2C(也叫 IIC)接口与主控通信。它的魅力在于:128x64 分辨率、四根引脚(VCC、GND、SCL、SDA)、功耗低,能显示汉字、曲线和简单波形,比 1602 液晶省下一大把 IO,比 TFT 屏省电得多。这篇内容就是一套从零到一的实战例程笔记,覆盖 Arduino 上的接线、库选型、代码跑通、动态刷新,再延伸到 C51 软件 I2C、STM32 和 ESP32 的移植思路。不管你是刚接触 Arduino 的新手,还是想在 51 单片机上复用这块屏的老手,都建议完整看一遍,排障那部分尤其值回票价。
1. 选这块屏,赌的就是 I2C 这两根线
1.1 为什么不是 1602,也不是 TFT 屏
我做桌面小仪表、温湿度计、电压监测器这类项目时,最初用过 LCD1602。1602 的麻烦在于要占 6 根以上 IO 口,即便加了 PCF8574 转 I2C 模块,也只能显示字符,画不了曲线和进度条。后来换过 1.8 寸 TFT,彩色显示确实好看,但 SPI 或并行接口接线多、功耗大,Arduino Uno 这种 Flash 只有 32KB 的板子跑复杂 UI 很容易吃力,屏的价格也是 OLED 的两三倍。
0.96 寸 OLED 走的是另一种思路:I2C 只需要 SCL 和 SDA 两根线,整屏工作电流约 20 到 40mA,显示内容完全由主控写入。128x64 分辨率意味着按 8x8 像素的字符排列,能排 8 行 16 列,做小型仪表盘刚刚好。它最大的短板是尺寸小、单色显示,但 DIY 项目里这几项几乎不影响使用。需要提醒的是,OLED 模块有蓝色、白色、黄蓝双色之分,如果你买的是黄蓝双色屏(上 1/4 黄、下 3/4 蓝),程序里按全屏同一色处理,显示区域会出现颜色不一致的情况,选购时要考虑好。
1.2 模块版本和“四根脚”背后的坑
市面上 0.96 寸 OLED 几乎都用 SSD1306 控制器,但驱动板有两种:I2C 版和 SPI 版。I2C 版是 4 个引脚(GND、VCC、SCL、SDA),SPI 版则有 7 个引脚(GND、VCC、D0、D1、RES、DC、CS)。标题里写的是 IIC,但你下单时一定要确认到手的是四脚版本。我见过不少人买错,然后折腾半天“为什么程序编译不过”,其实硬件版本就不对。
另一个被忽视的细节是模块背面的 I2C 地址设定。SSD1306 的地址由 SA0 引脚决定,大多数模块默认是 0x3C,少数是 0x3D。如果你用某个例程没跑通,先别怀疑代码,看看模块背面有没有标注地址的跳线或电阻,直接把程序里的地址改成另一个再试。这个检查只要 30 秒,但能省下一整晚的排障时间。
2. 上电前的硬件检查:I2C 总线的“门牌号”和上下拉
2.1 四根线怎么接,不同主控引脚不一样
接线这件事,不能只记 Arduino Uno 的引脚,因为不同板子的 I2C 引脚位置完全不同。下面列一份我实测过的对照表:
| 主控板 | SDA | SCL | 逻辑电平 |
|---|---|---|---|
| Arduino Uno / Nano | A4 | A5 | 5V |
| Arduino Mega2560 | 20 | 21 | 5V |
| Arduino Leonardo | D2 | D3 | 5V |
| ESP8266 NodeMCU | D2 (GPIO4) | D1 (GPIO5) | 3.3V |
| ESP32 | GPIO21 | GPIO22 | 3.3V |
| STM32F103C8T6 | PB7 (I2C1) | PB6 (I2C1) | 3.3V |
| 51单片机 (AT89C52) | 任意IO口软件模拟 | 任意IO口软件模拟 | 5V |
Arduino Uno 的 A4、A5 本身就是硬件 I2C 引脚,直接接模块的 SDA、SCL 即可。如果你用的是 ESP8266 这类 3.3V 主控,要注意模块的供电电压:很多便宜模块板载了 3.3V 稳压芯片和电平转换电路,可以直接接 5V 或 3.3V,但不同批次会有差异。最稳妥的办法是看模块背面有没有丝印标注工作电压,或者用万用表量一下模块 VCC 引脚对 GND 的电容和稳压芯片型号。裸屏自己焊排针时,还要检查引脚有没有焊锡桥接,我遇到过两片屏因为焊接时 SCL 和 SDA 连锡,扫描地址永远报错。
2.2 I2C 地址为什么是 0x3C,在 C51 里又为什么是 0x78
I2C 是一种双线总线协议,所有设备挂在同一条 SCL 和 SDA 上,通过固定地址识别彼此。就像一栋楼的门牌号:主控是快递员,OLED 是其中一个房间。SSD1306 的 7 位地址默认是 0x3C,但 I2C 标准在总线上发送时,实际是一个字节:7 位地址左移 1 位,最低位表示读或写。写操作时地址字节是 0x78(0x3C << 1),读操作是 0x79。
这就是为什么你在 Arduino 例程里看到的是 0x3C,而在很多 C51 例程里看到的是 0x78。两个地址说的其实是同一颗芯片,只是表示方式不同。理解这一点,能帮你跨平台移植时心里有底,而不是看到 0x78 就以为例程写错了。
2.3 上拉电阻:单设备没问题,多设备就出鬼
I2C 总线是开漏结构,SCL 和 SDA 线必须靠上拉电阻拉到高电平。Arduino Uno 内部有大约 20kΩ 到 50kΩ 的上拉,单设备、短线缆时通常能工作,所以很多教程直接接线就点亮了。但当你把总线延长到 20cm 以上,或者同时挂了多个 I2C 设备(比如 OLED + BME280 + PCF8574 扩展 IO),信号边沿会变缓,出现“昨天能显示,今天白屏”的诡异问题。
更好用的做法是:在 SCL 和 SDA 上各接一个 4.7kΩ 电阻到 VCC。有的 OLED 模块出厂已经焊好了上拉电阻,有的没有。用万用表量一下 SCL 对 VCC 的阻值,接近 4.7kΩ 说明模块自带;如果量出来是几十千欧甚至无穷大,说明要靠主控内部上拉,多设备时就要外接。这个细节,是我在把 OLED 和舵机、电机驱动放一起时踩过最大的坑。
3. Arduino 环境配置和驱动库:选库比写代码更重要
3.1 板卡安装和那些“占用 C 盘”的问题
Arduino IDE 2.x 安装后,AVR 系列(Uno、Nano、Mega)默认就能用,不需要额外装板卡。但很多人为了用 ESP8266 或 ESP32,会去“开发板管理器”里在线下载板卡包。这里有个典型问题:板卡包下载地址在国外服务器,网络慢的时候经常失败,而且整个包默认放在用户目录下的 Arduino15 文件夹里,C 盘空间越占越多。
解决思路有三种:一是改 Arduino IDE 的缓存目录,把 Arduino15 挪到其他盘;二是用离线安装包,把别人打包好的 esp8266 或 esp32 板卡文件直接解压到对应目录;三是用国内镜像源,在“文件 - 首选项 - 附加开发板管理器网址”里替换成镜像地址。这些方法都能让板卡安装过程顺畅很多,具体操作网上有不少整理好的教程,这里就不展开重复。
3.2 Adafruit SSD1306 和 U8g2 怎么选
驱动库不是越多越好,两个主流库我用下来,各有明显的适用场景。
Adafruit SSD1306 库轻量、API 直观、教程多,底层依赖 Adafruit GFX 图形库。它适合 Arduino Uno / Nano 这类 Flash 和 RAM 都小的板子,几个核心函数就能完成大部分显示需求,完整例程在 GitHub 上有十几个,直接改改就能用。
U8g2 库功能全,支持几乎所有单色 OLED 控制器,内置字体极其丰富,尤其是中文字体,不需要自己取模就能显示简体中文。缺点是编译体积大,Uno 上能跑但比较紧张,推荐在 STM32、ESP32 这类资源充足的板子上用。
做个小对比表,方便你选型:
| 维度 | Adafruit SSD1306 | U8g2 |
|---|---|---|
| 资源占用 | 低,适合Uno | 较高,适合STM32/ESP32 |
| 中文字体 | 需要自己取模 | 内置中文点阵字体 |
| API难易 | 简单直观 | 稍复杂,但可定制性强 |
| 图形绘制 | 有drawLine/fillRect等 | 有drawStr/drawBox等 |
| 社区教程 | 最多 | 很多例程,但命名风格独特 |
如果你是第一次玩 OLED,我建议先装 Adafruit SSD1306,跑通再换 U8g2。核心渲染逻辑相同,库怎么换都不会影响你理解 SSD1306 本身。
3.3 编译失败和缺文件,90% 是因为依赖库没装全
看到“Adafruit_GFX.h: No such file or directory”这类报错,第一反应不是代码问题,而是依赖缺失。Adafruit SSD1306 库不是独立工作的,它还依赖 Adafruit GFX 和 Adafruit BusIO 两个库。最简单的办法是在库管理器里搜索“Adafruit SSD1306”,点“Install All”一次装全,或者在搜索里分别把 Adafruit GFX、Adafruit BusIO 都装上。
还有一种情况是库版本冲突:旧版 Adafruit SSD1306 和新版 Adafruit GFX 之间 API 不兼容,导致编译报一堆奇怪错误。处理方法是在库管理器里找到对应的库,点版本下拉框,选一个稳定的旧版本,或者全部升到最新版。这个兼容性问题,我在升级 IDE 2.x 后遇到过两次,每次都是版本匹配问题。
4. 点亮第一行像素:先用扫描程序找到地址
4.1 I2C 地址扫描:这一步偷懒,后面全白费
很多人拿到屏,直接复制网上的例程,烧录后屏幕没反应,就开始怀疑屏坏了。我推荐的流程是:先烧一个 I2C 扫描程序,确认主控能不能在总线上看到这个设备。下面这个扫描程序我一直在用,任何 I2C 设备都能扫出来:
#include <Wire.h> void setup() { Wire.begin(); Serial.begin(9600); while (!Serial); Serial.println("\nI2C Scanner"); } void loop() { byte error, address; int nDevices = 0; for(address = 1; address < 127; address++) { Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { Serial.print("I2C device found at address 0x"); if (address < 16) Serial.print("0"); Serial.print(address, HEX); Serial.println(" !"); nDevices++; } else if (error == 4) { Serial.print("Unknown error at address 0x"); if (address < 16) Serial.print("0"); Serial.println(address, HEX); } } if (nDevices == 0) Serial.println("No I2C devices found\n"); else Serial.println("done\n"); delay(5000); }把 Serial 监视器波特率设成 9600。如果看到“I2C device found at address 0x3C”,说明接线和地址都正常,接下来再烧显示例程。如果扫描结果为空,先查接线、上拉、供电,不要急着换屏。如果你是在树莓派这类 Linux 系统上调试,用sudo i2cdetect -y 1也是同样的原理,t=i2c-tools 工具包可以直接查看总线上挂了哪些设备。
4.2 最小驱动例程,逐行看懂
地址确认后,用 Adafruit 库写一个最小例程。这也是我从 0 到 1 跑通屏幕的“地基代码”:
#include <SPI.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); void setup() { Serial.begin(9600); if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F("SSD1306 allocation failed")); for(;;); } display.clearDisplay(); display.setTextSize(2); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); display.println(F("Hello, OLED!")); display.display(); } void loop() { }这几行代码做的事很直白:创建一个 128x64 的 SSD1306 显示对象,地址传 0x3C,begin函数负责初始化和分配显存缓冲区。初始化失败时通常有两个原因——地址不对,或缓冲区分配失败(RAM 不足)。clearDisplay()清空显存,setTextSize设置字号,setCursor设置起点,println把字符串写进缓冲区,最后display()才真正把数据通过 I2C 刷到屏幕。
注意display()之前的所有绘制操作都只是修改了内存里的缓冲区,只有调用display()才会触发 I2C 传输。这一点是后面做动画和刷新优化的关键。
5. 让内容“动”起来:文字、数字、曲线和刷新优化
5.1 一个实时数据仪表盘:从静态显示到动态刷新
静态显示几个字太浪费这块屏了。最常见的需求是实时显示传感器数据,比如模拟引脚读到的电压值。下面这种代码就是“每秒刷新一次数值”的典型结构:
void loop() { int raw = analogRead(A0); float voltage = raw * (5.0 / 1023.0); display.clearDisplay(); display.setTextSize(1); display.setCursor(0, 0); display.print(F("VIN: ")); display.print(voltage); display.println(F(" V")); display.display(); delay(200); }这里有个新手经常问的问题:为什么每帧都要clearDisplay()?因为不清理的话,之前画的内容会残留,新旧数据叠加在一起,屏幕会变成一团乱码。但高频清屏也有代价,那就是闪烁。批量修改一次再统一显示,其实可以明显减少闪烁。
更好的方案是“局部刷新”:只更新数值变化的区域,不整屏清空。比如电压值固定显示在左上角,每次在同一个位置先画一个黑色矩形覆盖旧数字,再写新数字。这种方法对低帧率场景非常有效,实测下来肉眼几乎感觉不到闪烁。
5.2 进度条、波形和位图:把屏当成点阵画布
SSD1306 的显存本质是一个 128x64 的 bit-map,任何线、矩形、圆、位图都能往上画。Adafruit 库提供了几个好用函数:
display.drawRect(x, y, w, h)画矩形边框display.fillRect(x, y, w, h)填充矩形,适合画进度条display.drawLine(x1, y1, x2, y2)画直线,适合画波形display.drawBitmap(x, y, bitmap, w, h, WHITE)显示位图
做一个进度条,逻辑很简单:
void drawProgress(int percent) { int barWidth = 100; int barHeight = 10; int x = 14; int y = 40; display.drawRect(x, y, barWidth, barHeight, SSD1306_WHITE); display.fillRect(x, y, barWidth * percent / 100, barHeight, SSD1306_WHITE); }波形图则可以用一个数组存历史数据,每次循环把数据左移一位,再画整条折线。这个方法在做音频分析仪、心率监测时很常见,实现成本不高,效果却很直观。
如果你要显示中文,用 Adafruit 库时需要先取模,把汉字转成位图数组,再用drawBitmap画出来。U8g2 倒是内置了中文字体,直接setFont(u8g2_font_wqy12_t_gb2312)就能显示,但字体体积较大。取模时注意软件里的“扫描方向”和“取模方式”,方向和库不匹配时,文字会左右颠倒或上下翻转,这个坑很隐蔽。
5.3 刷新率为什么卡,以及怎么提速
I2C 的传输速度决定了刷新率上限。标准模式下 100kHz,快速模式下 400kHz。一块 128x64 的单色显存是 1024 字节,在 100kHz 下传完需要约 80ms,400kHz 下约 20ms。所以如果你用的是 Arduino Uno 的默认 I2C 速率,每帧全屏刷新理论帧率只有 12fps 左右,实际用起来体感就是“有点卡”。
提速思路有三条:
- 调用
Wire.setClock(400000L)把 I2C 提升到快速模式。Arduino Uno 的 AVR 芯片支持 400kHz,这是最直接有效的优化。 - 用局部更新代替全屏刷新。只把变化区域传过去,帧率能翻好几倍。
- 利用 SSD1306 硬件滚动命令。Adafruit 库提供了
startscrollleft()、startscrollright(),滚动字幕不需要逐帧重绘,让屏幕内部自行滚动,主控几乎不参与。
实测下来,我用 Uno + OLED 全屏刷新一帧大约 35ms,把 I2C 调到 400kHz 后能压到 20ms 以内,做简单动画已经够用。如果要做快速动画,建议换 STM32 或 ESP32,两者硬件 I2C 配合更顺畅,处理能力强很多。
6. 白屏乱码时别急着换屏:我的排障全链路记录
6.1 案例一:能扫描到地址,但屏幕始终白屏
这个问题出现频率最高。能扫描到地址说明 I2C 通信链路正常,问题大概率出在供电或初始化。
我遇到过两次这种白屏:一次是模块 VCC 接到了 3.3V,但那个模块的稳压芯片实际需要 5V 输入,供电不足导致 SSD1306 内部电压泵无法启动;另一次是用了 20cm 的杜邦线,线缆太长导致信号质量变差,把线缩短到 10cm 以内就恢复了。
处理步骤可以照这个顺序走:
- 用独立 5V 电源给 OLED 模块供电,Arduino 的 5V 引脚只是参考,别和电机共用。
- 换更短的杜邦线,或者用直连排针排母减少接触电阻。
- 在 VCC 和 GND 之间并一个 10uF 电解电容,稳定输入电压。
- 检查“上拉电阻”是否足够,必要时外接 4.7kΩ。
- 换一片 OLED 交叉验证。如果换了屏就好,说明原来那片已经损坏或批次有问题。
6.2 案例二:显示内容上下颠倒、镜像、花屏
这类问题常见于从别的项目拷代码,或者从淘宝店家给的例程里复制粘贴。SSD1306 有几条涉及显示方向的初始化命令:0xA1/0xA0控制段重映射,0xC8/0xC0控制 COM 扫描方向。不同模块厂家设置的默认值可能不同,所以同一份代码,在这家屏上正常,在那家屏上就倒过来了。
Adafruit 库的解法很直接,调用display.setRotation(),传 0、1、2、3 就能旋转四个方向。U8g2 里则是在构造函数传入U8G2_R0、U8G2_R1等参数,效果相同。
花屏则多半是接线太长、供电纹波大,或者 I2C 总线上有其他设备在干扰。可以先用示波器看 SCL 波形,如果边沿有严重振铃,串一个小电阻或者降低 I2C 速率都能缓解。没有示波器的话,直接把 I2C 速率从 400kHz 降回 100kHz 试试,往往能避开问题。
6.3 案例三:和舵机、电机一起用就死机
这是做小车、机械臂项目的经典问题。OLED 单独跑一切正常,一接上舵机或直流电机就白屏、乱码、甚至整个主控重启。根因通常是电源:舵机启动瞬间电流可以冲到 1A 以上,把主板 5V 电压拉低,SSD1306 的 I2C 信号逻辑电平也跟着崩了。
解法分三层:
- 舵机和电机单独用外部电源供电,和主控共地但不共 VCC。
- 在舵机电源两端并联一个大电容(470uF 以上),吸收瞬态电流。
- 给 OLED 的 VCC 加一个小型稳压方案(比如用 AMS1117-3.3 独立稳压),保证 I2C 通信期间电压波动被隔离。
另外提醒一句:舵机信号线最好单独走线,不要和 SDA/SCL 并列绑在一起,信号串扰会让 I2C 数据出错。
6.4 用 Linux 查看 I2C 总线,排查更直接
搜索热词里有一条“linux 调试 i2c 接口怎么查看寄存器的指令”,这其实是很实用的技能。当你在树莓派、Jetson 或任何 Linux 主机上调试 I2C 设备时,i2c-tools是你最好的朋友:
sudo apt install i2c-tools i2cdetect -l i2cdetect -y 1 i2cget -y 1 0x3c 0x00 i2cset -y 1 0x3c 0x00 0xAFi2cdetect扫描总线上的设备,i2cget和i2cset直接读写寄存器。比如读 0x3C 地址的 0x00 寄存器,可以拿到 SSD1306 的基本状态。这套工具链对排查主控和传感器通讯问题非常好用,也适用于其他 I2C 传感器和 EEPROM 芯片。很多用树莓派接 OLED 的用户反馈“为什么扫描不到”,用i2cdetect一眼就能看出是不是接线问题,比在 Arduino 上盲调快得多。
7. 从 Arduino 到 C51、STM32、ESP32 的移植路线
7.1 C51 软件 I2C:不用库也能把屏点亮
标题里的“C51”让我多说几句。51 单片机没有硬件 I2C 外设,或者说很多低端型号即使有也非常难用,所以在 AT89C52、STC89C52 这类芯片上驱动 SSD1306,主流方式是用普通 IO 口软件模拟 I2C 时序。
软件模拟 I2C,核心就四个动作:起始条件、停止条件、发送一个字节、接收应答。下面是精简的代码框架:
#include <reg52.h> sbit SCL = P2^0; sbit SDA = P2^1; void I2C_Start(void) { SDA = 1; SCL = 1; SDA = 0; SCL = 0; } void I2C_Stop(void) { SDA = 0; SCL = 1; SDA = 1; } void I2C_SendByte(unsigned char dat) { unsigned char i; for(i = 0; i < 8; i++) { SDA = (dat & 0x80) ? 1 : 0; dat <<= 1; SCL = 1; SCL = 0; } // 等待应答 SDA = 1; SCL = 1; SCL = 0; } void OLED_WriteCmd(unsigned char cmd) { I2C_Start(); I2C_SendByte(0x78); // 0x3C << 1,写方向 I2C_SendByte(0x00); // 控制字节:后续为命令 I2C_SendByte(cmd); I2C_Stop(); } void OLED_WriteData(unsigned char dat) { I2C_Start(); I2C_SendByte(0x78); I2C_SendByte(0x40); // 控制字节:后续为数据 I2C_SendByte(dat); I2C_Stop(); }地址 0x78 这个写法,如果你前面看了第 2.2 节,现在应该完全理解了:它就是 0x3C 左移一位后的结果。
写命令函数准备好以后,还需要一个初始化序列。标准 SSD1306 初始化命令有很多条,用 Adafruit 库时会自动执行,但 C51 里要自己发。关键几条包括:关显示(0xAE)、设置显示时钟分频(0xD5)、电荷泵开启(0x8D + 0x14)、设置内存寻址模式(0x20 + 0x00)、开显示(0xAF)。一套完整的初始化序列网上很容易找到,照着拉通即可。
C51 移植时特别注意两点:一是 51 的 IO 是准双向口,读应答前要把 SDA 拉高,否则读不到正确电平;二是 SCL 频率不要太快,在代码里加几个_nop_()空指令做延时,防止信号太快跟不上。网上常搜的“c51 重定向 printf”,其实是把printf底层输出的putchar改成自定义函数,让调试信息直接显示到 OLED 或串口,这在跑显示例程时很实用。
7.2 STM32 用 HAL 库:稳定性和速度都上一个台阶
STM32 的硬件 I2C 外设比 51 的软件模拟稳定得多。用 STM32CubeMX 配置 I2C1,速率选 400kHz,然后调用 HAL 库的HAL_I2C_Mem_Write或者直接用现成的 SSD1306 HAL 库驱动。这里有一个小坑:HAL 库函数里的设备地址参数传 0x3C 即可,不需要左移,因为 HAL 库内部自己会处理地址格式。如果你把 0x78 直接塞进去,反而会出错。
STM32 跑 U8g2 或 Adafruit 库都有对应移植版,一个比较推荐的参考路线是:CubeMX 生成 I2C 初始化代码,然后在中间层写一个OLED_WriteCmd/OLED_WriteData,底层用 HAL 库的HAL_I2C_Master_Transmit发送数据。这样把底层的 I2C 函数替换掉之后,上层可以直接复用 Arduino 上的绘图逻辑,迁移成本很低。
7.3 ESP8266 / ESP32 上的联网状态屏
ESP8266 和 ESP32 的火爆,让 OLED 找到了第二个主战场:联网状态显示屏。WiFi 配网信息、MQTT 消息、天气数据、服务器状态,都能在 OLED 上实时滚动显示。安装板卡的方法我在第 3.1 节提过,主要是 JSON 地址和离线安装包两个方案。ESP8266 的 I2C 引脚是 D1 (SCL)、D2 (SDA),ESP32 是 GPIO21、GPIO22,都是 3.3V 电平,模块选 3.3V 供电即可。
在 ESP8266 上用 U8g2 库可以轻松显示中文字体,比如显示城市名和天气描述,效果比 Arduino Uno 上好很多。而且 ESP32 有双核,可以一个核心负责网络请求,一个核心负责屏幕刷新,画面卡顿问题几乎消失。用 Blynk、Home Assistant 这类平台做远程数据面板时,OLED 作为本地显示模块非常合适。
7.4 Wokwi 在线仿真:没板子也能把 UI 调好
搜索热词里“wokwi 仿真平台 arduino”出现得很频繁,这个免费在线仿真平台确实值得推荐。在 wokwi.com 上创建 Arduino Uno 项目,可以直接在电路里拖一个 SSD1306 OLED 模块,然后写代码运行,浏览器里就能看到屏幕内容和 I2C 时序效果。对于刚入门的同学,先用 Wokwi 把代码逻辑调通,再买实物硬件,会少踩很多接线坑。我甚至见过有人用 Wokwi 先写好一整套仪表盘 UI,然后才下单买板子,效率很高。
有一点提醒:仿真环境毕竟不能完全模拟真实电源噪声和接线问题。你在 Wokwi 上能点亮,不代表真机上一定稳定,供电、线缆、干扰这些“体质问题”还是需要实机调试。
最后分享一点个人经验:我早期也迷信各种库和花哨功能,后来发现驱动 SSD1306 的核心就两个函数——写命令和写数据。只要把这两个函数真正弄懂,无论换什么主控,哪怕是一颗几块钱的 51 单片机,都能轻松点亮。如果一个项目只能在 Arduino 上跑,换到其他主控就抓瞎,说明还没吃透 I2C 时序。这块屏最大的价值不是参数,而是帮你建立“看地址、量上拉、查波形”的硬件排查习惯。学完它,再看其他 I2C 传感器和扩展芯片,思路会通一大半。
本文还有配套的精品资源,点击获取