SOC-ESP32S3部分:16-I2C
飞书文档https://x509p6c8to.feishu.cn/wiki/BuoBwzcpoikwZdkOGiEcmsoonod
I2C 是一种串行同步半双工通信协议,总线上可以同时挂载多个主机和从机。ESP32-S3 有 2 个 I2C 控制器(也被称为端口),负责处理 I2C 总线上的通信。单个 I2C 控制器既可以是主机也可以是从机。
I2C(Inter-Integrated Circuit)是一种串行通信协议,用于在微控制器和其他设备之间传输数据。ESP32-S3芯片支持I2C接口,使其能够与各种传感器、显示器和其他I2C兼容的设备进行通信。
主设备和从设备
- 主设备:控制I2C总线,负责发起和终止通信。
- 从设备:响应主设备的请求,提供数据或执行命令。
I2C接口特性
- 双向通信:数据可以在主设备和从设备之间双向传输。
- 多主模式:多个主设备可以通过仲裁机制在同一条I2C总线上通信。
- 低功耗:相比并行通信,I2C具有较低的功耗。
I2C文档参考:
https://docs.espressif.com/projects/esp-idf/zh_CN/v5.4/esp32s3/api-reference/peripherals/i2c.html
课程主要讲解主机,因为大多数情况下,ESP32S3都是作为主机去读取传感器从机的数据。
ESP32S3 SDK中,把I2C抽象为总线-设备模型,所以I2C主机的使用主要有以下几步
- 初始化I2C总线实例
- 添加I2C设备到总线上
- 进行I2C数据读写
初始化I2C总线实例-i2c_new_master_bus
创建并初始化一个新的 I2C 总线实例。该函数配置 I2C 主机模式,并返回一个总线句柄,以便后续操作使用。
esp_err_t i2c_new_master_bus(const i2c_master_bus_config_t *config, i2c_master_bus_handle_t *bus_handle);参数说明
config :指向 i2c_master_bus_config_t 结构体的指针,包含 I2C 总线的配置参数。
bus_handle:用于存储新创建的 I2C 总线句柄的指针。成功调用后,该句柄可用于后续的 I2C 操作。返回值
ESP_OK: 成功创建并初始化 I2C 总线。
使用参考
// 配置 I2C 总线参数i2c_master_bus_config_t i2c_bus_config = {.clk_source = I2C_CLK_SRC_DEFAULT, // 使用默认时钟源 有关不同时钟源对功耗的影响,请参阅 电源管理 部分。.i2c_port = PORT_NUMBER, // 指定 I2C 端口,如果设置为 -1,则使用默认端口。.scl_io_num = SCL_IO_PIN, // SCL 引脚编号.sda_io_num = SDA_IO_PIN, // SDA 引脚编号.glitch_ignore_cnt = 7, // 忽略毛刺计数,如果线上的毛刺周期小于设置的值(通常设为 7),则可以被滤除。.flags.enable_internal_pullup = true, //启用内部上拉};// 创建 I2C 总线句柄i2c_master_bus_handle_t bus_handle;ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &bus_handle)); // 初始化 I2C 总线
添加I2C设备到总线上-i2c_master_bus_add_device
将一个 I2C 设备添加到已创建的 I2C 总线上,该函数配置指定地址的 I2C 从设备,并返回一个设备句柄,以便后续对该设备进行读写操作。
esp_err_t i2c_master_bus_add_device(i2c_master_bus_handle_t bus_handle, const i2c_device_config_t *dev_cfg, i2c_master_dev_handle_t *dev_handle);参数说明
bus_handle:已创建的 I2C 总线句柄,由 i2c_new_master_bus 创建并初始化。
dev_cfg:指向 i2c_device_config_t 结构体的指针,包含要添加的 I2C 设备的配置参数。
dev_handle:用于存储新创建的 I2C 设备句柄的指针。成功调用后,该句柄可用于后续与该设备的通信操作。返回值
ESP_OK: 成功添加 I2C 设备。
使用参考
// 配置 I2C 设备参数i2c_device_config_t dev_cfg = {.dev_addr_length = I2C_ADDR_BIT_LEN_7, // 设备地址长度(7 位或 10 位).device_address = DEVICE_ADDR, // 设备地址.scl_speed_hz = MASTER_FREQUENCY, // SCL 时钟频率(单位:Hz)};// 创建 I2C 设备句柄i2c_master_dev_handle_t dev_handle;ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_cfg, &dev_handle)); // 添加设备到总线
通过调用 i2c_new_master_bus和i2c_master_bus_add_device安装好 I2C 主机控制器驱动程序后,ESP32-S3 就可以与其他 I2C 设备进行通信了。I2C API 允许标准事务,如下图所示:
I2C 主机发送
成功安装 I2C 主机总线之后,可以通过调用i2c_master_transmit来向从机设备写入数据。下图解释了该函数的原理。
将数据写入从机设备的简单示例:
esp_err_t i2c_master_transmit(i2c_master_dev_handle_t dev_handle, const uint8_t *data, size_t length, TickType_t ticks_to_wait);参数说明
dev_handle (i2c_master_dev_handle_t):I2C 设备句柄,该句柄是通过 i2c_master_bus_add_device 函数创建的。
data (const uint8_t *):指向要发送的数据缓冲区的指针。该缓冲区包含要通过 I2C 总线发送的数据字节。
length (size_t):要发送的数据长度(以字节为单位)。指定发送多少个字节的数据。
ticks_to_wait (TickType_t):等待队列可用的时间,单位为 FreeRTOS 的滴答数。如果设置为 portMAX_DELAY,则任务将无限期等待,直到有资源可用;如果设置为 0,则不会等待,直接返回错误。返回值
ESP_OK: 成功发送数据。
ESP_ERR_TIMEOUT: 在指定时间内无法获取 I2C 总线访问权限或队列满。
使用参考:
// 要发送的数据uint8_t data_to_send[] = {0x01, 0x02, 0x03, 0x04};// 发送数据esp_err_t ret = i2c_master_transmit(dev_handle, data_to_send, sizeof(data_to_send), pdMS_TO_TICKS(1000));if (ret == ESP_OK) {ESP_LOGI(TAG, "Data transmitted successfully");} else {ESP_LOGE(TAG, "Failed to transmit data: %d", ret);}
I2C 主机读取
在成功安装 I2C 主机总线后,可以通过调用 i2c_master_receive() 从从机设备读取数据。下图解释了该函数的原理。
I2C 主机从从机设备读取数据
从从机设备读取数据的简单示例:
esp_err_t i2c_master_receive(i2c_master_dev_handle_t dev_handle, uint8_t *data, size_t length, TickType_t ticks_to_wait);
参数说明
dev_handle (i2c_master_dev_handle_t):I2C 设备句柄,表示要通信的具体 I2C 从设备。
data (uint8_t *):指向接收缓冲区的指针。该缓冲区用于存储从 I2C 从设备接收到的数据字节。
length (size_t):要接收的数据长度(以字节为单位)。指定接收多少个字节的数据。
ticks_to_wait (TickType_t):等待队列可用的时间,单位为 FreeRTOS 的滴答数。如果设置为 portMAX_DELAY,则任务将无限期等待,直到有资源可用;如果设置为 0,则不会等待,直接返回错误。
返回值
ESP_OK: 成功接收数据。
ESP_ERR_TIMEOUT: 在指定时间内无法获取 I2C 总线访问权限或队列满。
使用参考
// 准备接收缓冲区uint8_t data_received[10] = {0}; // 假设最多接收 10 字节数据// 接收数据esp_err_t ret = i2c_master_receive(dev_handle, data_received, sizeof(data_received), pdMS_TO_TICKS(1000));if (ret == ESP_OK) {ESP_LOGI(TAG, "Data received successfully: ");for (int i = 0; i < sizeof(data_received); i++) {printf("0x%02X ", data_received[i]);}printf("\n");} else {ESP_LOGE(TAG, "Failed to receive data: %d", ret);}
I2C 主机写入后读取
从一些 I2C 设备中读取数据之前需要进行写入配置,可通过 i2c_master_transmit_receive接口进行配置。下图解释了该函数的原理。
I2C 主机向从机设备写入并从从机设备读取数据
请注意,在写入操作和读取操作之间没有插入 STOP 条件位,因此该功能适用于从 I2C 设备读取寄存器。以下是向从机设备写入数据并从从机设备读取数据的简单示例:
esp_err_t i2c_master_transmit_receive(i2c_master_dev_handle_t dev_handle, const uint8_t *tx_data, size_t tx_length, uint8_t *rx_data, size_t rx_length, TickType_t ticks_to_wait);
参数说明
dev_handle (i2c_master_dev_handle_t):
I2C 设备句柄,表示要通信的具体 I2C 从设备。该句柄是通过 i2c_master_bus_add_device 函数创建的。
tx_data (const uint8_t *):
指向要发送的数据缓冲区的指针。该缓冲区包含要通过 I2C 总线发送的数据字节。
tx_length (size_t):
要发送的数据长度(以字节为单位)。指定发送多少个字节的数据。
rx_data (uint8_t *):
指向接收缓冲区的指针。该缓冲区用于存储从 I2C 从设备接收到的数据字节。
rx_length (size_t):
要接收的数据长度(以字节为单位)。指定接收多少个字节的数据。
ticks_to_wait (TickType_t):
等待队列可用的时间,单位为 FreeRTOS 的滴答数。如果设置为 portMAX_DELAY,则任务将无限期等待,直到有资源可用;如果设置为 0,则不会等待,直接返回错误。
返回值
ESP_OK: 成功发送和接收数据。
使用参考
// 这里可以继续添加与 I2C 设备通信的代码// 要发送的数据(例如命令或寄存器地址)uint8_t command[] = {0x01}; // 假设我们要读取寄存器 0x01 的内容// 准备接收缓冲区uint8_t data_received[10] = {0}; // 假设最多接收 10 字节数据// 发送命令并接收数据esp_err_t ret = i2c_master_transmit_receive(dev_handle, command, sizeof(command), data_received, sizeof(data_received), pdMS_TO_TICKS(1000));if (ret == ESP_OK) {ESP_LOGI(TAG, "Data received successfully: ");for (int i = 0; i < sizeof(data_received); i++) {printf("0x%02X ", data_received[i]);}printf("\n");} else {ESP_LOGE(TAG, "Failed to transmit and receive data: %d", ret);}
I2C 主机探测
I2C 驱动程序可以使用 i2c_master_probe来检测设备是否已经连接到 I2C 总线上。如果该函数返回 ESP_OK,则表示该设备已经被检测到。
重要
在调用该函数时,必须将上拉电阻连接到 SCL 和 SDA 管脚。如果在正确解析 xfer_timeout_ms 时收到 ESP_ERR_TIMEOUT,则应检查上拉电阻。若暂无合适的电阻,也可将 flags.enable_internal_pullup 设为 true。
I2C 主机探测
探测 I2C 设备的简单示例:
该函数用于探测指定地址的 I2C 设备是否存在。如果地址正确并且接收到 ACK(应答)
esp_err_t i2c_master_probe(i2c_master_bus_handle_t bus_handle, uint16_t address, int xfer_timeout_ms)
参数
bus_handle (i2c_master_bus_handle_t):I2C 主设备总线句柄,由 i2c_new_master_bus 创建。
address (uint16_t):要探测的 I2C 设备地址(7 位或 10 位地址)。
xfer_timeout_ms (int):等待超时时间,单位为毫秒。注意:-1 表示无限期等待(不推荐在此函数中使用)。
返回值
ESP_OK: I2C 设备探测成功。
ESP_ERR_NOT_FOUND: I2C 探测失败,未找到指定地址的设备。
使用参考
// 探测设备esp_err_t ret = i2c_master_probe(bus_handle, DEVICE_ADDR, 1000);if (ret == ESP_OK) {ESP_LOGI(TAG, "Device at address 0x%02X found and responsive", DEVICE_ADDR);} else if (ret == ESP_ERR_NOT_FOUND) {ESP_LOGE(TAG, "Failed to find device at address 0x%02X", DEVICE_ADDR);} else if (ret == ESP_ERR_TIMEOUT) {ESP_LOGE(TAG, "Timeout while probing device at address 0x%02X", DEVICE_ADDR);} else {ESP_LOGE(TAG, "Failed to probe device at address 0x%02X: %d", DEVICE_ADDR, ret);}
最终参考代码如下
这个代码只是参考,无需运行,下节课,我们会基于温湿度传感器和EEPROM例程进行学习,让大家了解如何使用ESP32S3 I2C驱动这两个外设。
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gptimer.h"
#include "esp_log.h"#include <string.h>
#include "sdkconfig.h"
#include "driver/i2c_master.h"static const char *TAG = "Timer"; // 定义日志标签// I2C 引脚和设备配置
#define SCL_IO_PIN 8 // I2C 时钟线引脚编号
#define SDA_IO_PIN 4 // I2C 数据线引脚编号
#define DEVICE_ADDR 0x58 // I2C 设备地址 (7-bit 地址)
#define MASTER_FREQUENCY 400000 // I2C 总线频率 (400kHz)
#define PORT_NUMBER -1 // I2C 端口号 (-1 表示使用默认端口)
#define LENGTH 48 // 缓冲区长度(未在代码中使用)void app_main(void)
{// 配置 I2C 总线参数i2c_master_bus_config_t i2c_bus_config = {.clk_source = I2C_CLK_SRC_DEFAULT, // 使用默认时钟源 有关不同时钟源对功耗的影响,请参阅 电源管理 部分。.i2c_port = PORT_NUMBER, // 指定 I2C 端口,如果设置为 -1,则使用默认端口。.scl_io_num = SCL_IO_PIN, // SCL 引脚编号.sda_io_num = SDA_IO_PIN, // SDA 引脚编号.glitch_ignore_cnt = 7, // 忽略毛刺计数,如果线上的毛刺周期小于设置的值(通常设为 7),则可以被滤除。.flags.enable_internal_pullup = true, //启用内部上拉};// 创建 I2C 总线句柄i2c_master_bus_handle_t bus_handle;ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &bus_handle)); // 初始化 I2C 总线// 配置 I2C 设备参数i2c_device_config_t dev_cfg = {.dev_addr_length = I2C_ADDR_BIT_LEN_7, // 设备地址长度(7 位或 10 位).device_address = DEVICE_ADDR, // 设备地址.scl_speed_hz = MASTER_FREQUENCY, // SCL 时钟频率(单位:Hz)};// 创建 I2C 设备句柄i2c_master_dev_handle_t dev_handle;ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_cfg, &dev_handle)); // 添加设备到总线// 下方添加与 I2C 设备通信的代码,根据从机的协议进行读写即可// TODO
}