How to recover from I2C failure?
Posted: Thu Oct 12, 2017 4:21 pm
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 Zoomed out on the normal read
Read when lines stop working
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.
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.
I can reproduce the problem by quickly and briefly grounding one of the lines.
Normal Read over i2c Zoomed out on the normal read
Read when lines stop working
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;
}
}
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();
}
}
}