您好,我想知道使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