Page 1 of 1

How to recover from I2C failure?

Posted: Thu Oct 12, 2017 4:21 pm
by gregstewart90
I'm using a MCP23017 port expander which uses i2c. Occasionally I get a problem on the lines and the timeout is reached. This throws an error that I read. I simply reset the esp32 and then it starts working again. I've upgraded my IDF to the latest version, and now the reset no longer works. When it reboots, the problem still exists. I can't revert back to release 2.1 as it contains a core dump error. Most of the time when it stops working, both SDA and SCL remain high. I have seen SCL clock continuously and SDA go from high to low at a slightly slower frequency than SCL.

I can reproduce the problem by quickly and briefly grounding one of the lines.

Normal Read over i2c
Screen Shot 2017-10-12 at 10.07.12 AM.png
Screen Shot 2017-10-12 at 10.07.12 AM.png (28.38 KiB) Viewed 7036 times
Zoomed out on the normal read
Screen Shot 2017-10-12 at 10.06.56 AM.png
Screen Shot 2017-10-12 at 10.06.56 AM.png (22.04 KiB) Viewed 7036 times
Read when lines stop working
Screen Shot 2017-10-12 at 10.06.35 AM.png
Screen Shot 2017-10-12 at 10.06.35 AM.png (22.45 KiB) Viewed 7036 times

I've tried deleting the driver with i2c_driver_delete and then reinitializing it. I've tried changing the frequency on the clock. I've tried reseting rx and tx fifo with i2c_reset_rx_fifo and i2c_reset_tx_fifo. I've tried lowering the pins in software before reinitializing i2c. The only thing that works is reseting the device physically.

Below I have some of my code.

Code: Select all

void initI2C() {
	i2c_config_t conf;
	conf.mode = I2C_MODE_MASTER;
	conf.sda_io_num = I2C_SDA;
	conf.scl_io_num = I2C_SCL;
	conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
	conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
	conf.master.clk_speed = 400000;
	ESP_ERROR_CHECK(i2c_param_config(I2C_NUM, &conf));
	ESP_ERROR_CHECK(i2c_driver_install(I2C_NUM, I2C_MODE_MASTER, 0, 0, 0));
	ESP_ERROR_CHECK(i2c_driver_delete(I2C_NUM));
	ESP_ERROR_CHECK(i2c_driver_install(I2C_NUM, I2C_MODE_MASTER, 0, 0, 0));
}

void mcp23017_local_read(uint8_t MCP23017_ADDRESS, uint8_t* data_h){

	i2c_cmd_handler = i2c_cmd_link_create();
	i2c_master_start(i2c_cmd_handler);
	i2c_master_write_byte(i2c_cmd_handler, MCP23017_ADDRESS << 1 | READ_BIT, ACK_CHECK_EN);
	i2c_master_read_byte(i2c_cmd_handler, data_h, NACK_VAL);
	i2c_master_stop(i2c_cmd_handler);
	int ret = i2c_master_cmd_begin(I2C_NUM, i2c_cmd_handler, 100 / portTICK_RATE_MS);
	i2c_cmd_link_delete(i2c_cmd_handler);
	if (ret != 0) {
		printf("ERROR-lc\n");
		i2c_stuck=1;
	}
}

void mcp23017_local_write(uint8_t MCP23017_ADDRESS, uint8_t command){

	vTaskDelay(30 / portTICK_RATE_MS);
	i2c_cmd_handler = i2c_cmd_link_create();
	i2c_master_start(i2c_cmd_handler);
	i2c_master_write_byte(i2c_cmd_handler, MCP23017_ADDRESS << 1 | WRITE_BIT, ACK_CHECK_EN);
	i2c_master_write_byte(i2c_cmd_handler, command, ACK_CHECK_EN);// Access IODIRB
	i2c_master_stop(i2c_cmd_handler);
	int ret = i2c_master_cmd_begin(I2C_NUM, i2c_cmd_handler, 100 / portTICK_RATE_MS);
	i2c_cmd_link_delete(i2c_cmd_handler);
	if (ret == ESP_FAIL) {
		printf("ERROR-lw\n");
		i2c_stuck=1;
	}
}

uint8_t mcp23017_read(uint8_t MCP23017_ADDRESS, uint8_t command){
	uint8_t data_h;
	mcp23017_local_write(MCP23017_ADDRESS, command);
	vTaskDelay(30 / portTICK_RATE_MS);
	mcp23017_local_read(MCP23017_ADDRESS, &data_h);
	vTaskDelay(30 / portTICK_RATE_MS);
	return data_h;
}

void mcp23017_duel_write(uint8_t MCP23017_ADDRESS, uint8_t command, uint8_t command2){

	i2c_cmd_handler = i2c_cmd_link_create();
	i2c_master_start(i2c_cmd_handler);
	i2c_master_write_byte(i2c_cmd_handler, MCP23017_ADDRESS << 1 | WRITE_BIT, ACK_CHECK_EN);
	i2c_master_write_byte(i2c_cmd_handler, command, ACK_CHECK_EN);// Access IODIRB
	i2c_master_write_byte(i2c_cmd_handler, command2, ACK_CHECK_EN);// Write Register
	i2c_master_stop(i2c_cmd_handler);
	int ret = i2c_master_cmd_begin(I2C_NUM, i2c_cmd_handler, 100 / portTICK_RATE_MS);

	i2c_cmd_link_delete(i2c_cmd_handler);
	if (ret == ESP_FAIL) {
		printf("ERROR-dw\n");
		i2c_stuck=1;
	}
}
I've see the i2cInitFix function from the arduino-esp32 by espressif, but I can't find anything on the esp-idf that works.

EDIT:
Below I have code that can be tested with a mcp23017. This will output the value of the mcp23017. If you pull the SDA line low, it will immediately fail. You have to hold the SCL line low for a couple of seconds and then it starts to fail. When mcp_local_read times out, i2cStuck is set as 1. This will call reset_i2c() in the main loop.

Code: Select all

#include "nvs_flash.h"
#include "driver/i2c.h"

#define ACK_CHECK_EN   0x1
#define I2C_MASTER_NUM I2C_N   /*!< I2C port number for master dev */
#define I2C_MASTER_FREQ_HZ    1700000     /*!< I2C master clock frequency */
#define I2C_MASTER_TX_BUF_DISABLE   0   /*!< I2C master do not need buffer */
#define I2C_MASTER_RX_BUF_DISABLE   0   /*!< I2C master do not need buffer */
#define WRITE_BIT  I2C_MASTER_WRITE /*!< I2C master write */
#define READ_BIT   I2C_MASTER_READ  /*!< I2C master read */
#define ACK_VAL    0x0         /*!< I2C ack value */
#define NACK_VAL   0x1         /*!< I2C nack value */

#define MCP_Relay_Address 0x20 /*!< I2C address of mcp on relays */
#define MCP_Input_Address 0x21 /*!< I2C address of mcp on relays */
#define IODIRA 0x00 /*!< Set direction on bank A */
#define IODIRB 0x01 /*!< Set direction on bank B */
#define GPIOA 0x12 /*!< GPIO register on bank A */
#define GPIOB 0x13 /*!< GPIO register on bank B */
#define GPPUA 0x0C /*!< Pull-up resistor register on bank A */
#define GPPUB 0x0D /*!< Pull-up resistor register on bank B */


char i2cStuck;
char I2C_N;
int I2C_SDA_GPIO = 4;
int I2C_SCL_GPIO = 5;
i2c_cmd_handle_t i2c_cmd_handler;




void initI2C() {
	i2c_config_t conf;
	conf.mode = I2C_MODE_MASTER;
	conf.sda_io_num = I2C_SDA_GPIO;
	conf.scl_io_num = I2C_SCL_GPIO;
	conf.sda_pullup_en = GPIO_PULLUP_DISABLE;
	conf.scl_pullup_en = GPIO_PULLUP_DISABLE;
	conf.master.clk_speed = 100000;
	ESP_ERROR_CHECK(i2c_param_config(I2C_N, &conf));
	ESP_ERROR_CHECK(i2c_driver_install(I2C_N, I2C_MODE_MASTER, 0, 0, 0));
	ESP_ERROR_CHECK(i2c_driver_delete(I2C_N));
	ESP_ERROR_CHECK(i2c_driver_install(I2C_N, I2C_MODE_MASTER, 0, 0, 0));
}

void mcp_local_read(uint8_t MCP_ADDRESS, uint8_t* data_h){

	i2c_cmd_handler = i2c_cmd_link_create();
	i2c_master_start(i2c_cmd_handler);
	i2c_master_write_byte(i2c_cmd_handler, MCP_ADDRESS << 1 | READ_BIT, ACK_CHECK_EN);
	i2c_master_read_byte(i2c_cmd_handler, data_h, NACK_VAL);
	i2c_master_stop(i2c_cmd_handler);
	int ret = i2c_master_cmd_begin(I2C_N, i2c_cmd_handler, 100 / portTICK_RATE_MS);
	//	printf("ret:%d-%d\n", ret, ESP_ERR_TIMEOUT);
	i2c_cmd_link_delete(i2c_cmd_handler);
	if (ret != 0) {
		printf("ERROR-lc\n");
		i2cStuck=1;
	}else{
		i2cStuck=0;
	}
}

void mcp_local_write(uint8_t MCP_ADDRESS, uint8_t command){

	vTaskDelay(30 / portTICK_RATE_MS);
	i2c_cmd_handler = i2c_cmd_link_create();
	i2c_master_start(i2c_cmd_handler);
	i2c_master_write_byte(i2c_cmd_handler, MCP_ADDRESS << 1 | WRITE_BIT, ACK_CHECK_EN);
	i2c_master_write_byte(i2c_cmd_handler, command, ACK_CHECK_EN);// Access IODIRB
	i2c_master_stop(i2c_cmd_handler);
	int ret = i2c_master_cmd_begin(I2C_N, i2c_cmd_handler, 100 / portTICK_RATE_MS);
	i2c_cmd_link_delete(i2c_cmd_handler);
	if (ret == ESP_FAIL) {
		printf("ERROR-lw\n");
	}
}

void mcp_duel_write(uint8_t MCP_ADDRESS, uint8_t command, uint8_t command2){

	i2c_cmd_handler = i2c_cmd_link_create();
	i2c_master_start(i2c_cmd_handler);
	i2c_master_write_byte(i2c_cmd_handler, MCP_ADDRESS << 1 | WRITE_BIT, ACK_CHECK_EN);
	i2c_master_write_byte(i2c_cmd_handler, command, ACK_CHECK_EN);// Access IODIRB
	i2c_master_write_byte(i2c_cmd_handler, command2, ACK_CHECK_EN);// Write Register
	i2c_master_stop(i2c_cmd_handler);
	int ret = i2c_master_cmd_begin(I2C_N, i2c_cmd_handler, 100 / portTICK_RATE_MS);

	i2c_cmd_link_delete(i2c_cmd_handler);
	if (ret == ESP_FAIL) {
		printf("ERROR-dw\n");
	}
}

uint8_t mcp_read(uint8_t MCP_ADDRESS, uint8_t command){
	uint8_t data_h;
	mcp_local_write(MCP_ADDRESS, command);
	vTaskDelay(30 / portTICK_RATE_MS);
	mcp_local_read(MCP_ADDRESS, &data_h);
	vTaskDelay(30 / portTICK_RATE_MS);
	return data_h;
}


void reset_i2c(void){
	i2c_driver_delete(I2C_N);
//	i2c_reset_tx_fifo(I2C_N);
//	i2c_reset_rx_fifo(I2C_N);
	initI2C();
//	esp_restart();
}


void app_main(void)
{
	nvs_flash_init();
	i2cStuck = 0;
	I2C_N = I2C_NUM_0;
	initI2C();

	mcp_duel_write(MCP_Relay_Address, IODIRA, 0x00);// Initialize MCP on Relays
	mcp_duel_write(MCP_Relay_Address, IODIRB, 0x00);

	mcp_duel_write(MCP_Input_Address, IODIRA, 0xFF);// Initialize MCP on Relays
	mcp_duel_write(MCP_Input_Address, IODIRB, 0xFF);

	mcp_duel_write(MCP_Input_Address, GPPUA, 0xfF);
	mcp_duel_write(MCP_Input_Address, GPPUB, 0xFF);

	while(1){
		uint8_t GPIOA_VALUE=mcp_read(MCP_Input_Address, GPIOA);
		printf("GPIOA: %02x\n", GPIOA_VALUE);

		vTaskDelay(1000 / portTICK_PERIOD_MS);
		if(i2cStuck){
			reset_i2c();
		}
	}
}

Re: How to recover from I2C failure?

Posted: Thu Oct 12, 2017 10:29 pm
by WiFive

Re: How to recover from I2C failure?

Posted: Thu Oct 12, 2017 10:50 pm
by gregstewart90
THANK YOU THANK YOU!

Code: Select all

#include "driver/periph_ctrl.h"
void reset_i2c(void){

	i2c_reset_tx_fifo(I2C_N);
	i2c_reset_rx_fifo(I2C_N);
	periph_module_disable(PERIPH_I2C0_MODULE);
	periph_module_enable(PERIPH_I2C0_MODULE);
	i2c_driver_delete(I2C_N);
	init_I2C();
}
This worked perfectly for me without any resetting. I'm not sure the i2c_reset_tx_fifo and i2c_reset_rx_fifo are necessary. It works without them, but I kept them since the arduino i2cInitFix function used them.

EDIT:

This actually only works when the SDA line is pulled low. If the SCL line is pulled low, it still times out and gives bad data.

EDIT 2:

It doesn't always cause the issue on the SCL line, but most of the time. If I ground it again, it starts working. Still not sure what else it needs to totally work in software.