这篇文章将介绍使用 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_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/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.mode = I2C_MODE_SLAVE;
conf_slave.slave.addr_10bit_en = 0;
conf_slave.slave.slave_addr = ESP_SLAVE_ADDR;
i2c_param_config(i2c_slave_port, &conf_slave);
i2c_driver_install(i2c_slave_port, conf_slave.mode, I2C_SLAVE_RX_BUF_LEN, I2C_SLAVE_TX_BUF_LEN, 0);
if (i2c_isr_register(I2C_SLAVE_NUM, i2c_slave_isr_handler_default, NULL, 0, NULL) != ESP_OK) {
ESP_LOGE(TAG, "i2c_isr_register error");
return ESP_FAIL;
}
return ESP_OK;
}
需要使用的几个中断:
static void IRAM_ATTR i2c_slave_isr_handler_default(void* arg)
{
int i2c_num = I2C_SLAVE_NUM;
uint32_t status = I2C_INSTANCE[i2c_num]->int_status.val;
int idx = 0;
portBASE_TYPE HPTaskAwoken = pdFALSE;
while (status != 0) {
status = I2C_INSTANCE[i2c_num]->int_status.val;
if (status & I2C_ACK_ERR_INT_ST_M) {
ets_printf("ae\n");
I2C_INSTANCE[i2c_num]->int_ena.ack_err = 0;
I2C_INSTANCE[i2c_num]->int_clr.ack_err = 1;
} else if (status & I2C_TRANS_COMPLETE_INT_ST_M) { // receive data after receive device address + W/R bit and data address
// ets_printf("tc, ");
I2C_INSTANCE[i2c_num]->int_clr.trans_complete = 1;
int rx_fifo_cnt = I2C_INSTANCE[i2c_num]->status_reg.rx_fifo_cnt;
if (I2C_INSTANCE[i2c_num]->status_reg.slave_rw) { // read, slave should to send
// ets_printf("R\n");
} else { // write, slave should to recv
// ets_printf("W ");
ets_printf("Slave Recv");
for (idx = 0; idx < rx_fifo_cnt; idx++) {
slave_data[w_r_index++] = I2C_INSTANCE[i2c_num]->fifo_data.data;
}
ets_printf("\n");
}
I2C_INSTANCE[i2c_num]->int_clr.rx_fifo_full = 1;
slave_event = SLAVE_IDLE;
} else if (status & I2C_SLAVE_TRAN_COMP_INT_ST_M) { // slave receive device address + W/R bit + data address
if (I2C_INSTANCE[i2c_num]->status_reg.slave_rw) { // read, slave should to send
ets_printf("sc, Slave Send\n");
} else { // write, slave should to recv
// ets_printf("sc W\n");
w_r_index = I2C_INSTANCE[i2c_num]->fifo_data.data;
switch (slave_event) {
case SLAVE_IDLE:
ets_printf("sc, I2W\n");
// reset tx fifo to avoid send last byte when master send read command next.
i2c_reset_tx_fifo(i2c_num);
slave_event = SLAVE_WRITE;
slave_send_index = w_r_index;
int tx_fifo_rem = I2C_FIFO_LEN - I2C_INSTANCE[i2c_num]->status_reg.tx_fifo_cnt;
for (size_t i = 0; i < tx_fifo_rem; i++) {
WRITE_PERI_REG(I2C_DATA_APB_REG(i2c_num), slave_data[slave_send_index++]);
}
I2C_INSTANCE[i2c_num]->int_ena.tx_fifo_empty = 1;
I2C_INSTANCE[i2c_num]->int_clr.tx_fifo_empty = 1;
break;
}
}
I2C_INSTANCE[i2c_num]->int_clr.slave_tran_comp = 1;
} else if (status & I2C_TXFIFO_EMPTY_INT_ST_M) {
ets_printf("tfe, ");
int tx_fifo_rem = I2C_FIFO_LEN - I2C_INSTANCE[i2c_num]->status_reg.tx_fifo_cnt;
if (I2C_INSTANCE[i2c_num]->status_reg.slave_rw) { // read, slave should to send
ets_printf("R\r\n");
for (size_t i = 0; i < tx_fifo_rem; i++) {
WRITE_PERI_REG(I2C_DATA_APB_REG(i2c_num), slave_data[slave_send_index++]);
}
I2C_INSTANCE[i2c_num]->int_ena.tx_fifo_empty = 1;
I2C_INSTANCE[i2c_num]->int_clr.tx_fifo_empty = 1;
} else { // write, slave should to recv
ets_printf("W\r\n");
}
} else if (status & I2C_RXFIFO_OVF_INT_ST_M) {
ets_printf("rfo\n");
I2C_INSTANCE[i2c_num]->int_clr.rx_fifo_ovf = 1;
} else if (status & I2C_RXFIFO_FULL_INT_ST_M) {
ets_printf("rff\n");
int rx_fifo_cnt = I2C_INSTANCE[i2c_num]->status_reg.rx_fifo_cnt;
for (idx = 0; idx < rx_fifo_cnt; idx++) {
slave_data[w_r_index++] = I2C_INSTANCE[i2c_num]->fifo_data.data;
}
I2C_INSTANCE[i2c_num]->int_clr.rx_fifo_full = 1;
} else {
// ets_printf("%x\n", status);
I2C_INSTANCE[i2c_num]->int_clr.val = status;
}
}
//We only need to check here if there is a high-priority task needs to be switched.
if(HPTaskAwoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
ESP32-I2C-Slave
I2C Specification