Так что у меня было время протестировать ARM M0 + в микросхеме MKL17Z256VFT4. Использование CMSIS-RTOS RTX (v 4.75).
Работает это так:
void os_idle_demon(void) { // task with lowest priority - scheduled by
//system when there is no action to do
for (;;) {
timeToSleep = os_suspend(); // stop OS from switching tasks and get maximum allowed sleep time
__disable_irq();
LPTMR_Init(timeToSleep...); // set Low Power sleep timer
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;//set DeepSleep
GPIO(pin=0,val=1); // signalize on GPIO pad that CPU is (almost) in sleep
__enable_irq();
__wfi(); // go to DeepSleep
GPIO(pin=0,val=0); // signalize on GPIO pad that CPU just wakeup
sleptTime = LPTMR_GetCounterValue(); // get sleepTime after wakeup
os_resume(sleptTime); // set system to schedule tasks and give os info about sleep time
}
Я наблюдал, что происходит, когда я стимулирую прерывание в разных местах выполнения кода. Я использовал NVIC_SetPendingIRQ(PORTCD_IRQn);
для принудительного выполнения IRQ. Я наблюдал, какая задача выполняется логическим анализатором, наблюдающим за выводами GPIO.
случай 1) простой случай: IRQ запускается до того, как os_suspend()
вызывается ISR, и я использую в системе сигнализации ISR osSignalSet(ID_Thread1, SIGNAL_X)
. Поскольку каждый поток имеет более высокий приоритет, чем os_idle_demon
, поток ID_Thread1
, ожидающий в event = osSignalWait(ANY_SIGNAL, osWaitForever);
, переключается в (RTOS) и сигнал обрабатывается. После этого поток снова начинает ждать любого сигнала, и задача os_idle_demon
запланирована, и ARM переходит в спящий режим.
случай 2) Другой случай: IRQ устанавливается между os_suspend()
и __disable_irq()
. Я обнаружил, что когда IRQ вызывается непосредственно перед __disable_irq()
, ARM недостаточно быстро обрабатывает IRQ, и фактически __disable_irq()
выполняется первым. Таким образом, IRQ ожидает вызова __enable_irq()
. Итак, все переходит в другой случай.
случай 3) IRQ устанавливается до __enable_irq()
. Сразу после включения IRQ и до того, как ЦП выполнит DeepsSleep (__wfi();
), выполняется ISR. Сигнал установлен. Но система не может переключить поток (как мы называли os_suspend()
). Но, видимо, WFI каким-то волшебным образом (я все еще изучаю спецификацию, почему) не выполняется. Глубокий сон не вводится, и код продолжает os_resume()
. Тогда задачи переключения ОС и сигнал обрабатывается правильно.
Таким образом, единственный случай с ошибкой - это когда вы помещаете что-то между инструкциями:
__enable_irq();
// do not put anything here
__wfi();
Если вы поместите туда что-нибудь, случай 3 отреагирует следующим образом: ISR выполняется сразу после __enable_irq()
. ISR устанавливает сигнал ОС, но сигнализируемая задача не запланирована (потому что мы вызвали os_suspend()
раньше). Затем __wfi()
входит в глубокий сон. Затем система спит вечно или до LPTMR. Но это ошибка, потому что есть сигнал, который нужно обработать как можно скорее, а это не так!
Итак, вывод: кажется, что последовательность, изображенная в ответе, безопасна. Пока вы не помещаете никаких инструкций между __enable_irq();
и __wfi();
. Также вы не должны помещать никаких инструкций между: os_suspend();
и __disable_irq();
. Это действительно как минимум для MKL17Z256VFT4. Незнаю о другом чипе. Но вы можете проверить себя, установив флаг IRQ с помощью функции NVIC_SetPendingIRQ()
.
--- РЕДАКТИРОВАТЬ ---
Мой друг показал мне также документацию где написано, что даже при отключении прерывания CPSID
ARM просыпается из WFI. Так что, возможно, более безопасная последовательность
__wfi(); // go to DeepSleep
// optionally enable peripherals that might been disabled
__enable_irq();
Не забудьте позвонить __enable_irq();
не позднее, чем позвонить os_resume(sleptTime);
, иначе на моем чипе появится HardFault.
--- РЕДАКТИРОВАТЬ 2 ---
Также я обнаружил, что мы можем использовать инструкцию __WFE();
, чтобы убедиться, что нет гоночных условий. WFE ждет события. Он переводит ЦП в тот же спящий режим, что и WFI. Но он также проверяет «регистр событий». Этот регистр устанавливается на каждой ISR (в конце). Если перед WFE было IRQ, WFE не перейдет в спящий режим. Вы можете дополнительно установить «регистр событий», вызвав инструкцию __SEV();
. "регистр событий" недоступен из ПО. Если вы хотите быть уверены, что очистили его, вы можете позвонить
__SEV(); // set event register if it was not set
__WFE(); // clear event register and don't goto sleep because we set event register just before
Но учтите, что эта инструкция немного отличается от WFI. Например, WFE может просыпаться и при ожидающем IRQ, если установлен SEVONPEND (см. спецификация WFE). (Обратите внимание, что прерывание ожидается, если его приоритет ниже, чем указано в Регистр базовой маски приоритета) См. также Переход в спящий режим. Здесь также очень хорошая таблица о различиях WFI и WFE
person
Vit Bernatik
schedule
30.06.2016