CMSIS-RTOS Keil RTX - 进入ARM深度睡眠的正确方法

CMSIS-RTOS Keil RTX - Proper way to enter ARM deep-sleep

你好我想知道让ARM Cortex M0+进入深度睡眠的正确方法是什么。特别是我使用的是 CMSIS-RTOS RTX。

我的 IRQ 处理方式是 ISR 只是设置 OS 信号并清除 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(它只是在 OS 中设置了信号,一些线程会根据优先级和调度来处理它——我想保持这种状态)。但是当 IRQ 出现在 os_suspend()__wfi() 指令之间时,IRQ 被清除但任务不能被调度(因为 os_suspend())。当 CPU 到达 WFI 时,它会进入睡眠状态,因此 OS 处理来自 ISR 信号的线程永远不会执行。但是 CPU 也没有被 (pad) IRQ 唤醒,因为它已经被处理了。

问题是如何自动检查没有待处理的任务并启动 WFI。

类似

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

我推荐两种方法中的一种。

1) 当我在 OS 上下文切换中使用 wfi() 时,我启用了 SysTick 中断,以便在极少数情况下中断到达 os_suspend() 和 wfi() 之间,系统将仅在 SysTick 中断期间休眠,然后唤醒以检查 OS 状态。这种方法适用于大多数情况。

2) 如果您有严格的实时要求,您可以使用此处记录的退出时睡眠功能:http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/BABHHGEB.html。这可能实现起来更复杂,但是使用中断优先级可以保证 os_suspend() 和进入睡眠之间的原子操作。

所以我有时间在芯片 MKL17Z256VFT4 的 ARM M0+ 上做一些测试。使用 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,因此在 event = osSignalWait(ANY_SIGNAL, osWaitForever); 中等待的线程 ID_Thread1 被切换到(通过 RTOS)并处理信号。在该线程开始再次等待任何信号并且 os_idle_demon 任务被调度并且 ARM 进入睡眠状态之后。

情况2)另一种情况是:IRQ设置在os_suspend()__disable_irq()之间。我发现当在 __disable_irq() 之前调用 IRQ 时,ARM 处理 IRQ 的速度不够快,实际上 __disable_irq() 首先执行。所以 IRQ 一直等到 __enable_irq() 被调用。所以一切都进入另一个案例。

case 3) IRQ设置在__enable_irq()之前。在启用 IRQ 之后和 CPU 执行 DeepsSleep (__wfi();) 之前,ISR 被执行。信号已设置。但是系统不能切换线程(我们称之为os_suspend())。但显然 WFI 以某种方式神奇地(我仍在研究具体原因)未执行。深度睡眠未结束,代码继续 os_resume()。然后 OS 切换任务和信号被正确处理。

所以唯一的错误是当你在指令之间放置一些东西时:

__enable_irq();
// do not put anything here
__wfi();

如果你在那里放任何东西,那么情况 3 会这样反应:ISR 在 __enable_irq() 之后立即执行。 ISR 设置了 OS 信号,但未安排有信号的任务(因为我们之前调用了 os_suspend())。然后通过__wfi()进入深度睡眠。系统然后永远休眠或直到 LPTMR。但这是错误的,因为有信号应该尽快处理,但没有!

所以结论是,响应中描述的序列似乎是安全的。只要您不在 __enable_irq();__wfi(); 之间放置任何指令。此外,您不得在 os_suspend();__disable_irq(); 之间放置任何指令。这至少对 MKL17Z256VFT4 有效。不知道其他芯片。但是您可以通过使用函数 NVIC_SetPendingIRQ()

强制执行 IRQ 标志来测试自己

--- 编辑 ---

所以我的朋友还向我展示了 documentation 哪里写到即使您禁用中断 CPSID ARM 也会从 WFI 中唤醒。所以也许更安全的顺序是

__wfi();   // go to DeepSleep
// optionally enable peripherals that might been disabled
__enable_irq();

不要忘记最迟在调用 os_resume(sleptTime); 之前调用 __enable_irq(); 否则在我的芯片上我会遇到 HardFault。

--- 编辑 2 ---

我还发现我们可以使用 __WFE(); 指令来确保没有竞争条件。 WFE 是等待事件。它将 CPU 置于与 WFI 相同的睡眠模式。但它也会检查 "event register"。该寄存器在每个 ISR(最后)上设置。当 WFE 之前有 IRQ 时,WFE 不会进入休眠状态。您可以选择通过调用指令 __SEV(); 来设置 "event register"。 "event register" 无法从 SW 访问。如果你想确保清除它,你可以调用

__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 的行为略有不同。例如,如果设置了 SEVONPEND,WFE 也可以在挂起的 IRQ 上唤醒(参见 WFE spec). (Note interrupt is pending if has less priority than configured in Base Priority Mask Register)See also Entering Sleep Mode. Here is also very nice table about difference WFI vs WFE