Обработка ошибок и перезапуск модуля I2C

Сегодня в комментариях меня попросили рассмотреть работу I2C более подробно, обратить внимание на нетривиальные случаи: например, что будет в случае возникновения ошибок на линии, как такие ошибки обрабатывать? Дело в том, что при появлении таких ошибок модуль I2C часто «зависает», и не реагирует на дальнейшие обращения — нужно ловить такую ситуацию и перезапускать модуль.

Стандартная процедура общения с I2C-модулем предусматривает отправку байт и проверку флага — передано или нет. Флаг проверяется в цикле while, и именно этот цикл подвержен зависаниям — если флаг не устанавливается из-за ошибки, из этого цикла мы уже не выйдем никогда.

Я предлагаю простой способ выхода из этой ситуации — вместо простого while нужно сделать «while с условием». К примеру, если цикл опроса флага безрезультатно прокрутился более 200 раз — наверное ситуация уже не изменится, т.к. возникла ошибка. Значит нужно перезагружать модуль I2C.

Сделать это просто: добавим переменную «таймаут» = 200, и в цикле опроса флага будем её декрементировать. Как только она дошла до нуля — сбрасываем текущую передачу и перезапускаем модуль. Очень важно именно отменить передачу, потому что иначе после перезапуска модуля она продолжится с того места, где застряла в прошлый раз — ну и ни к чему хорошему это не приведёт.

Код примера, демонстрирующего эту «безопасную передачу», таков:

void delay()
{
for(volatile uint32_t del = 0; del<250000; del++);
}

typedef enum { EVENT, FLAG } OCCASION;

ErrorStatus wait_for(OCCASION occasion, uint32_t I2C_FLAG_OR_EVENT)
{
uint16_t ticks = 200; // таймаут - 200 проверок события или флага. нужно подбирать это значение.
while( (occasion == FLAG ? I2C_GetFlagStatus(I2C1, I2C_FLAG_OR_EVENT) : !I2C_CheckEvent(I2C1, I2C_FLAG_OR_EVENT))
&&
ticks) // а не вышел ли таймаут?
ticks--;

if(ticks == 0)
/*
вышли из while по таймауту, это нестандартная ситуация - значит, произошла ошибка.
честно говоря, сейчас не очень интересно что за ошибка -  просто вернём значение ERROR.
при желании можно спросить текущий статус в регистрах статуса SR1 и SR2.
*/
{
I2C_DeInit(I2C1);
delay();
I2C_init();
delay();
return ERROR; // Ошибка в модуле I2C, теперь нужно прервать текущие операции
}
return SUCCESS;
}

ErrorStatus I2C_single_write(uint8_t HW_address, uint8_t data)
{
/*
переходим к следующему действию только если предыдущее выполнено со статусом SUCCESS
как только в любом действии получаем ERROR - сразу выходим из всей функции.
*/
I2C_GenerateSTART(I2C1, ENABLE);
if(wait_for(EVENT, I2C_EVENT_MASTER_MODE_SELECT) == ERROR) return ERROR;
// раньше было while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, HW_address, I2C_Direction_Transmitter);
if(wait_for(EVENT, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == ERROR) return ERROR;
I2C_SendData(I2C1, data);
if(wait_for(EVENT, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == ERROR) return ERROR;
I2C_GenerateSTOP(I2C1, ENABLE);
if(wait_for(FLAG, I2C_FLAG_BUSY) == ERROR) return ERROR;
return SUCCESS;
}

И в заключение пару слов, почему я сделал именно так, ведь более логичным было бы добавление прерываний на ошибки и обработка этих прерываний. Я просто хотел как можно меньше менять исходный код, сделать это проще всего оказалось именно расширив стандартный while до такого «while с таймаутом». Более того, так можно обойтись без глобальных переменных, а это всегда очень хорошо.

Введение обработчиков прерываний ошибок раздуло бы код и спрятало бы основную логику. Мне это неудобно. Кстати, подобная проблема встречалась у меня при работе микроконтроллера LM3S с RFID-ридером, я думаю такой подход решил бы её.

Этот код проверен в течение нескольких десятков замыканий SDA и SCL друг на друга и на землю: возникает ошибка, попадаем в обработчик ошибки, перезапускаем I2C и обмен продолжается. Таймаут и длительность пауз при перезапуске модуля нужно подгонять под свою скорость MK, мой код отлажен под 72 МГц, использован МК STM32F107. Тип ErrorStatus описан в заголовочном файле stm32f10x.h, можно было и без него — но с enum удобнее работать, чем с 0 и 1.

Ссылка на основную публикацию