CMSIS-RTOS Keil RTX - правильный способ перехода в режим глубокого сна ARM

Здравствуйте, я хотел бы знать, как правильно перевести ARM Cortex M0 + в режим глубокого сна. В частности, я использую CMSIS-RTOS RTX.

Мой IRQ обрабатывается так: ISR просто устанавливает сигнал ОС и очищает IRQ. Например.:

void ISR_A(){
  osSignalSet(ID_Task_Handling_IRQ_A, IRQ_A_SIGNAL_CODE);
  DisableIRQ_A();
}

Тогда в моем холостом цикле

void os_idle_demon(void) {
...
timeToSleep = os_suspend(); // get from OS how long I can sleep and also stop OS scheduling
LPTMR_Init(timeToSleep,...) // set wakeup timer
POWER_EnterLLS(void)        // enter deep sleep. Set registers and calls WFI instruction
// after wakup compute actual slpetTime
os_resume(sleptTime); // enable OS scheduling
}

Проблема в том, что мой ISR не обрабатывает IRQ полностью (он просто устанавливает сигнал в ОС, и какой-то поток будет обрабатывать его в соответствии с приоритетом и расписанием - я бы хотел, чтобы это было так). Но когда IRQ находится между os_suspend() и __wfi() инструкциями, тогда IRQ очищается, но задача не может быть запланирована (потому что os_suspend()). Когда ЦП переходит в WFI, он переходит в спящий режим, и поэтому поток ОС, который должен обрабатывать сигнал от ISR, никогда не выполняется. Но ЦП также не просыпается (вводом) IRQ, потому что оно уже обработано.

Вопрос в том, как атомарно проверить отсутствие отложенных задач и запустить WFI.

Что-то вроде

if( ! OS_Signal_Is_rised) {  
  // only do it atomically because what if IRQ would come here?
  wfi; 
}

person Vit Bernatik    schedule 08.03.2016    source источник


Ответы (2)


Так что у меня было время протестировать 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

Я рекомендую один из двух подходов.

1) Когда я использую wfi () в переключении контекста ОС, я включаю прерывание SysTick, так что в редких случаях прерывание приходит между os_suspend () и wfi (), система будет спать только на время прерывания SysTick, а затем проснуться, чтобы проверить статус ОС. Такой подход работает в большинстве ситуаций.

2) Если у вас есть жесткие требования к работе в реальном времени, вы можете использовать функцию сна при выходе, описанную здесь: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/BABHHGEB.html . Это, вероятно, будет сложнее реализовать, но используя приоритеты прерывания, вы можете гарантировать атомарную операцию между os_suspend () и переходом в спящий режим.

person TylerG    schedule 24.04.2016
comment
спасибо за комментарий, но в случае 1) я не могу позволить, чтобы сигнал был обработан через некоторое время. А установка короткого времени будет означать, что я не сплю слишком долго (мое приложение работает от батареи, поэтому сон имеет решающее значение). объявление 2) У меня ОСРВ. Поэтому я не могу запускать только код в ISR и спать, когда нет ISR ... - person Vit Bernatik; 27.04.2016