ESP32 I2C Slave
概述
这篇文章将介绍使用 ESP32 作为 I2C 实现 Random Read/Write 和 Sequential Read/Write 时序。
首先通过下面的图了解下 Random Read 时序,I2C Master 通过这个时序读取任意数据地址开始的数据。
___________________________________________________________________________________________________________________________________________________
| start | slave_addr + write_bit + ack | data address |start | slave_addr + read_bit + ack | read n-1 bytes + ack | read 1 byte + nack | stop |
RANDOM READ: A random read requires a “dummy” byte write sequence to load in the data word address. Once the device address word and data word address are clocked in and acknowledged by the EEPROM, the microcontroller must generate another start condition.
The microcontroller now initiates a current address read by sending a device address with the read/write select bit high. The EEPROM acknowledges the device address and serially clocks out the data word. The microcontroller does not respond with a zero but does generate a following stop condition
- 现有的 I2C Slave 无法实现类似 Random Read 时序
- 原因分析:esp-idf 提供的
i2c_slave_write_buffer
i2c_slave_read_buffer
API 都是操作 RingBuffer 实现,而 Random Read 需要 Slave 在 接收到slave_addr + read_bit + data address
前将数据放入 I2C 的硬件 FIFO 中。若是通过 API 进行判断当前 Master 想要操作的 数据地址,会因为这个 API 都是操作 RingBuffer 而有所延迟,导致 Master 接收到错误的数据(因为此时硬件 FIFO 还没有数据)。 - 解决办法:
- 在接收到
slave_addr + write_bit + data address
时将可能需要发送到主机的数据放入 TX FIFO 中,当主机继续发送slave_addr + read_bit + data address
并 提供读数据时钟 时,I2C Slave 硬件会将 TX FIFO 中的数据发送到 Master。若主机不再发送slave_addr + read_bit + data address
,就将 FIFO 清空避免在之后的操作中造成错误。 - 通过自定义中断处理程序,在相应的中断中进行处理
- 在接收到
- 原因分析:esp-idf 提供的
I2C 中断介绍
- 想要针对某个 I2C 或者 某种模式,使用自定义的中断处理程序,需要修改
esp-idf/components/driver/i2c.c
中的代码,这里仅仅针对 master 使用驱动中提供的中断处理程序,当模式为 Slave 时,使用自定义的中断处理程序。
在 release/3.2 中,无法直接使用
i2c_isr_register
覆盖之前的中断处理程序。通过简单修改驱动源文件,并在应用程序中调用i2c_isr_register
实现使用自定义的中断处理程序。
修改 esp-idf/components/driver/i2c.c
文件中 i2c_driver_install
函数中的这段代码:
if (mode == I2C_MODE_MASTER) {
//hook isr handler
i2c_isr_register(i2c_num, i2c_isr_handler_default, p_i2c_obj[i2c_num], intr_alloc_flags, &p_i2c_obj[i2c_num]->intr_handle);
}
在应用程序中使用自定义的中断处理程序:
static void IRAM_ATTR i2c_slave_isr_handler_default(void* arg)
{
…
…
…
}
/**
* @brief i2c slave initialization
*/
static esp_err_t i2c_slave_init()
{
int i2c_slave_port = I2C_SLAVE_NUM;
i2c_config_t conf_slave;
conf_slave.sda_io_num = I2C_SLAVE_SDA_IO;
conf_slave.sda_pullup_en = GPIO_PULLUP_ENABLE;
conf_slave.scl_io_num = I2C_SLAVE_SCL_IO;
conf_slave.scl_pullup_en = GPIO_PULLUP_ENABLE;
conf_slave