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

我的IRQ的处理方式是ISR只需设置OS Signal并清除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时,它将进入睡眠状态,因此应处理ISR信号的OS线程将永远不会执行。但是,(填充)IRQ也不会唤醒CPU,因为它已经被处理。

问题是如何自动执行检查是否有未决任务并启动WFI。

就像是

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

最佳答案

所以我有时间对芯片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)简单的情况是:在os_suspend()调用ISR之前触发IRQ,我在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()之间。我发现,在__disable_irq()之前调用IRQ时,ARM不够快,无法处理IRQ,实际上__disable_irq()首先执行。因此,IRQ等待直到__enable_irq()被调用。因此,所有情况都进入了另一种情况。

情况3)IRQ设置在__enable_irq()之前。在启用IRQ之后,在CPU做DeepsSleep(__wfi();)之前,立即执行ISR。信号已设定。但是系统无法切换线程(我们称为os_suspend())。但显然WFI某种程度上没有执行(我仍在研究规范原因)。不会进入深度睡眠状态,并且代码将继续os_resume()。然后,操作系统切换任务和信号将得到正确处理。

因此,唯一的错误案例是您在指令之间放了一些东西:

__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有效。 Dunno关于其他芯片。但是您可以使用功能NVIC_SetPendingIRQ()强制执行IRQ标志来测试自己

-编辑-

因此,我的朋友还向我展示了documentation的位置,即使禁用中断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是等待事件。它将CPU置于与WFI相同的睡眠模式。但是它也检查“事件寄存器”。该寄存器在每个ISR上设置(最后)。如果在WFE之前有IRQ,则WFE将不会进入睡眠状态。您可以选择通过调用指令__SEV();来设置“事件寄存器”。无法从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)。 (请注意,如果优先级比Base Priority Mask Register中配置的优先级低,则挂起中断)。另请参见Entering Sleep Mode。这也是nice table about difference WFI vs WFE

08-05 13:37