ESP32-S3 IDF编程第九课:I2C通信与OLED显示 —— 让数据"看得见"
ESP32-S3 IDF编程第九课:I2C通信与OLED显示 —— 让数据”看得见”
上节课我们用ADC采集了电压值,但只能对着串口调试助手看数字。这节课让数据”跳”到屏幕上——I2C通信 + OLED显示,从此告别”盲调”。
你可能会问:串口打印不也能看数据吗?为什么还要学I2C和OLED?
来,看个真实场景:
- 你做了个电池电量监测仪,总不能一直插着电脑看串口吧?
- 你做了个温湿度传感器,想挂在墙上实时显示,没屏幕怎么行?
- 你做了个小型示波器,要把波形画出来,串口可画不了图
这时候,你就需要I2C通信和OLED显示屏——I2C是连接传感器的”标准语言”,OLED是让数据”可视化”的最佳选择。128×64的分辨率,能显示8行文字,功耗还低,简直是嵌入式开发的神器!
一、I2C协议是什么?
1.1 从生活类比理解I2C
想象I2C是一条双向单车道马路:
- SDA(数据线):运送数据的”货车”
- SCL(时钟线):指挥交通的”红绿灯”
- 上拉电阻:确保没车的时候路面是”高电平”(空载状态)
- 设备地址:每个设备的”门牌号”,主机靠这个找到从机
关键特性:
- 只需要2根线(SDA + SCL),省IO口
- 多设备共享一条总线,每个设备有独立地址
- 主从架构:主机(ESP32)发起通信,从机(OLED)被动响应
1.2 I2C通信时序
一次完整的I2C通信就像”打电话”:
1 | 主机:喂,是0x3C吗?(发送起始信号 + 设备地址) |
时序图解:
时序分段解析
1. 起始信号
- 动作:SCL 保持高电平期间,SDA 由高电平变为低电平。
- 意义:这标志着一次 I2C 通信的开始,所有从机都会被唤醒并准备接收数据。
2. 数据传输 (bit7 到 bit0)
I2C 协议规定数据是以 字节(8位) 为单位传输的,且 高位先行。
bit7 ~ bit0
:图中展示了 8 个数据位。
- 在 SCL 的每一个时钟脉冲(高电平期间),SDA 线上的电平状态代表当前的数据位是 1 还是 0。
- 注意:SDA 上的数据必须在 SCL 为高电平时保持稳定,只能在 SCL 为低电平时改变,以确保接收方能正确采样。
中间省略 (3-6):图中虚线框表示 bit5 到 bit2 的传输过程与前后类似,为了简化图表被省略了。
3. 应答信号
动作:在第 9 个时钟脉冲期间,SDA 线被拉低。
意义
:这是从机发给主机的
应答信号
。
- 低电平:表示从机成功接收到了前面的 8 位数据,并准备好了接收下一个字节。
- 高电平:如果 SDA 保持高电平(非应答),则表示接收失败或从机忙。
4. 停止信号
- 动作:SCL 保持高电平期间,SDA 由低电平变为高电平。
- 意义:这标志着本次 I2C 通信的结束,总线被释放。
1. 基础信号:通信的”开场”与”收尾”
无论读还是写,都离不开这几个物理信号:
- 起始位 (Start Condition): SCL 高电平时,SDA 由高变低。这表示”大家注意,我要开始讲话了”。
- 停止位 (Stop Condition): SCL 高电平时,SDA 由低变高。这表示”通信结束,释放总线”。
- 应答位 (ACK/NACK): 接收方在第 9 个时钟周期将 SDA 拉低表示 ACK(收到),保持高电平则表示 NACK(没收到或出错)。
2. 写数据流程 (Master Write)
写数据通常用于设置寄存器或发送指令。流程如下:
- 发送起始位。
- 发送设备地址 + 写位 (0):主机发送 7 位从机地址,第 8 位设为
0(代表 Write)。 - 接收 ACK:对应的从机发出应答。
- 发送寄存器地址:告诉从机你要往哪个”抽屉”里写东西。
- 接收 ACK。
- 发送数据字节:发送你要写入的内容(可以连续发送多个字节)。
- 接收 ACK:每发一个字节,从机都要回一个应答。
- 发送停止位。
3. 读数据流程 (Master Read)
读数据稍微复杂一点,因为你必须先告诉从机”我想读哪个寄存器”,这涉及到一个**”复合格式”**:
- 发送起始位。
- 发送设备地址 + 写位 (0):注意,这里先用写模式。
- 接收 ACK。
- 发送寄存器地址:告诉从机你想读哪个位置。
- 接收 ACK。
- 重复起始位 (Restart):不发停止位,直接再次发送起始信号。这是为了切换模式。
- 发送设备地址 + 读位 (1):这次第 8 位设为
1(代表 Read)。 - 接收 ACK。
- 读取数据:此时 SDA 的控制权交给从机,主机每读一个字节要回传一个 ACK。
- 发送 NACK:读到最后一个字节时,主机发送 NACK,告诉从机”够了,别发了”。
- 发送停止位。
1.3 SSD1306 OLED 简介
SSD1306 是一款单芯片CMOS OLED驱动器,特性:
| 参数 | 数值 |
|---|---|
| 分辨率 | 128 × 64 像素 |
| 颜色 | 单色(黑/白) |
| 通信接口 | I2C(默认)/ SPI |
| I2C地址 | 0x3C(SA0=0)或 0x3D(SA0=1) |
| 显存 | 128 × 64 = 8192 bit = 1KB |
显存结构(页寻址模式):
- 把64行分成8个”页”(Page),每页8行
- 每页128列,每列1个字节(8位)
- 总共 8页 × 128字节 = 1024字节显存
1 | 列 0 ~ 127 |
二、硬件准备
2.1 所需材料
- ESP32-S3开发板 × 1
- SSD1306 OLED显示屏(128×64,I2C接口)× 1
- 杜邦线 × 4
2.2 接线图
1 | ESP32-S3 OLED SSD1306 |
💡 注意:
- SDA和SCL需要上拉电阻(4.7kΩ),但ESP32-S3内部已集成,可以省略
- 部分OLED模块已经板载了上拉电阻
- I2C地址通常是0x3C,如果不行试试0x3D
三、SSD1306驱动原理详解
3.1 驱动架构概览
SSD1306驱动分为三层:
1 | ┌─────────────────────────────────────┐ |
3.2 显存缓冲区设计
1 | // 显存缓冲区: 128列 × 8页 = 1024字节 |
显存寻址公式:
1 | buffer[page * 128 + column] = data; |
发送命令
1 | void ssd1306_command(uint8_t cmd) { |
I2C数据包结构:
1 | [Control Byte: 0x00] [Command Byte 1] [Command Byte 2] ... |
发送数据
1 | void ssd1306_data(uint8_t *data, size_t len) { |
数据 vs 命令的区别:
- 命令 (0x00):控制屏幕行为(开关显示、设置对比度等)
- 数据 (0x40):写入显存,显示内容
3.4 SSD1306初始化序列详解
1 | void ssd1306_init() { |
初始化命令速查表:
| 命令 | 代码 | 参数 | 作用 |
|---|---|---|---|
| Display OFF | 0xAE | - | 关闭显示 |
| Set MUX Ratio | 0xA8 | 0x3F | 64行复用 |
| Set Display Offset | 0xD3 | 0x00 | 显示偏移 |
| Set Start Line | 0x40n | - | 起始行 |
| Charge Pump | 0x8D | 0x14 | 使能电荷泵 |
| Memory Mode | 0x20 | 0x00 | 水平寻址 |
| Segment Remap | 0xA1 | - | 段重映射 |
| COM Scan Direction | 0xC8 | - | COM扫描方向 |
| Set COM Pins | 0xDA | 0x12 | COM引脚配置 |
| Set Contrast | 0x81 | 0xCF | 对比度 |
| Set Pre-charge | 0xD9 | 0xF1 | 预充电 |
| Set VCOMH | 0xDB | 0x30 | VCOMH电平 |
| Display ON | 0xAF | - | 打开显示 |
⚠️ 重要:
0x8D 0x14(电荷泵使能)是屏幕能亮起来的关键!
3.5 显存刷新机制
1 | void ssd1306_refresh() { |
刷新原理:
- SSD1306有8页,每页128列
- 必须先设置页地址和列地址,才能写入数据
- 一次性发送128字节,比逐字节发送高效得多
3.6 绘图函数实现
画点函数(核心)
1 | void ssd1306_draw_pixel(uint8_t x, uint8_t y) { |
画点原理图解:
1 | 要在坐标 (10, 17) 画点: |
字符绘制
1 | void ssd1306_draw_char(uint8_t x, uint8_t y, char c) { |
3.7 完整驱动代码
1 |
|
四、字库生成与使用
4.1 使用 image2display 生成字库
推荐使用 image2display 生成自定义字库:
配置参数:
- 字体大小:12
- 宽度:9,高度:8
- 遍历顺序:如图所示
- 字符集:选择大小写字母和标点符号
4.2 字库文件修改
生成的 font_data.c 需要修改:
1 |
|
4.3 CMakeLists.txt 配置
1 | idf_component_register( |
五、常见问题与解决
5.1 OLED不亮
检查清单:
- 接线是否正确(SDA、SCL是否接反)
- I2C地址是否正确(0x3C或0x3D)
- 是否发送了电荷泵使能命令(
0x8D 0x14) - 最后是否发送了显示开启命令(
0xAF)
5.2 显示花屏/乱码
原因:显存数据未正确初始化
解决:
1 | ssd1306_clear(); // 清空显存 |
5.3 显示方向不对
解决:修改初始化命令
1 | ssd1306_command(0xA0); // 段重映射(水平翻转) |
5.4 刷新闪烁
原因:每次刷新整个屏幕
优化:只刷新变化的部分(局部刷新)




