Стан сну мікроконтролера


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

теоретично ви використовуєте таймер, який прокидає вас один раз кожні (введіть прийнятне значення) мс, а потім повертається до сну, якщо нічого не потрібно робити.
Гравець Граді

1
Я б робив це interrupt_flagяк int, і збільшувати його кожного разу, коли відбувається переривання. Потім перемніть if(has_flag)на, while (interrupts_count)а потім сплять. Тим не менш, переривання може статися після виходу з циклу while. Якщо це проблема, то чи робиться обробка в самому перериванні?
angelatlarge

1
добре, це залежить від того, який мікрофон ви працюєте. Якби це був ATmega328, ви могли б вимкнути сплячий режим при перериванні, тож, якщо описаний вами гоночний стан станеться, функція сну буде відмінена, знову поверніться до циклу, і ви будете обробляти переривання з невеликою затримкою. Але також прекрасним рішенням буде також використання таймера для прокидання з інтервалом, рівним або меншим, ніж ваша максимальна затримка
Kvegaoro

1
@TRISAbits: На PIC 18x підхід, який я описав у своїй відповіді, працює чудово (це мій звичайний дизайн при використанні цієї частини).
supercat

Відповіді:


9

Зазвичай у цьому випадку є якась апаратна підтримка. Наприклад, інструкція AVR seiщодо включення переривань відкладає включення, поки не буде виконана наступна інструкція. З ним можна зробити:

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, якщо можливо, що перерва, яка стає в очікуванні під час відключення-переривання інструкція може бути оброблена після того, як вже був виконаний збір операнда, пов'язаний із наступною інструкцією]. Якщо прапор досі не встановлений, перейдіть спати.

За цим сценарієм переривання, яке відбувається до того, як основний цикл відключає переривання, встановить прапор перед остаточним тестом. Переривання, яке стає занадто пізно для обслуговування перед інструкцією сну, не дасть процесору спати. Обидві ситуації просто чудові.

Сон-на-виході іноді є хорошою моделлю для використання, але не всі програми дійсно "підходять". Наприклад, пристрій з енергоефективним РК-екраном може бути найпростіше запрограмований з кодом, який виглядає так:

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

Програмуйте мікросхема для пробудження на переривання.

Конкретні деталі залежать від мікрокористувача, який ви використовуєте.

Потім змініть основну () процедуру:

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

1
У питанні передбачається архітектура "пробудження на переривання". Я не думаю, що ваша відповідь вирішує питання / проблему.
angelatlarge

@angelatlarge Point прийнято. Я додав приклад коду, який, на мою думку, допомагає.
jwygralak67

@ jwygralak67: Дякую за пропозицію, але наданий вами код просто переносить проблему в процедуру () процесу, яка тепер повинна перевірити, чи не відбулося переривання перед виконанням органу process ().
TRISAbits

2
Якщо переривання не сталося, чому ми прокидаємось?
JRobert

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