Дан микроконтроллер, на котором выполняется следующий код:
volatile bool has_flag = false;
void interrupt(void) //called when an interrupt is received
{
clear_interrupt_flag(); //clear interrupt flag
has_flag = true; //signal that we have an interrupt to process
}
int main()
{
while(1)
{
if(has_flag) //if we had an interrupt
{
has_flag = false; //clear the interrupt flag
process(); //process the interrupt
}
else
sleep(); //place the micro to sleep
}
}
Предположим, что if(has_flag)
условие оценивается как ложное, и мы собираемся выполнить инструкцию сна. Непосредственно перед тем, как выполнить инструкцию сна, мы получаем прерывание. После выхода из прерывания мы выполняем инструкцию сна.
Эта последовательность выполнения нежелательна, потому что:
- Микроконтроллер пошел спать вместо того, чтобы просыпаться и звонить
process()
. - Микроконтроллер может никогда не проснуться, если после этого не получено никакого прерывания.
- Звонок
process()
откладывается до следующего прерывания.
Как можно написать код, чтобы предотвратить возникновение этого состояния гонки?
редактировать
Некоторые микроконтроллеры, такие как ATMega, имеют бит включения режима сна, который предотвращает возникновение этого состояния (спасибо Kvegaoro за указание на это). JRoberts предлагает пример реализации, которая иллюстрирует это поведение.
Другие микросхемы, такие как PIC18, не имеют этого бита, и проблема все еще возникает. Однако эти микросхемы спроектированы таким образом, что прерывания могут по-прежнему пробуждать ядро независимо от того, установлен ли бит разрешения глобальных прерываний (спасибо суперкату за указание на это). Для таких архитектур решение заключается в отключении глобальных прерываний непосредственно перед сном. Если прерывание сработает прямо перед выполнением команды ожидания, обработчик прерываний не будет выполнен, ядро проснется, и как только глобальные прерывания будут повторно включены, обработчик прерываний будет выполнен. В псевдокоде реализация будет выглядеть так:
int main()
{
while(1)
{
//clear global interrupt enable bit.
//if the flag tested below is not set, then we enter
//sleep with the global interrupt bit cleared, which is
//the intended behavior.
disable_global_interrupts();
if(has_flag) //if we had an interrupt
{
has_flag = false; //clear the interrupt flag
enable_global_interrupts(); //set global interrupt enable bit.
process(); //process the interrupt
}
else
sleep(); //place the micro to sleep
}
}
interrupt_flag
какint
, и увеличивать его каждый раз, когда есть прерывание. Затем изменитеif(has_flag)
кwhile (interrupts_count)
и затем спать. Тем не менее, прерывание может произойти после того, как вы вышли из цикла while. Если это проблема, значит ли обработка в самом прерывании?Ответы:
Обычно для этого случая есть какая-то аппаратная поддержка. Например,
sei
инструкция AVR для включения прерываний откладывает включение до тех пор, пока не будет выполнена следующая инструкция. С этим можно сделать:Прерывание, которое было бы пропущено в примере, в этом случае будет удерживаться до тех пор, пока процессор не завершит свою последовательность ожидания.
источник
На многих микроконтроллерах, помимо возможности включать или отключать конкретные причины прерывания (обычно в модуле контроллера прерываний), в ядре ЦП имеется главный флаг, который определяет, будут ли приниматься запросы на прерывание. Многие микроконтроллеры выходят из спящего режима, если запрос прерывания достигает ядра, независимо от того, готово ли ядро принять его.
В такой конструкции простой подход к достижению надежного режима сна состоит в том, чтобы проверка основного цикла сбрасывала флаг, а затем проверяла, знает ли он причину, по которой процессор должен проснуться. Любое прерывание, которое происходит в течение этого времени и которое может повлиять на любую из этих причин, должно установить флаг. Если основной цикл не нашел причины оставаться в бодрствующем состоянии, и если флаг не установлен, основной цикл должен отключить прерывания и снова проверить флаг [возможно, после пары инструкций NOP, если возможно, что прерывание становится ожидающим во время команды отключения-прерывания может быть обработана после того, как выборка операнда, связанная со следующей инструкцией, уже была выполнена]. Если флаг все еще не установлен, то иди спать.
В этом сценарии прерывание, которое происходит до того, как основной цикл отключает прерывания, установит флаг перед финальным тестом. Прерывание, которое становится ожидающим слишком поздно, чтобы быть обслуженным до того, как инструкция режима сна не позволит процессору перейти в режим ожидания. Обе ситуации просто отлично.
Sleep-on-exit иногда является хорошей моделью для использования, но не все приложения действительно "подходят" ей. Например, устройство с энергоэффективным ЖК-дисплеем может быть наиболее легко запрограммировано с кодом, который выглядит следующим образом:
Если ни одна кнопка не нажата, и больше ничего не происходит, нет причины, по которой система не должна переходить в спящий режим во время выполнения
get_key
метода. Хотя ключи могут вызывать прерывания и управлять всеми взаимодействиями с пользовательским интерфейсом с помощью конечного автомата, код, подобный приведенному выше, часто является наиболее логичным способом обработки высокомодальных потоков пользовательского интерфейса, типичных для небольших микроконтроллеров.источник
Запрограммируйте микро для пробуждения при прерывании.
Конкретные детали могут отличаться в зависимости от используемого вами микроэлемента.
Затем измените подпрограмму main ():
источник