Микроконтроллер сна состояние гонки


11

Дан микроконтроллер, на котором выполняется следующий код:

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
    }
}

Это практический или теоретический вопрос?
AndrejaKo

теоретически вы используете таймер, который будит вас один раз каждые (вводит приемлемое значение) мс, а затем возвращается в спящий режим, если ничего не нужно делать.
Grady Player

1
Я бы сделал interrupt_flagкак int, и увеличивать его каждый раз, когда есть прерывание. Затем измените if(has_flag)к while (interrupts_count)и затем спать. Тем не менее, прерывание может произойти после того, как вы вышли из цикла while. Если это проблема, значит ли обработка в самом прерывании?
Angelatlarge

1
ну, это зависит от того, на каком микросхеме вы работаете. Если бы это был ATmega328, вы могли бы отключить спящий режим на прерывании, поэтому, если бы описанное вами состояние гонки произошло, то функция спящего режима была бы переопределена, повторили бы цикл снова, и вы бы обработали прерывание с небольшой задержкой. Но использование таймера для пробуждения с интервалом, равным или меньшим вашей максимальной задержки, также будет отличным решением
Kvegaoro

1
@TRISAbits: На PIC 18x подход, который я описал в своем ответе, работает отлично (это мой обычный дизайн при использовании этой части).
суперкат

Ответы:


9

Обычно для этого случая есть какая-то аппаратная поддержка. Например, seiинструкция AVR для включения прерываний откладывает включение до тех пор, пока не будет выполнена следующая инструкция. С этим можно сделать:

forever,
   interrupts off;
   if has_flag,
      interrupts on;
      process interrupt;
   else,
      interrupts-on-and-sleep;    # won't be interrupted
   end
end

Прерывание, которое было бы пропущено в примере, в этом случае будет удерживаться до тех пор, пока процессор не завершит свою последовательность ожидания.


Отличный ответ! Алгоритм, который вы предоставляете, действительно хорошо работает на AVR. Спасибо за предложение.
TRISAbits

3

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

В такой конструкции простой подход к достижению надежного режима сна состоит в том, чтобы проверка основного цикла сбрасывала флаг, а затем проверяла, знает ли он причину, по которой процессор должен проснуться. Любое прерывание, которое происходит в течение этого времени и которое может повлиять на любую из этих причин, должно установить флаг. Если основной цикл не нашел причины оставаться в бодрствующем состоянии, и если флаг не установлен, основной цикл должен отключить прерывания и снова проверить флаг [возможно, после пары инструкций NOP, если возможно, что прерывание становится ожидающим во время команды отключения-прерывания может быть обработана после того, как выборка операнда, связанная со следующей инструкцией, уже была выполнена]. Если флаг все еще не установлен, то иди спать.

В этом сценарии прерывание, которое происходит до того, как основной цикл отключает прерывания, установит флаг перед финальным тестом. Прерывание, которое становится ожидающим слишком поздно, чтобы быть обслуженным до того, как инструкция режима сна не позволит процессору перейти в режим ожидания. Обе ситуации просто отлично.

Sleep-on-exit иногда является хорошей моделью для использования, но не все приложения действительно "подходят" ей. Например, устройство с энергоэффективным ЖК-дисплеем может быть наиболее легко запрограммировано с кодом, который выглядит следующим образом:

void select_view_user(int default_user)
{
  int current_user;
  int ch;
  current_user = default_user;
  do
  {
    lcd_printf(0, "User %d");
    lcd_printf(1, ...whatever... );
    get_key();
    if (key_hit(KEY_UP)) {current_user = (current_user + 1) % MAX_USERS};
    if (key_hit(KEY_DOWN)) {current_user = (current_user + MAX_USERS-1) % MAX_USERS};
    if (key_hit(KEY_ENTER)) view_user(current_user);
  } while(!key_hit(KEY_EXIT | KEY_TIMEOUT));
}

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


Спасибо суперкатам за отличный ответ. Отключение прерываний, а затем переход в спящий режим - это фантастическое решение, при условии, что ядро ​​проснется от любых источников прерываний, независимо от того, установлен или сброшен бит глобального прерывания. Я взглянул на схему аппаратных прерываний PIC18, и это решение подойдет.
TRISAbits

1

Запрограммируйте микро для пробуждения при прерывании.

Конкретные детали могут отличаться в зависимости от используемого вами микроэлемента.

Затем измените подпрограмму main ():

int main()
{
    while(1)
    {
        sleep();
        process(); //process the interrupt
    }
}

1
В этом вопросе предполагается архитектура Wake-On-Interrupt. Я не думаю, что ваш ответ решает вопрос / проблему.
Angelatlarge

@angelatlarge Пункт принят. Я добавил пример кода, который, я думаю, помогает.
jwygralak67

@ jwygralak67: Спасибо за предложение, но код, который вы предоставляете, просто перемещает проблему в подпрограмму process (), которая теперь должна проверить, произошло ли прерывание перед выполнением тела process ().
TRISAbits

2
Если прерывание не произошло, почему мы не спим?
JRobert

1
@JRobert: Мы можем проснуться от предыдущего прерывания, завершить процедуру process (), и когда мы закончим тест if (has_flag) и прямо перед сном, мы получим другое прерывание, которое вызывает проблему, которую я описал в вопрос.
TRISAbits
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.