本文最后更新于 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 API
  • esp_log.h → 日志输出
  • esp_err.h → 错误码
  • string.h / stdlib.h → 内存操作

2. I2C 的配置

ESP32-S3 拥有两个 I2C 控制器可供使用:

控制器宏定义功能
I2C_NUM_00通用用途
I2C_NUM_11通用用途

在驱动中以宏的形式指定要使用的端口:

#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 = 17SDA 引脚
.scl_io_num = 18SCL 引脚

这两个引脚可映射到任意可用 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主机模式
256RX 缓冲区大小
256TX 缓冲区大小
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]

封装流程:

  1. 创建 len + 1 缓冲区
  2. buf[0] = 0x40
  3. 拷贝数据
  4. 整块写入 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);