Найкращий зразок для WFI (очікування перерви) на мікроконтролерах Cortex (ARM)


18

Я розглядаю розробку програмного забезпечення, що працює на батареях, за допомогою контролерів EFM Gekko (http://energymicro.com/), і хотів би спати контролер, коли нічого корисного для цього не робити. Для цього використовується інструкція WFI (Wait For Interrupt); він переведе процесор у режим сну, поки не відбудеться переривання.

Якщо уві сні спати, зберігаючи щось десь, можна використовувати ексклюзивні / магазинні ексклюзивні операції, щоб зробити щось на кшталт:

  // dont_sleep завантажується з 2 будь-коли, коли щось трапляється
  // повинен змусити головну петлю провести цикл хоча б один раз. Якщо перерва
  // відбувається, що призводить до його скидання до 2 під час наступного твердження,
  // поведінка буде так, ніби перерва сталася після неї.

  store_exclusive (load_exclusive (dont_sleep) >> 1);

  поки (! dont_sleep)
  {
    // Якщо відбувається переривання між наступним оператором і store_exclusive, не спите
    load_exclusive (SLEEP_TRIGGER);
    if (! dont_sleep)             
      store_exclusive (SLEEP_TRIGGER);
  }

Якщо між операціями load_exclusive і store_exclusive відбудеться переривання, ефектом було б пропустити store_exclusive, тим самим змусивши систему ще раз пробігти цикл (щоб побачити, чи встановив переривання dont_sleep). На жаль, Gekko використовує інструкцію WFI, а не адресу запису для запуску режиму сну; написання коду, як

  if (! dont_sleep)
    WFI ();

ризикує виникнути перерва між 'if' і 'wfi' та встановити dont_sleep, але wfi продовжуватиметься та виконуватиметься все одно. Яка найкраща модель для запобігання цьому? Встановіть PRIMASK на 1, щоб запобігти перериванню процесора безпосередньо перед виконанням WFI, і очистити його відразу після? Або є якийсь кращий трюк?

EDIT

Мене цікавить біт Події. За загальним описом, хотілося б, що він призначений для підтримки декількох процесорів, але цікаво, чи може працювати щось подібне:

  якщо (dont_sleep)
    SEV (); / * Зробимо чіткий прапор події WFE, але не сплю * /
  WFE ();

Кожне переривання, яке встановлює don_sleep, також повинно виконувати інструкцію SEV, тому якщо переривання відбудеться після тесту "якщо", WFE очистить прапор події, але не перейде до сну. Це звучить як добра парадигма?


1
Інструкція WFI не приводить до сну ядро, якщо його стан не відповідає, коли інструкція виконується. Наприклад, якщо є незачищений IRQ, коли WFI виконується, він виступає як NOP.
Марк

@Mark: Проблема полягає в тому, що якщо перерва буде зроблена між "if (! Dont_sleep)" і "WFI", умова переривання більше не буде очікуватися, коли WFI виконується, але переривання може встановити dont_sleep, оскільки це зробив щось, що виправдало б головний цикл, який виконує ще одну ітерацію. У моєму застосуванні Cypress PSOC будь-які переривання, які повинні викликати розширене пробудження, супроводжуватимуть стек, якщо основний код збирається спати, але це здається досить прискіпливим, і я розумію, що ARM відлякує подібні маніпуляції зі стеком.
supercat

@supercat Переривання може бути усунене, або не може бути усунене при виконанні WFI. Це залежить від вас і коли / де ви вирішите зняти перерву. Позбудьтеся від змінної dont_sleep і просто скористайтеся масковим перериванням, щоб позначити, коли ви хочете не спати або спати. Ви можете просто позбутися оператора if разом і залишити WFI в кінці основного циклу. Якщо ви обслуговували всі запити, очистіть IRQ, щоб ви могли спати. Якщо вам потрібно не спати, запустіть IRQ, його замасковано, щоб нічого не трапилося, але коли WFI намагатиметься виконати, це буде NOP.
Марк

2
@supercat На більш фундаментальному рівні здається, що ви намагаєтеся поєднати дизайн, керований перериванням, з дизайном "великої основної петлі", який, як правило, не є критичним часом, часто опитується на основі опитування та має мінімальні переривання. Змішування їх може вийти досить потворним досить швидко. Якщо взагалі можливо, виберіть одну із дизайнерських парадигм або іншу, яку слід використовувати. Пам’ятайте, що за допомогою сучасних контролерів переривань ви отримуєте попереджувальну багатозадачність між перериваннями і тим, що становить черги завдань (сервіс один переривання, потім наступний більш високий пріоритет тощо). Використовуйте це на вашу користь.
Марк

@Mark: я розробив систему, яка досить добре використовувала PIC 18x у додатку, що працює на акумуляторі; через обмеження стека, він не міг би справляти занадто багато часу в перерві, тому переважна більшість речей обробляється в основному циклі як зручніше. В основному це працює досить добре, хоча є кілька плям, де речі блокуються на секунду-дві через тривалі операції. Якщо я переміщуюсь до ARM, я можу використовувати простий RTOS, щоб полегшити поділ тривалих операцій, але я не впевнений, чи використовувати попереджувальний або спільний багатозадачність.
supercat

Відповіді:


3

Я не повністю зрозумів dont_sleepріч, але одне, що можна спробувати, - це зробити «основну роботу» в обробнику PendSV, встановленому на найнижчий пріоритет. Тоді просто сплануйте PendSV від інших обробників щоразу, коли вам потрібно щось зробити. Дивіться тут, як це зробити (це для M1, але M3 не надто відрізняється).

Ще одна річ, яку ви могли використовувати (можливо, разом із попереднім підходом) - це функція Sleep-on-exit. Якщо ввімкнути цю функцію, процесор перейде до сну після виходу з останнього обробника ISR, без того, щоб вам потрібно було викликати WFI. Дивіться деякі приклади тут .


5
інструкція WFI не вимагає вмикання переривань для пробудження процесора, F і I біти в CPSR ігноруються.
Марк

1
@Mark Я, мабуть, пропустив цей біт у документах, чи є у вас якісь посилання / покажчики? Що відбувається з сигналом переривання, який розбудив серцевину? Чи залишається він, поки перерва не буде ввімкнено знову?
Ігор Скочинський

Довідковий посібник з ASM знаходиться тут: infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489c/… Більш конкретну інформацію про cortex-m3 можна знайти тут: infocenter.arm.com/help/ index.jsp? topic = / com.arm.doc.dui0552a / ... коротко, коли масковане переривання стає до очікування, ядро ​​прокидається і продовжує роботу після інструкції WFI. Якщо ви спробували видати інший WFI, не очистивши, що очікуючий на переривання, WFI буде виконувати функцію NOP (ядро не буде спати, оскільки стан WFI не відповідає).
Марк

@Mark: Одне, що я розглядав, - це мати будь-який обробник перерв, який встановлює dont_sleep, також виконувати інструкцію SEV ("Set Event"), а потім використовувати WFE ("Чекати події"), а не WFI. На прикладі Gekko, здається, використовується WFI, але я думаю, що WFE також може працювати. Будь-які думки?
supercat

10

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

uint8 interruptStatus;
interruptStatus = EnterCriticalSection();
if (!dont_sleep)
  WFI();
ExitCriticalSection(interruptStatus);

Навколишнє середовище вашої розробки, ймовірно, має критичні функції секцій, але приблизно так:

EnterCriticalSection:

MRS r0, PRIMASK /* Save interrupt state. */
CPSID i /* Turn off interrupts. */
BX lr /* Return. */

ExitCriticalSection:

MSR PRIMASK, r0 /* Restore interrupt states. */
BX lr /* Return. */

2
Цікаво, що кілька бібліотек ARM використовують реалізацію критичного розділу, яка використовує глобальний лічильник, а не зберігає статус на локальному рівні. Я вважаю, що глузує з розуму, оскільки підхід лічильника є складнішим, і він працюватиме лише в тому випадку, якщо весь код системи використовує один і той же лічильник.
supercat

1
Чи не буде відключено переривання, поки ми не вийдемо з критичного розділу? Якщо так, то чи не змусить WFI процесор чекати нескінченно?
Корнеліу Зузу

1
Відповідь @Kenzi Shrimp вказує на дискусійний потік Linux, який відповідає на моє попереднє запитання. Я відредагував його відповідь і вашу, щоб уточнити це.
Корнеліу Зузу

@CorneliuZuzu Редагування чужої відповіді на заступництво вашої власної дискусії - не чудова ідея. Додавання цитати для вдосконалення відповіді "лише за посиланням" - інша справа. Якщо у вас є реальне питання про ваш виграш, можливо, поставте це як питання і посилання на це.
Шон Хуліхане

1
@SeanHoulihane Я нічого не визнав і не видалив з її відповіді. Коротке роз'яснення, чому це працює, не є окремим обговоренням. Чесно кажучи, я не вважаю, що ця відповідь заслуговує на підсумкове голосування без уточнення WFI, але це заслуговує на це найбільше.
Корнеліу Зузу

7

Ваша ідея чудова, саме це реалізує Linux. Дивіться тут .

Корисна цитата з вищезгаданої теми обговорення, щоб уточнити, чому WFI працює навіть з вимкненнями переривань:

Якщо ви збираєтеся простоювати до наступного переривання, вам доведеться провести деяку підготовку. Під час цієї підготовки перерва може стати активною. Таке переривання може бути подією пробудження, яку ви шукаєте.

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

Ось чому всі процесори ARM, про які я знаю, прокинуться, навіть якщо вони будуть замасковані в основному процесорі (CPSR I bit.)

Все інше, і ви повинні забути використовувати режим простою.


1
Ви маєте на увазі вимкнення переривань під час інструкції WFI або WFE? Чи бачите ви якісь значущі відмінності між використанням WFI або WFE для цієї мети?
supercat

1
@supercat: Я б точно використовував WFI. WFE IMO - це головним чином для підказки синхронізації між ядрами в багатоядерній системі (наприклад, WFE для відключення спінлок та випуск SEV після виходу із спінлок). Крім того, WFE враховує прапор маскування перерв, тому він не такий корисний, як WFI. Ця модель справді добре працює в Linux.
Креветки

2

Якщо припустити, що:

  1. Основна нитка виконує фонові завдання
  2. Переривання виконують лише завдання з високим пріоритетом і без фонових завдань
  3. Основну нитку можна перервати будь-коли (вона зазвичай не маскує переривання)

Тоді рішення - використовувати PRIMASK для блокування перерв між валідацією прапора та WFI:

mask_interrupts();
if (!dont_sleep)
    wfi();
unmask_interrupts();

0

Що про сон у режимі виходу? Це автоматично переходить у режим сну будь-який час, коли обробник IRQ закінчується, тому насправді не працює жоден "звичайний режим" після налаштування. Трапляється IRQ, він прокидається і запускає обробник і повертається спати. Жодна WFI не потрібна.


2
Як слід найкраще боротися з тим, що тип сну, в який повинен впасти процесор, може змінюватися залежно від того, що відбувається під час перерви? Наприклад, подія зміни PIN-коду може означати, що послідовні дані можуть бути найближчими, і тому процесор повинен тримати основний тактовий генератор, працюючи в очікуванні даних. Якщо основний цикл очищає прапор події, вивчає, що відбувається, і переводить процесор у відповідний сплячий режим з WFI, то будь-яке переривання, яке могло б вплинути на те, який режим був би відповідним, встановив би прапор події ...
supercat

... і перервати сон. Якщо один обробник основного циклу керує режимом сну, то здається чистішим, ніж турбуватися про нього при кожному перериванні. Необхідність "перекручувати" цю частину основного циклу при кожному перериванні може бути не ефективно, але це не повинно бути дуже погано, особливо якщо всі переривання, які можуть вплинути на поведінку сну, потрапляють під якийсь прапор.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.