I2C master and slave

maurizio.scian
Posts: 26
Joined: Mon Jun 26, 2023 8:09 am

I2C master and slave

Postby maurizio.scian » 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:

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);
	}
}
With the right delay and timeout timing, the master communicates correctly with the slave.
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));
	}
}
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

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);
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?

maurizio.scian
Posts: 26
Joined: Mon Jun 26, 2023 8:09 am

Re: I2C master and slave

Postby maurizio.scian » Thu Jun 26, 2025 3:35 pm

Ok, this solution works, but i need 2 read operation instead 1, because i2c_reset_rx_fifo is useless. In the following code, always the loop end with 1 retry. The following code works with 2 I2C slaves, with a CRC16 check.
Write 1Kb from master to slave, and read 1Kb from slave to master. With 400KHz transfer 1Kb in 70ms. Every device update (read and write 1Kb) is done in 360ms (2 read operation and 1 write operation, with a little delay 50ms between each operation).
Code for master:

Code: Select all

#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "driver/i2c.h"
static const char *TAG = "i2c-master";

#define I2C_MASTER_SCL_IO 11					/*!< gpio number for I2C master clock */
#define I2C_MASTER_SDA_IO 10					/*!< gpio number for I2C master data  */
#define I2C_MASTER_FREQ_HZ 400000				/*!< I2C master clock frequency */
#define I2C_SLAVE_TX_BUF_LEN 2048				/*!< I2C master doesn't need buffer */
#define I2C_SLAVE_RX_BUF_LEN 2048				/*!< I2C master doesn't need buffer */
#define I2C_MASTER_TX_BUF_DISABLE 0             /*!< I2C master doesn't need buffer */
#define I2C_MASTER_RX_BUF_DISABLE 0             /*!< I2C master doesn't need buffer */
#define ESP_SLAVE_ADDR1 0x0A
#define ESP_SLAVE_ADDR2 0x0B

#define WRITE_BIT I2C_MASTER_WRITE              /*!< I2C master write */
#define READ_BIT I2C_MASTER_READ                /*!< I2C master read */
#define ACK_CHECK_EN 0x1                        /*!< I2C master will check ack from slave*/
#define ACK_CHECK_DIS 0x0                       /*!< I2C master will not check ack from slave */
#define ACK_VAL 0x0                             /*!< I2C ack value */
#define NACK_VAL 0x1                            /*!< I2C nack value */

#define RW_TEST_LENGTH                     1024              /*!<Data length for r/w test, any value from 0-DATA_LENGTH*/

int i2c_master_port = I2C_NUM_0;
int8_t  master_rx_data[I2C_SLAVE_RX_BUF_LEN] = { 0 };
uint8_t  master_wr_data[I2C_SLAVE_RX_BUF_LEN] = { 0 };

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;
	uint16_t crc = 0;
	while (1) {
		slaveAddr = (slaveAddr == ESP_SLAVE_ADDR1?ESP_SLAVE_ADDR2:ESP_SLAVE_ADDR1);
		vTaskDelay(pdMS_TO_TICKS(50));
		
		//<<<<<SET YOUR WRITE BUFFER HERE>>>>>
		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 slave %d", slaveAddr);
		ret = i2c_master_write_slave(i2c_master_port, slaveAddr, master_wr_data, RW_TEST_LENGTH, pdMS_TO_TICKS(10000));
		
		ESP_LOGI(TAG, "Write slave %d ret %d", slaveAddr, ret);

		bool exit = false;
		uint8_t crc_err_retry = 0;
		while (!exit) {
			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, pdMS_TO_TICKS(10000));
			if (ret==ESP_ERR_TIMEOUT) {
				ESP_LOGE(TAG, "I2C timeout");
			}
			else if (ret==ESP_OK) {
				ESP_LOGI(TAG, "Read correct slave %d", slaveAddr);
			}
			else {
				ESP_LOGE(TAG, "Master read slave error, IO not connected... slave %d", slaveAddr);
			}
			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) {
				if (crc_err_retry<3) {
					crc_err_retry++;
				}
				else {
					ESP_LOGE(TAG, "CRC ERROR!!!! slave %d", slaveAddr);
					exit = true;
				}
			}
			else {
				ESP_LOGI(TAG, "CRC correct slave %d retries: %d", slaveAddr, crc_err_retry);
				//<<<<<USE YOUR READ BUFFER HERE>>>>>
				exit = true;
			}
		}
	}
}
Slave code:

Code: Select all

#define RW_TEST_LENGTH                     1024              /*!<Data length for r/w test, any value from 0-DATA_LENGTH*/

#define I2C_SLAVE_TX_BUF_LEN (2*RW_TEST_LENGTH)
#define I2C_SLAVE_RX_BUF_LEN (2*RW_TEST_LENGTH)
#define ESP_SLAVE_ADDR1 0x0A
#define ESP_SLAVE_ADDR2 0x0B
#define ESP_SLAVE_ADDR					ESP_SLAVE_ADDR2
#define I2C_SLAVE_SCL_IO 36					/*!< gpio number for I2C slave clock */
#define I2C_SLAVE_SDA_IO 35					/*!< gpio number for I2C slave data  */

uint8_t slave_rx_data[I2C_SLAVE_RX_BUF_LEN] = { 0 };
uint8_t slave_wr_data[I2C_SLAVE_RX_BUF_LEN] = { 0 };

const i2c_config_t conf_slave = {
	.sda_io_num = I2C_SLAVE_SDA_IO,
	// select GPIO specific to your project
	.sda_pullup_en = GPIO_PULLUP_ENABLE,
	.scl_io_num = I2C_SLAVE_SCL_IO,
	// select GPIO specific to your project
	.scl_pullup_en = GPIO_PULLUP_ENABLE,
	.mode = I2C_MODE_SLAVE,
	.slave.addr_10bit_en = 0,
	.slave.slave_addr = ESP_SLAVE_ADDR,
	// address of your project
	.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) {
	ESP_ERROR_CHECK(i2c_slave_init());
	uint16_t crc = 0;
	while (1) {
		int size = i2c_slave_read_buffer(i2c_slave_port, slave_rx_data, RW_TEST_LENGTH, pdMS_TO_TICKS(1000));

		uint16_t sizeExtra = 0;
		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 %d", ESP_SLAVE_ADDR);
				do {
					ESP_LOGW(TAG, "Empty residual data slave %d", ESP_SLAVE_ADDR);
					sizeExtra = i2c_slave_read_buffer(i2c_slave_port, slave_rx_data, sizeof(slave_rx_data), pdMS_TO_TICKS(50));
					ESP_LOGW(TAG, "Empty %d bytes from slave %d, ", sizeExtra, ESP_SLAVE_ADDR);
				} while (sizeExtra != 0);
			}
			else {
				ESP_LOGI(TAG, "CRC correct, slave %d", ESP_SLAVE_ADDR);
				//<<<<<USE YOUR READ BUFFER HERE>>>>>
			}
		}
		else if (size>0) {
			ESP_LOGW(TAG, "Unexpected packet size: %d slave 0x%X", size, ESP_SLAVE_ADDR);
			do {
				ESP_LOGW(TAG, "Empty residual data slave %d", ESP_SLAVE_ADDR);
				sizeExtra = i2c_slave_read_buffer(i2c_slave_port, slave_rx_data, sizeof(slave_rx_data), pdMS_TO_TICKS(50));
				ESP_LOGW(TAG, "Empty %d bytes da slave %d, ", sizeExtra, ESP_SLAVE_ADDR);
			} while (sizeExtra!=0);
		}
		else {
			ESP_LOGW(TAG, "No data received or timeout slave %d", ESP_SLAVE_ADDR);
		}
		memset(slave_rx_data, 0, sizeof(slave_rx_data));
		
		//<<<<<SET YOUR WRITE BUFFER HERE>>>>>
		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(1000));
		if (d_size==0) {
			ESP_LOGW(TAG, "TX buffer full (write ignored) slave %d", ESP_SLAVE_ADDR);
		}
	}
}

Who is online

Users browsing this forum: Applebot, Baidu [Spider], Bing [Bot], ChatGPT-User, Qwantbot and 4 guests