On a previous post, the code to implement the I2C protocol for an STM32 microcontroller connected to an TC74 temperature sensor was implemented. However, what happens if no TC74 is connected or due to a failure does not respond. Since the TC74 sensor will be used on the UPSat mission, the microcontroller needs to be able to acknowledge that the sensor does not respond and continue with executing the main program.

One peripheral that can be used in case the TC74 doesn't respond is the watchdog counter. If no error handling is implemented on the I2C communication, the program enters into a deadlock. The watchdog's purpose is to reset the microcontroller if it doesn't reset the watchdog counter after a short period of time, acting as an auto reset. The STM32 micro controllers include independent and window watchdogs. However, on the next time that the temperature sensor will be called again, the microcontroller will freeze again. Of course, a "reset reason" detection code could be implemented and disable TC74 communication after a reset caused to an I2C error, but the damage to the procedure execution could already be done because of the reset.

A more correct way is to implement error handling on the I2C communication and continue code execution when an error is detected, discarding the measurement. The microcontroller after sending a message through the I2C bus waits for the peripheral to respond. If the peripheral does not respond, the code execution freezes at this point. The simplest solution is to implement a timeout function, meaning that if the peripheral does not respond after a certain amount of time, the code execution will continue. The I2C peripheral on the STM32 microcontroller does not implement a timeout support inherently. However, the System Management Bus (SMBus) mode can be used which implements timeout detection. So, on the initialization function of the I2C peripheral, the I2C_mode needs to be set to I2C_Mode_SMBusHost. Then, using the I2C Timeout flag or the I2C Timeout interrupt, the error can be handled.

Furthermore, another error that can appear is an acknowledge failure. This exists when the microcontroller sends a message to the device, awaits for an acknowledge bit on the next clock but it doesn't respond. Again, the Acknowledge Failure (AF) flag can be polled to detect the error.

So based on the above, the communication protocol functions are rewritten to return 0 is the communication is good and >0 if there is an error. The main function finally returns the temperature, or -127 in case of an error:

int TC74_Read_Temperature(uint8_t TC74address)
{
int8_t data1, data2;
if(I2C_start(I2C1, TC74address, I2C_Direction_Transmitter)>0) {
I2CBus_Reset();
return -127;
}
if(I2C_write(I2C1, 0x00)>0) return -127;
if(I2C_stop(I2C1)==1) return -127;

if(I2C_start(I2C1, TC74address, I2C_Direction_Receiver)>0) {
I2CBus_Reset();
return -127;
}
data1 = I2C_read_ack(I2C1);
data2 = I2C_read_nack(I2C1);

return data1;
}

So the modification that is implemented is inside the while statement to avoid the infinite loop. If an error flag is set, the code execution is stopped, returning the error code.

int I2C_start(I2C_TypeDef* I2Cx, uint8_t address, uint8_t direction)
{
int i2c_timeout=I2CTIMEOUT;
while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));

I2C_GenerateSTART(I2Cx, ENABLE);

while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)){
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_TIMEOUT)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_TIMEOUT);
return 1;
}
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
return 2;
}
}
I2C_Send7bitAddress(I2Cx, address, direction);

if (direction== I2C_Direction_Transmitter) {
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_TIMEOUT)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_TIMEOUT);
return 1;
}
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
return 2;
}
}
}

else if(direction == I2C_Direction_Receiver) {
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)){
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_TIMEOUT)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_TIMEOUT);
return 3;
}
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
return 4;
}
}
}
return 0;
}
int I2C_write(I2C_TypeDef* I2Cx, uint8_t data)
{
I2C_SendData(I2Cx, data);

while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)){
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_TIMEOUT)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_TIMEOUT);
return 1;
}
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
return 2;
}
}
return 0;
}
int I2C_stop(I2C_TypeDef* I2Cx)
{
I2C_GenerateSTOP(I2Cx, ENABLE);
return 0;
}
int8_t I2C_read_ack(I2C_TypeDef* I2Cx)
{
int8_t data;

I2C_AcknowledgeConfig(I2Cx, ENABLE);
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)){
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_TIMEOUT)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_TIMEOUT);
return -127;
}
if(I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
return -127;
}
}
data=I2C_ReceiveData(I2Cx);

return data;
}
int8_t I2C_read_nack(I2C_TypeDef* I2Cx)
{
uint8_t data;

I2C_AcknowledgeConfig(I2Cx, DISABLE);
I2C_GenerateSTOP(I2Cx, ENABLE);

while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)){
if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_TIMEOUT)!=RESET) {
I2C_ClearFlag(I2Cx, I2C_FLAG_TIMEOUT);
return -127;
}
}
data=I2C_ReceiveData(I2Cx);

return data;
}

The full project to read the temperature from the TC74 sensor with error handling can be found on my Github page.