I2C master and slave
Posted: Wed Jun 25, 2025 1:17 pm
I want to connect two ESP32S3 devices with an I2C bus. In the future I want to be able to add another ESP32S3 to the bus as a slave.
I am using the IDF 5.2.2 toolchain.
The only examples that are out there use the old API, and I made some code that when it connects a master and a slave it works. The master has this code:
With the right delay and timeout timing, the master communicates correctly with the slave.
The slave has this code:
When I add a new device, the slave device buffers fill up with data not addressed to them. The master correctly writes the address of the destination device. This creates buffer alignment problems in the slaves.
Note that in the master I need to do an additional read to empty the fifo buffer, i2c_reset_rx_fifo and i2c_reset_tx_fifo do not work.
Also in the slave to empty the fifo buffer I do not use i2c_reset_rx_fifo and i2c_reset_tx_fifo, I have to use i2c_slave_read_buffer.
Anyway, with the correct timeout and wait times, the communication between master and slave works.
However, if I add a new device, and make the master do a write and a read per device, both slaves fill up with data, even if the master correctly sets the target address.
I finally tried to modify the slave with the new API, hoping to be able to use the callback events to handle the buffer clearing: in the routine call
an error is raised in the find_desc_for_source function in the intr_alloc.c source.
Has anyone already connected three ESP32S3s to I2C communication, and can provide me with some sample code or tell me what I'm doing wrong? Do the new I2C APIs work?
I am using the IDF 5.2.2 toolchain.
The only examples that are out there use the old API, and I made some code that when it connects a master and a slave it works. The master has this code:
Code: Select all
const i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = I2C_MASTER_SCL_IO,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
// .clk_flags = 0, /*!< Optional, you can use I2C_SCLK_SRC_FLAG_* flags to choose i2c source clock here. */
};
static esp_err_t i2c_master_init(void) {
esp_err_t err = i2c_param_config(i2c_master_port, &conf);
if (err!=ESP_OK) {
return err;
}
return i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
}
static esp_err_t i2c_master_read_slave(i2c_port_t i2c_num, uint8_t slave_addr, uint8_t* data_rd, size_t size, uint16_t timeout_ms) {
if (size==0) {
return ESP_OK;
}
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (slave_addr<<1)|READ_BIT, ACK_CHECK_EN);
if (size>1) {
i2c_master_read(cmd, data_rd, size-1, ACK_VAL);
}
i2c_master_read_byte(cmd, data_rd+size-1, NACK_VAL);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, pdMS_TO_TICKS(timeout_ms));
i2c_cmd_link_delete(cmd);
return ret;
}
static esp_err_t i2c_master_write_slave(i2c_port_t i2c_num, uint8_t slave_addr, uint8_t* data_wr, size_t size, uint16_t timeout_ms) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (slave_addr<<1)|WRITE_BIT, ACK_CHECK_EN);
i2c_master_write(cmd, data_wr, size, ACK_CHECK_EN);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, pdMS_TO_TICKS(timeout_ms));
i2c_cmd_link_delete(cmd);
return ret;
}
void app_main(void) {
i2c_master_init();
int ret;
uint8_t slaveAddr = ESP_SLAVE_ADDR2;
while (1) {
vTaskDelay(pdMS_TO_TICKS(RIT1));
memset(master_rx_data, 0, I2C_SLAVE_RX_BUF_LEN);
ret = i2c_master_read_slave(i2c_master_port, slaveAddr, master_rx_data, RW_TEST_LENGTH, TIMEOUT);
if (ret==ESP_ERR_TIMEOUT) {
ESP_LOGE(TAG, "I2C timeout");
}
else if (ret==ESP_OK) {
ESP_LOGI(TAG, "Read correct slave 0x%X", slaveAddr);
}
else {
ESP_LOGE(TAG, "Master read slave error, IO not connected... slave 0x%X", slaveAddr);
}
uint16_t crc = crc16(0xFFFF, master_rx_data, RW_TEST_LENGTH-2);
uint16_t crc_rx_buf = *((uint16_t *)&master_rx_data[RW_TEST_LENGTH-2]);
if (crc_rx_buf!=crc) {
ESP_LOGE(TAG, "CRC error!!!! slave 0x%X", slaveAddr);
ret = i2c_master_read_slave(i2c_master_port, slaveAddr, master_rx_data, RW_TEST_LENGTH, TIMEOUT);
}
else {
ESP_LOGI(TAG, "CRC correct slave 0x%X", slaveAddr);
}
vTaskDelay(pdMS_TO_TICKS(RIT2));
for (int i = 0; i<RW_TEST_LENGTH; i++) {
master_wr_data[i] = i+10;
}
crc = crc16(0xFFFF, master_wr_data, RW_TEST_LENGTH-2);
uint16_t *crc_in_buf = (uint16_t *)&master_wr_data[RW_TEST_LENGTH-2];
*crc_in_buf = crc;
ESP_LOGI(TAG, "Write on slave slave 0x%X", slaveAddr);
ret = i2c_master_write_slave(i2c_master_port, slaveAddr, master_wr_data, RW_TEST_LENGTH, TIMEOUT);
}
}
The slave has this code:
Code: Select all
const i2c_config_t conf_slave = {
.sda_io_num = I2C_SLAVE_SDA_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = I2C_SLAVE_SCL_IO,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.mode = I2C_MODE_SLAVE,
.slave.addr_10bit_en = 0,
.slave.slave_addr = ESP_SLAVE_ADDR,
.clk_flags = 0,
};
static esp_err_t i2c_slave_init(void) {
esp_err_t err = i2c_param_config(i2c_slave_port, &conf_slave);
if (err!=ESP_OK) {
return err;
}
return i2c_driver_install(i2c_slave_port, conf_slave.mode, I2C_SLAVE_RX_BUF_LEN, I2C_SLAVE_TX_BUF_LEN, 0);
}
void app_main(void) {
i2c_slave_init();
ESP_LOGI(TAG, "I2C Slave initialized successfully");
while (1) {
// TX: prepara i dati da inviare
for (int i = 0; i<RW_TEST_LENGTH; i++) {
slave_wr_data[i] = i;
}
uint16_t crc = crc16(0xFFFF, slave_wr_data, RW_TEST_LENGTH-2);
*(uint16_t *)&slave_wr_data[RW_TEST_LENGTH-2] = crc;
size_t d_size = i2c_slave_write_buffer(i2c_slave_port, slave_wr_data, RW_TEST_LENGTH, pdMS_TO_TICKS(TIMEOUT));
if (d_size==0) {
ESP_LOGW(TAG, "TX buffer full (scrittura ignorata) slave 0x%X", ESP_SLAVE_ADDR);
}
int size = i2c_slave_read_buffer(i2c_slave_port, slave_rx_data, I2C_SLAVE_RX_BUF_LEN, pdMS_TO_TICKS(TIMEOUT));
if (size==RW_TEST_LENGTH) {
uint16_t crc_rx_buf = *((uint16_t *)&slave_rx_data[RW_TEST_LENGTH-2]);
crc = crc16(0xFFFF, slave_rx_data, RW_TEST_LENGTH-2);
if (crc_rx_buf!=crc) {
ESP_LOGE(TAG, "CRC Error!!!! slave 0x%X", ESP_SLAVE_ADDR);
while (i2c_slave_read_buffer(i2c_slave_port, dump_buf, sizeof(dump_buf), TIMEOUT_ERR)>0) {
ESP_LOGW(TAG, "Empty buffer slave 0x%X", ESP_SLAVE_ADDR);
}
}
else {
ESP_LOGI(TAG, "CRC correct, dati ok slave 0x%X", ESP_SLAVE_ADDR);
}
}
else if (size>0) {
ESP_LOGW(TAG, "Unhexpected length: %d slave 0x%X", size, ESP_SLAVE_ADDR);
while (i2c_slave_read_buffer(i2c_slave_port, dump_buf, sizeof(dump_buf), TIMEOUT_ERR)>0) {
ESP_LOGW(TAG, "Empty buffer (unhexpected length) slave 0x%X", ESP_SLAVE_ADDR);
}
}
else {
ESP_LOGW(TAG, "No received data or timeout slave 0x%X", ESP_SLAVE_ADDR);
}
memset(slave_rx_data, 0, sizeof(slave_rx_data));
vTaskDelay(pdMS_TO_TICKS(RIT));
}
}
Note that in the master I need to do an additional read to empty the fifo buffer, i2c_reset_rx_fifo and i2c_reset_tx_fifo do not work.
Also in the slave to empty the fifo buffer I do not use i2c_reset_rx_fifo and i2c_reset_tx_fifo, I have to use i2c_slave_read_buffer.
Anyway, with the correct timeout and wait times, the communication between master and slave works.
However, if I add a new device, and make the master do a write and a read per device, both slaves fill up with data, even if the master correctly sets the target address.
I finally tried to modify the slave with the new API, hoping to be able to use the callback events to handle the buffer clearing: in the routine call
Code: Select all
i2c_slave_config_t i2c_slv_config = {
.addr_bit_len = I2C_ADDR_BIT_LEN_7,
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = I2C_NUM_0,
.send_buf_depth = 256,
.scl_io_num = I2C_SLAVE_SCL_IO,
.sda_io_num = I2C_SLAVE_SDA_IO,
.slave_addr = ESP_SLAVE_ADDR,
.intr_priority = 1
};
i2c_new_slave_device(&i2c_slv_config, &slave_handle);Has anyone already connected three ESP32S3s to I2C communication, and can provide me with some sample code or tell me what I'm doing wrong? Do the new I2C APIs work?