ESP-C3入门23. I2C读写外部存储器
- 一、准备工作
- 1. 开发环境
- 2. ESP32-C3 I2C资源介绍
- 二、主要函数
- 1. 配置驱动程序
- 2. 源时钟配置
- 3. 安装驱动程序
- 4. 通信
- 5. 指示写入或读取数据
- 二、实现步骤
- 1. 配置 I2C 总线:
- 2. 初始化 I2C 总线:
- 3. 与外部存储设备通信:
- 4. 处理读写数据:
- 5. 关闭 I2C 总线:
- 三、完整代码
- 1. i2c_util.h
- 2. i2c_util.c
- 3. 读写示例
一、准备工作
1. 开发环境
- ESP32 IDF开发环境
- 外部存储器Flash
官方文档地址: https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32c3/api-reference/peripherals/i2c.html
2. ESP32-C3 I2C资源介绍
I2C 是一种串行同步半双工通信协议,总线上可以同时挂载多个主机和从机。I2C 总线由串行数据线 (SDA) 和串行时钟线 (SCL) 线构成。这些线都需要上拉电阻。
ESP32-C3 上通常包含两个 I2C 控制器(I2C0 和 I2C1),它们可以分别用于不同的设备或任务。以下是 I2C 控制器的一些常见特性和资源:
-
I2C 控制器数量:
- ESP32-C3 通常配备了两个独立的 I2C 控制器,分别命名为 I2C0 和 I2C1。
-
引脚分配:
- 每个 I2C 控制器都需要两个引脚:SDA(数据线)和 SCL(时钟线)。
- ESP32-C3 上的引脚分配可以根据硬件设计进行配置。
-
工作模式:
- I2C 控制器可以配置为主设备(Master)或从设备(Slave)模式。
- 主设备模式允许 ESP32-C3 与其他 I2C 设备通信,发送和接收数据。
- 从设备模式允许 ESP32-C3 作为从设备响应其他主设备的请求。
-
时钟速率:
- I2C 控制器支持多种时钟速率,通常以 Hz 为单位。时钟速率决定了数据传输的速度。
- ESP32-C3 上的 I2C 控制器通常支持标准模式(100 kHz)和快速模式(400 kHz)。
-
数据缓冲区:
- I2C 控制器通常具有用于存储发送和接收数据的缓冲区。
- 你可以在代码中填充这些缓冲区以发送数据或从中读取数据。
-
中断和DMA支持:
- ESP32-C3 的 I2C 控制器通常支持中断和DMA(直接内存访问),以提高数据传输的效率。
-
主要函数:
- 在 ESP32-C3 上,你可以使用 ESP-IDF(Espressif IoT Development Framework)中的 I2C 驱动库来初始化和操作 I2C 控制器。
- 一些常用的函数包括
i2c_master_init()
(初始化主设备模式)、i2c_slave_init()
(初始化从设备模式)、i2c_master_cmd_begin()
(发送 I2C 命令)等。
-
错误处理:
- ESP32-C3 的 I2C 控制器通常提供了错误处理机制,允许检测和处理通信中的错误,如冲突、丢失的应答等。
要使用 ESP32-C3 上的 I2C 资源,需要配置相应的引脚、初始化 I2C 控制器,然后编写代码来进行数据传输和通信。
二、主要函数
1. 配置驱动程序
使用 i2c_config_t结构体,
配置主机示例
int i2c_master_port = 0;
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO, // 配置 SDA 的 GPIO
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = I2C_MASTER_SCL_IO, // 配置 SCL 的 GPIO
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ, // 为项目选择频率
.clk_flags = 0, // 可选项,可以使用 I2C_SCLK_SRC_FLAG_* 标志来选择 I2C 源时钟
};
配置从机示例
int i2c_slave_port = I2C_SLAVE_NUM;
i2c_config_t conf_slave = {
.sda_io_num = I2C_SLAVE_SDA_IO, // 配置 SDA 的 GPIO
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = I2C_SLAVE_SCL_IO, // 配置 SCL 的 GPIO
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.mode = I2C_MODE_SLAVE,
.slave.addr_10bit_en = 0,
.slave.slave_addr = ESP_SLAVE_ADDR, // 项目从机地址
.slave.maximum_speed = I2C_SLAVE_MAX_SPEED // 预期的最大时钟速度
.clk_flags = 0, // 可选项,可以使用 I2C_SCLK_SRC_FLAG_* 标志来选择 I2C 源时钟
};
2. 源时钟配置
时钟源的配置过程如下:
- 确定所需的时钟源:根据应用需求,选择满足频率和能力要求的时钟源。
- 配置时钟分配器:根据所选的时钟源,配置时钟分配器以满足应用需求。例如,如果选择的是APB时钟,那么需要配置时钟分配器以满足APB时钟的要求。
- 设置时钟频率:根据应用需求,设置所需的时钟频率。例如,如果需要100,000的时钟频率,那么需要将相应的时钟频率值设置到相应的寄存器中。
- 设置时钟源:将所选的时钟源设置到相应的寄存器中,以便在进行I2C操作时使用。
3. 安装驱动程序
使用 i2c_driver_install()
安装驱动程序,主要设置:
- 端口号
- 主机 或 从机模式
4. 通信
下图是I2C 主机构建命令链接、向从机发送n字节的过程。
具体地说,过程是:
- 使用
i2c_cmd_link_crate()
创建一个命令链接 i2c_master_start()
启动位i2c_master_write_byte()
提供单字节作为调用此函数的实参- 用
i2c_master_write()
写一个或多个数据 i2c_master_stop()
停止- 调用
i2c_master_cmd_begin()
来执行命令链接 - 使用
i2c_cmd_link_delete()
释放命令链接使用的资源
下图是主机读取数据的命令链接过程示例:
5. 指示写入或读取数据
发送从机地址后(请参考上图中第 3 步),主机可以写入或从从机读取数据。
主机实际执行的操作信息存储在从机地址的最低有效位中。
因此,为了将数据写入从机,主机发送的命令链接应包含地址 (ESP_SLAVE_ADDR << 1) | I2C_MASTER_WRITE
,如下所示:
i2c_master_write_byte(cmd, (ESP_SLAVE_ADDR << 1) | I2C_MASTER_WRITE, ACK_EN);
同理,指示从从机读取数据的命令链接如下所示:
i2c_master_write_byte(cmd, (ESP_SLAVE_ADDR << 1) | I2C_MASTER_READ, ACK_EN);
二、实现步骤
1. 配置 I2C 总线:
确保 I2C 总线的配置正确,包括引脚定义、时钟速率等。
#define CONFIG_I2C_MASTER_SDA 21 // SDA引脚
#define CONFIG_I2C_MASTER_SCL 22 // SCL引脚
#define CONFIG_I2C_MASTER_FREQ_HZ 100000 // I2C时钟速率
2. 初始化 I2C 总线:
- 包含 ESP32 IDF 的 I2C 头文件。
- 使用 i2c_config_t 结构体来配置 I2C 总线。
- 使用 i2c_param_config() 初始化 I2C 总线。
i2c_config_t conf;
conf.mode = I2C_MODE_MASTER;
conf.sda_io_num = CONFIG_I2C_MASTER_SDA;
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
conf.scl_io_num = CONFIG_I2C_MASTER_SCL;
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
conf.master.clk_speed = CONFIG_I2C_MASTER_FREQ_HZ;
i2c_param_config(I2C_NUM_0, &conf);
i2c_driver_install(I2C_NUM_0, conf.mode, 0, 0, 0);
3. 与外部存储设备通信:
使用 i2c_cmd_handle_t 和 i2c_master_cmd_begin() 函数来发送 I2C 命令与外部存储设备通信。
需要根据外部存储设备的数据手册来构建正确的读写命令。
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (device_address << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN);
i2c_master_write_byte(cmd, register_address, ACK_CHECK_EN);
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_NUM_0, cmd, pdMS_TO_TICKS(1000));
i2c_cmd_link_delete(cmd);
4. 处理读写数据:
根据外部存储设备的规范,可以使用 i2c_master_read() 函数读取数据或使用 i2c_master_write() 函数写入数据。
处理读取的数据或根据需要进行写入操作。
5. 关闭 I2C 总线:
当完成与外部存储设备的通信后,使用 i2c_driver_delete() 函数关闭 I2C 总线。
i2c_driver_delete(I2C_NUM_0);
三、完整代码
1. i2c_util.h
#include <string.h>
#include <esp_err.h>
#include <driver/i2c.h>
#define EEPROM_ADDR 0x57
#define START_ADDR 0x25
#define BUF_SIZE 50
esp_err_t i2c_master_init(void);
void writeEEPROM(uint16_t address, uint8_t data);
uint8_t readEEPROM(uint16_t address);
2. i2c_util.c
#include "include/i2c_utils.h"
#include "esp_log.h"
static const char* TAG = "[i2c_utils]";
esp_err_t i2c_master_init(void)
{
i2c_config_t conf;
conf.mode = I2C_MODE_MASTER;
conf.sda_io_num = GPIO_NUM_5; // GPIO pin for SDA
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
conf.scl_io_num = GPIO_NUM_6; // GPIO pin for SCL
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
conf.master.clk_speed = 100000; // I2C clock frequency
conf.clk_flags = I2C_SCLK_SRC_FLAG_FOR_NOMAL;
i2c_param_config(I2C_NUM_0, &conf);
return i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0);
}
void writeEEPROM(uint16_t address, uint8_t data)
{
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (EEPROM_ADDR << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, address >> 8, true);
i2c_master_write_byte(cmd, address & 0xFF, true);
i2c_master_write_byte(cmd, data, true);
i2c_master_stop(cmd);
esp_err_t espRc = i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS);
if (espRc == ESP_OK) {
ESP_LOGI(TAG, "i2c configured successfully");
} else {
ESP_LOGE(TAG, "i2c configuration failed. code: 0x%.2X", espRc);
}
i2c_cmd_link_delete(cmd);
vTaskDelay(5 / portTICK_PERIOD_MS); // wait to complete the write cycle
}
uint8_t readEEPROM(uint16_t address)
{
uint8_t data;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (EEPROM_ADDR << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, address >> 8, true);
i2c_master_write_byte(cmd, address & 0xFF, true);
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (EEPROM_ADDR << 1) | I2C_MASTER_READ, true);
i2c_master_read_byte(cmd, &data, I2C_MASTER_LAST_NACK);
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
return data;
}
3. 读写示例
i2c_master_init();
printf("Writing to Ext.EEPROM...\n");
for (uint16_t i = 0; i < sizeof(myMessage); i++)
{
writeEEPROM(START_ADDR + i, myMessage[i]);
vTaskDelay(5 / portTICK_PERIOD_MS);
}
printf("Write Complete.\n");
printf("Reading Ext.EEPROM...\n");
for (uint16_t j = 0; j < sizeof(MEM); j++){
uint8_t data = readEEPROM(START_ADDR + j);
printf("Address 0x%02X: 0x%02X '", START_ADDR + j, data);
if (data >= 32 && data <= 126){
printf("%c", (char)data);
} else{
printf(".");
}
printf("'\n");
}
printf("Read Complete.\n");