Лучший шаблон для 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);
    если (! dont_sleep)             
      store_exclusive (SLEEP_TRIGGER);
  }

Если между операциями load_exclusive и store_exclusive должно произойти прерывание, эффект будет состоять в том, чтобы пропустить store_exclusive, что приведет к повторному прохождению системы через цикл (чтобы проверить, не было ли установлено прерывание dont_sleep). К сожалению, Gekko использует инструкцию WFI, а не адрес записи для запуска спящего режима; писать код как

  если (! dont_sleep)
    ВДИ ();

рискует, что между «if» и «wfi» может произойти прерывание, и установит dont_sleep, но wfi все равно будет выполняться и выполняться. Какой лучший способ предотвратить это? Установите PRIMASK в 1, чтобы прерывания не прерывали процессор непосредственно перед выполнением WFI, и очистите его сразу после? Или есть какой-то лучший трюк?

РЕДАКТИРОВАТЬ

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

  если (dont_sleep)
    СЕВ (); / * После WFE очистит флаг события, но не перейдет в режим сна * /
  WFE ();

Каждое прерывание, которое устанавливает not_sleep, должно также выполнять инструкцию SEV, поэтому, если прерывание произойдет после теста «if», WFE очистит флаг события, но не перейдет в спящий режим. Это звучит как хорошая парадигма?

Supercat
источник
1
Инструкция WFI не переводит ядро ​​в спящий режим, если его условие пробуждения истинно при выполнении инструкции. Например, если есть неочищенный IRQ при выполнении WFI, он действует как NOP.
Mark
@Mark: проблема заключается в том, что если между «if (! Dont_sleep)» и «WFI» выполняется прерывание, условие прерывания больше не будет ожидаться при выполнении WFI, но прерывание могло бы установить dont_sleep, потому что оно сделал что-то, что оправдало бы основной цикл, выполняющий другую итерацию. В моем приложении Cypress PSOC любые прерывания, которые должны вызывать расширенное пробуждение, приводили бы к сбою в стеке, если основной код собирался спать, но это кажется довольно странным, и я понимаю, что ARM препятствует подобным манипуляциям со стеком.
суперкат
@supercat Прерывание может или не может быть очищено при выполнении WFI. Это зависит от вас, и когда / где вы решите очистить прерывание. Избавьтесь от переменной dont_sleep и просто используйте маскированное прерывание, чтобы указать, когда вы хотите не спать или спать. Вы можете просто избавиться от оператора if и оставить WFI в конце основного цикла. Если вы обслужили все запросы, очистите IRQ, чтобы вы могли спать. Если вам нужно бодрствовать, запустите IRQ, он замаскирован, чтобы ничего не происходило, но когда WFI попытается выполнить его, он потерпит неудачу.
Mark
2
@supercat На более фундаментальном уровне кажется, что вы пытаетесь смешать дизайн, управляемый прерываниями, с дизайном «большого основного цикла», который обычно не критичен по времени, часто основан на опросе и имеет минимальные прерывания. Смешивание их может стать довольно уродливым, довольно быстрым. Если это вообще возможно, выберите одну модель дизайна или другую для использования. Помните, что с современными контроллерами прерываний вы в основном получаете вытесняющую многозадачность между прерываниями и тем, что составляет очереди задач (обслуживайте одно прерывание, затем следующий более высокий приоритет и т. Д.). Используйте это в ваших интересах.
Mark
@Mark: я разработал систему, которая довольно хорошо использовала PIC 18x в приложении с питанием от батареи; из-за ограничений стека он не мог обрабатывать слишком много внутри прерывания, поэтому подавляющее большинство вещей обрабатывается в основном цикле как удобно. В основном это работает довольно хорошо, хотя есть пара мест, где вещи блокируются на секунду или две из-за длительных операций. Если я перейду на ARM, я могу использовать простую ОСРВ, чтобы упростить разделение длительных операций, но я не уверен, использовать ли упреждающую или совместную многозадачность.
суперкат

Ответы:

3

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

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

Игорь Скочинский
источник
5
инструкция WFI не требует, чтобы прерывания были активированы для активации процессора, биты F и I в CPSR игнорируются.
Mark
1
@ Отметьте, что я, должно быть, пропустил этот бит в документах, у вас есть ссылки / указатели по этому поводу? Что происходит с сигналом прерывания, который разбудил ядро? Он остается в ожидании до тех пор, пока прерывание не будет снова включено?
Игорь Скочинский
Справочное руководство по ASM находится здесь: infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489c/… более подробная информация для cortex-m3 находится здесь: infocenter.arm.com/help/ index.jsp? тема = / com.arm.doc.dui0552a / ... короче, когда замаскированное прерывание становится ожидающим, ядро ​​просыпается и продолжает работу после инструкции WFI. Если вы попытаетесь выдать другой WFI без очистки этого ожидающего прерывания, WFI будет действовать как NOP (ядро не будет спать, так как условие пробуждения для WFI верно).
Mark
@Mark: Одна вещь, которую я рассматривал, - это чтобы любой обработчик прерываний, который устанавливает dont_sleep, также выполнял инструкцию SEV («Set Event»), а затем использовал WFE («Wait For Event»), а не WFI. Примеры Gekko, кажется, используют WFI, но я думаю, что WFE также может работать Есть предположения?
суперкат
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 используют реализацию критического раздела, которая использует глобальный счетчик вместо локального сохранения статуса. Я нахожу это ошеломляющим, поскольку подход с использованием счетчиков является более сложным и будет работать только в том случае, если весь системный код использует один и тот же счетчик.
суперкат
1
Разве прерывания не будут отключены, пока мы не выйдем из критической секции? Если это так, не будет ли WFI заставлять процессор ждать бесконечно?
Корнелиу Зузу
1
Ответ @Kenzi Shrimp указывает на ветку обсуждения Linux, которая отвечает на мой предыдущий вопрос. Я отредактировал его и ваш ответ, чтобы уточнить это.
Корнелиу Зузу
@CorneliuZuzu Редактирование чужого ответа для вставки вашей собственной дискуссии не очень хорошая идея. Добавление цитаты для улучшения ответа «только ссылка» - это другое дело. Если у вас есть реальный вопрос о вашем выигрыше, возможно, задайте его как вопрос и ссылку на него.
Шон
1
@SeanHoulihane Я не аннулировал и не удалил ничего из ее ответа. Краткое разъяснение того, почему это работает, не является отдельным обсуждением. Честно говоря, я не чувствую, что этот ответ заслуживает голосования без разъяснений WFI, но он заслуживает этого больше всего.
Корнелиу Зузу
7

Ваша идея в порядке, это именно то, что реализует Linux. Смотрите здесь .

Полезная цитата из вышеупомянутой ветки обсуждения, чтобы уточнить, почему WFI работает даже с отключенными прерываниями:

Если вы намерены бездействовать до следующего прерывания, вам нужно подготовиться. Во время этой подготовки прерывание может стать активным. Такое прерывание может быть событием пробуждения, которое вы ищете.

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

Вот почему все процессоры ARM, о которых я знаю, проснутся, даже если они замаскированы на центральном процессоре (бит CPSR I).

Что-нибудь еще, и вы должны забыть об использовании режима ожидания.

Креветка
источник
1
Вы имеете в виду отключение прерываний во время инструкции WFI или WFE? Видите ли вы какое-либо значимое различие между использованием WFI или WFE для этой цели?
суперкат
1
@supercat: я бы определенно использовал WFI. WFE IMO в основном предназначен для подсказок по синхронизации между ядрами в многоядерной системе (например, выполнение WFE при сбое спин-блокировки и выдача SEV после выхода из спин-блокировки). Кроме того, WFE учитывает флаг маскирования прерываний, поэтому он здесь не так полезен, как WFI. Этот шаблон действительно хорошо работает в Linux.
Креветка
2

При условии, что:

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

Тогда решение состоит в том, чтобы использовать PRIMASK для блокировки прерываний между проверкой флага и WFI:

mask_interrupts();
if (!dont_sleep)
    wfi();
unmask_interrupts();
hdante
источник
0

А как насчет Sleep on Exit mode? Он автоматически переходит в спящий режим при каждом выходе из обработчика IRQ, поэтому после его настройки «нормальный режим» не работает. Происходит IRQ, он просыпается, запускает обработчик и возвращается в режим сна. Не требуется WFI.

billt
источник
2
Как лучше всего учитывать тот факт, что тип сна, в который должен попасть процессор, может варьироваться в зависимости от того, что происходит во время прерывания? Например, событие смены штырька может указывать на то, что могут поступить последовательные данные, и поэтому процессор должен поддерживать работу основного тактового генератора в ожидании данных. Если основной цикл очищает флаг события, проверяет, что происходит, и переводит процессор в соответствующий режим ожидания с WFI, то любое прерывание, которое могло бы повлиять на то, какой режим будет подходящим, установило бы флаг события ...
суперкат
... и прервать сон. При наличии одного обработчика основного цикла режим ожидания выглядит чище, чем беспокоиться об этом при каждом прерывании. Необходимость «раскручивать» эту часть основного цикла при каждом прерывании может быть неоптимально эффективной, но это не должно быть слишком плохо, особенно если все прерывания, которые могут повлиять на спящее поведение, попадают в какой-то флаг.
суперкат