CMSIS-RTOS Keil RTX - 进入ARM深度睡眠的正确方法
您好,我想知道将ARM Cortex M0 +深度睡眠的正确方法是什么。特别是我使用CMSIS-RTOS RTX。CMSIS-RTOS Keil RTX - 进入ARM深度睡眠的正确方法
我的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(它只是设置信号OS和一些线程将根据优先级处理,并调度 - 我想保持这种方式)。但是当IRQ进入os_suspend()
和__wfi()
指令之间时,则清除IRQ,但不能调度任务(因为os_suspend()
)。当CPU进入WFI时,它会进入休眠状态,因此处理来自ISR的信号的OS线程将不会执行。但是CPU也没有被(pad)IRQ唤醒,因为这已经被处理了。
问题是如何自动做检查,没有任务挂起并启动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)容易的情况是:在调用ISR之前触发IRQ,并在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()
被调用。所有这些都涉及另一个案例。
情况3)在__enable_irq()
之前设置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_suspend()
)。然后通过__wfi()
进入深度睡眠。系统然后永久休眠或直到LPTMR。但这是错误的,因为有信号应该尽快处理,而不是!
所以结论是,看起来序列中描绘的序列是安全的。只要你没有在__enable_irq();
和__wfi();
之间放置任何指令。您也不得在中间放置任何指示:os_suspend();
和__disable_irq();
。这至少对于MKL17Z256VFT4有效。不知道其他芯片。但是你可以通过执行该IRQ标记功能测试自己NVIC_SetPendingIRQ()
---编辑---
所以,我的朋友给我看了也01其中写入,即使你关闭中断CPSID
的ARM唤醒功能WFI。因此,也许更安全的序列是
__wfi(); // go to DeepSleep
// optionally enable peripherals that might been disabled
__enable_irq();
不要忘了最新的你叫os_resume(sleptTime);
之前,否则我的芯片上,我得到HardFault打电话__enable_irq();
。
--- EDIT 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
我推荐以下两种方法之一。当我在操作系统上下文切换中使用wfi()时,我启用了SysTick中断,因此在极少数情况下,中断在os_suspend()和wfi()之间到达,系统只会在SysTick中断,然后唤醒以检查操作系统状态。这种方法适用于大多数情况。 2)如果您有严格的实时要求,可以使用此处记录的睡眠退出功能:http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/BABHHGEB.html。这可能会更复杂的实现,但使用中断优先级,你可以保证os_suspend()和进入睡眠之间的原子操作。
谢谢你的评论,但在情况下1)我买不起信号要处理很长一段时间。设定很短的时间意味着我不会睡得太久(我的应用程序从电池运行,所以睡眠是至关重要的)。广告2)我有RTOS。所以我不能在ISR中只运行代码,在没有ISR的情况下睡眠。 –