Following the post I have written about error handling the I2C communication between an STM32 microcontroller and a TC74 temperature sensor, I have implemented the same rules to talk to an I2C EEPROM, the 24LC01B. However, the same protocol applies to any memory chip using the I2C protocol. The code analysed below has been implemented as a library, in order to use it for various projects. First up, the initialisation of the STM32 I2C peripheral must take place. In the following init function, pins A8 and C9 are used for SCL and SDA respectively withe the I2C3 peripheral, operating at 100kHz. The internal pull-up resistors are used in order to avoid extra components on the board.

void I2C_Memory_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
I2C_InitTypeDef I2C_InitStruct;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C3, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOC, &GPIO_InitStruct);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_I2C3); //SCL
GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_I2C3); //SDA

I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStruct.I2C_OwnAddress1 = 0x00;
I2C_InitStruct.I2C_Ack = I2C_Ack_Disable;
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStruct.I2C_ClockSpeed = 100000;
I2C_DeInit(I2C3);
I2C_Init(I2C3, &I2C_InitStruct);
I2C_Cmd(I2C3, ENABLE); 
}

Next up, the read from memory function is implemented. The I2C_TIMEOUT_MAX is defined as the maximum clocks that the I2C peripheral will wait for response before returning an error and must be defined based on the board layout. After each I2C operation if there is a timeout, the function returns -1. Based on this code, a dedicated I2C protocol error handler function can be implemented.

int I2C_Memory_Read(I2C_TypeDef* I2Cx, uint8_t address)
{
uint32_t timeout = I2C_TIMEOUT_MAX;
uint8_t Data = 0;

I2C_GenerateSTART(I2Cx, ENABLE);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if ((timeout--) == 0) return -1;
}
I2C_Send7bitAddress(I2Cx, 0xA0, I2C_Direction_Transmitter);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if ((timeout--) == 0) return -1;
} 

I2C_SendData(I2Cx, address);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if ((timeout--) == 0) return -1;
}

I2C_GenerateSTART(I2Cx, ENABLE);
timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if ((timeout--) == 0) return -1;
}
I2C_Send7bitAddress(I2Cx, 0xA0, I2C_Direction_Receiver);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
{
if ((timeout--) == 0) return -1;
}

I2C_AcknowledgeConfig(I2Cx, DISABLE);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED))
{
if ((timeout--) == 0) return -1;
}

I2C_GenerateSTOP(I2Cx, ENABLE);
Data = I2C_ReceiveData(I2Cx);

return Data; 
}

Finally, the I2C write function is implemented, using the same principles as above. It can be used recursively to send several bytes of data.

int I2C_Memory_Write(I2C_TypeDef* I2Cx, uint8_t address, uint8_t data)
{
uint32_t timeout = I2C_TIMEOUT_MAX;

I2C_GenerateSTART(I2Cx, ENABLE);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if ((timeout--) == 0) return -1;
}
I2C_Send7bitAddress(I2Cx, 0xA0, I2C_Direction_Transmitter);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if ((timeout--) == 0) return -1;
} 

I2C_SendData(I2Cx, address);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if ((timeout--) == 0) return -1;
}

I2C_SendData(I2Cx, data);

timeout = I2C_TIMEOUT_MAX;
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if ((timeout--) == 0) return -1;
}
I2C_GenerateSTOP(I2Cx, ENABLE);

return 0; 
}

The full project to read and write to an EEPROM via I2C with error handling can be found on my Github page.