Ловушки прерываний при программировании микроконтроллеров

Важное замечание по прерываниям, зависаниям и прочим "радостям" при написании программ для микроконтроллеров. Всякое бывает, но главное, на поиск ошибок порою уходит очень не мало времени..

Правильно сохраняйте и восстанавливайте регистр SREG

В один момент вдруг девайс стал подвисать. Несколько раз выполнит команду и подвисает. Прошелся отладчиком, основной цикл крутится, но в прерывания не заходит. Флаг SREG (I) глобальных прерываний снят. Для теста в основном цикле вставляю asm("sei"); и все условно работает, как ни в чем не бывало. Потом попробовал запретил прерывание по TX, тоже все наладилось..

Вобщем дело было в следующем, мы в цикле и получали сообщения и отправляли, соответственно при одновременно включенных прерывания TX и RX у нас они оба и срабатывали, перекрывая друг-друга. Хотя в самих прерываниях мы не зависали, как это бывает иногда, нас наоборот в итоге оттуда выкидывало. На самом деле глобальная проблема была в том, что при некоторых действиях в разных участках кода и в том числе в самих прерываниях мы выполняли макрос по сохранению состояния регистра SREG и после выполнения текущих задач мы этот регистр восстанавливали. Сейчас уже не помню точно весь ход событий, но смысл пролбемы был таков: если этот макрос срабатывал, например как вложенный, то во вложенном макросе мы уже сохраняли регистр SREG с отключенным флагом глобальных прерываний. Не трудно догадаться, что на последнем шаге при восстановлении регистра SREG мы и восстановим уже флаг I пустым. Кто последний, того и тапки..

Еще один нюанс по прерываниям TX и RX

Проверка состояния флагов TXOK и RXOK через while(), иногда может привести к сюрпризам. Не у всех новичков есть отладчик, а если такой и есть, то даже с отладчиком не всегда просто и быстро найти ошибку, особенно, если вы в свой проект внедряете чужой код, найденный на просторах сети, ведь в чужом коде разбираться всегда сложнее.

Рассмотрим следующий код по отправке сообщения (CAN):

// Включаем передачу
CANCDMOB |= (1<<CONMOB0);
//wait until complete
while (!(CANSTMOB & (1<<TXOK)));
//reset flag
CANSTMOB &= ~(1<<TXOK);

Например вы начали отправлять сообщение, оно успешно отправилось и вот-вот должна была сработать строка с циклом while, в котором мы ждем, когда пакет успешно отправится. Но вдруг в этот момент, до исполнения цикла while срабатывает прерывание, в котором мы простым ифом (if) проверяем состояние флага TXOK и тутже его снимаем. Что произойдет в таком случае? Правильно, мы выйдем из прерывания и навечно зависнем в while в основом коде. Конечно можно на таймере соорудить break по таймауту, или защититься дополнительными флагами "занято", но в данном случае проще запомнить одно правило: если в вашей программе прерывания включены, то проверяем флаги только там. Конечно вы можете поступать как хотите, все зависит от ваших задач, просто зная о такой ловушке заранее, вы правильно примите меры предосторожности, чтобы в нее не попасть.

Проблема задержек типа delay_ms(100)

В самих прерываниях такие вещи лучше не использовать, а в основном цикле при включенных прерываниях перед паузой выключайте все прерывания перед задержкой и включайте обратно после функции задержки. В зависимости от задач, можно использовать макросы с сохранением SREG, или просто asm("cli") и asm("sei").

Пример:
asm("cli");
delay_ms(100);
asm("sei");

Если вы так не сделаете, как показано выше, то при частых прерываниях ваш основной цикл программы замедлится так, что даже простые задачи станут вечностью.

Все дело в том, что во время задержки прерывания все равно работают! В итоге вы просто постоянно будете висеть в прерываниях, не успеет закончится одно прерывание, вы влетаете в другое. В результате ваша задержка из 100ms превратится 10 минут (условно).

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *