ESP32-S3学习笔记<1>:I2C 的应用(驱动封装与 OLED 通信基础)
本文最后更新于 2025-11-21,文章内容可能已经过时。
本文基于 ESP-IDF 的旧版 I2C 驱动(driver/i2c.h),详细解析 I2C 初始化、命令链机制、OLED 命令/数据发送逻辑,并说明代码设计原理。
1. 头文件包含
I2C 驱动模块通常只需包含用户自定义的 I2C 接口头文件:
#include "I2C.h"
实际源码内部还会使用:
#include "driver/i2c.h"
#include "esp_log.h"
#include "esp_err.h"
#include "string.h"
#include "stdlib.h"
其中:
driver/i2c.h→ ESP-IDF I2C APIesp_log.h→ 日志输出esp_err.h→ 错误码string.h/stdlib.h→ 内存操作
2. I2C 的配置
ESP32-S3 拥有两个 I2C 控制器可供使用:
| 控制器 | 宏定义 | 功能 |
|---|---|---|
| I2C_NUM_0 | 0 | 通用用途 |
| I2C_NUM_1 | 1 | 通用用途 |
在驱动中以宏的形式指定要使用的端口:
#define I2C_PORT_NUM I2C_NUM_0
2.1 I2C 配置结构体:i2c_config_t
配置 I2C 主机模式使用以下结构体:
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = 17,
.scl_io_num = 18,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 100000, // 100kHz
.clk_flags = 0,
};
下面解释各个字段的含义。
2.1.1 mode / 工作模式
可选值:
I2C_MODE_MASTER→ 主机模式(常用)I2C_MODE_SLAVE→ 从机模式(极少使用)
OLED、传感器等外设都是从机,所以 ESP32 作为 主机。
2.1.2 sda_io_num / scl_io_num
指定 SDA / SCL 使用的 GPIO 口:
| 字段 | 含义 |
|---|---|
.sda_io_num = 17 | SDA 引脚 |
.scl_io_num = 18 | SCL 引脚 |
这两个引脚可映射到任意可用 GPIO。
2.1.3 上拉电阻
I2C 为开漏输出,因此 SDA/SCL 必须上拉:
.sda_pullup_en = GPIO_PULLUP_ENABLE
.scl_pullup_en = GPIO_PULLUP_ENABLE
模块上通常已经带有 4.7k 上拉,但软件上也可以开启内部上拉。
2.1.4 I2C 主时钟频率
.master.clk_speed = 100000
OLED 一般支持:
- 100kHz(标准模式)
- 400kHz(快速模式)
本项目设置为保守的 100kHz。(因为SH1107对于快速模式的兼容存在问题)
2.1.5 clk_flags
一般设定为 0。
2.2 应用配置
调用 i2c_param_config 将配置写入控制器:
i2c_param_config(I2C_PORT_NUM, &conf);
2.3 安装 I2C 驱动
i2c_driver_install(I2C_PORT_NUM, conf.mode, 256, 256, 0);
参数解释:
| 参数 | 含义 |
|---|---|
I2C_PORT_NUM | 使用的 I2C 控制器 |
conf.mode | 主机模式 |
256 | RX 缓冲区大小 |
256 | TX 缓冲区大小 |
0 | 不使用事件队列 |
RX/TX 缓冲设为 256 bytes 可以提升性能,尤其是整行写入 OLED 数据时。
3. 设备扫描(可选)
如果用户没有定义 OLED 地址宏:
#ifndef OLED_I2C_ADDR
初始化函数会自动扫描 I2C 总线:
for (uint8_t addr = 1; addr < 127; addr++)
扫描步骤示例:
sequenceDiagram
participant Host
participant Slave
Host->>Slave: START
Host->>Slave: 地址 (addr << 1)
Slave-->>Host: ACK
Host->>Slave: STOP
打印类似信息:
Found I2C device at address: 0x3C
这对 OLED 地址不明确时很有帮助。
4. OLED 地址检查(可选)
如果用户定义了:
#define OLED_I2C_ADDR 0x3C
会自动检查 OLED 是否上线:
i2c_master_write_byte(handle, (OLED_I2C_ADDR << 1) | WRITE, true);
OLED 未连好时:
W I2C_Module: OLED device not found at address 0x3C
连接正常时:
I I2C_Module: OLED device found at address 0x3C
5. 判断初始化状态
通过全局变量避免重复初始化:
static bool i2c_initialized = false;
提供接口:
bool is_I2C_Initialized(void)
{
return i2c_initialized;
}
用于保护:
- 多模块复用同一 I2C 总线
- 防止重复安装驱动产生错误
6. I2C 命令链机制(Command Link)
ESP-IDF 的旧版 I2C API 采用 命令链模型,表示一次 I2C 事务。
基本用法:
i2c_cmd_handle_t cmd_handle = i2c_cmd_link_create();
i2c_master_start(cmd_handle);
i2c_master_write_byte(cmd_handle, byte, true);
i2c_master_stop(cmd_handle);
i2c_master_cmd_begin(I2C_PORT_NUM, cmd_handle, timeout);
i2c_cmd_link_delete(cmd_handle);
每个函数的作用如下:
| 函数 | 作用 |
|---|---|
i2c_master_start() | 发送 START 信号 |
i2c_master_write_byte() | 写入一个字节(含 ACK 选择) |
i2c_master_write() | 写入多个字节 |
i2c_master_stop() | 发送 STOP 信号 |
i2c_master_cmd_begin() | 真正执行事务 |
i2c_cmd_link_delete() | 删除链表释放内存 |
7. 发送 OLED 命令(Command Mode)
OLED 的 I2C 命令格式固定:(仅适用于SH1107G)
[0x00][cmd]
其中:
0x00= Command mode(Co = 0,D/C# = 0)
代码封装:
uint8_t cmd_buf[2] = {0x00, cmd};
i2c_master_write(cmd_handle, cmd_buf, 2, true);
完整调用:
I2C_Write_Cmd(0xAE); // Display OFF
I2C_Write_Cmd(0xAF); // Display ON
8. 发送 OLED 数据(Data Mode)
数据模式的前缀是:
0x40
格式:
[0x40][data0][data1]...[dataN]
封装流程:
- 创建
len + 1缓冲区 buf[0] = 0x40- 拷贝数据
- 整块写入 I2C
适合写整行数据:
uint8_t row[64];
I2C_Write_Data(row, 64);
9. 通信流程示意图
flowchart TD
A["UI_Display.c<br/>(OLED上层接口实现)"]
B["I2C_Module.c<br/>(Init / Cmd / Data)"]
C["ESP-IDF I2C Driver<br/>(cmd_link & cmd_begin)"]
A --> B --> C
10. 模块优点
| 功能 | 说明 |
|---|---|
| 自动扫描设备 | OLED 地址不确定时非常实用 |
| 自动检测 OLED 在线状态 | 防止无效初始化 |
| 使用命令链标准流程 | 符合 ESP-IDF 驱动规范 |
| 全局初始化标志位 | 便于多模块共享 I2C |
| 二次封装命令与数据模式 | 上层调用更简单 |
| 支持发送整行数据 | 提升 OLED 刷新效率 |
11. 使用示例
初始化 I2C:
I2C_Init();
发送命令:
I2C_Write_Cmd(0xAF); // Display ON
发送像素数据:
uint8_t buf[128];
I2C_Write_Data(buf, 128);