Arm CMSIS-RTOS Keil RTX-进入手臂深度睡眠的正确方式
你好,我想知道什么是让手臂皮层M0+进入深度睡眠的正确方法。特别是我使用的是CMSIS-RTOS RTX 我的IRQ处理方式是ISR只设置操作系统信号并清除IRQ。例如:Arm CMSIS-RTOS Keil RTX-进入手臂深度睡眠的正确方式,arm,cortex-m,sleep-mode,Arm,Cortex M,Sleep Mode,你好,我想知道什么是让手臂皮层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(); /
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\u suspend()
和\u wfi()
指令之间时,IRQ被清除,但任务无法调度(因为os\u 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;
}
我推荐两种方法中的一种 1) 当我在操作系统上下文切换中使用wfi()时,我会启用SysTick中断,以便在极少数情况下,中断在OS_suspend()和wfi()之间到达,系统将仅在SysTick中断期间休眠,然后唤醒以检查操作系统状态。这种方法在大多数情况下都有效
2) 如果您有硬实时要求,可以使用此处介绍的“退出时睡眠”功能:。这可能会更加复杂,但使用中断优先级可以保证在os_suspend()和进入睡眠之间进行原子操作。因此我有时间在芯片MKL17Z256VFT4中的ARM M0+上进行一些测试。使用CMSIS-RTOS RTX(V4.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系统信令中使用ISR(ID\U Thread1,SIGNAL\U X)
。由于每个线程的优先级都高于os\u idle\u demon
线程ID\u Thread1
,该线程正在event=osignalwait(任何信号,osWaitForever)中等待代码>切换到(由RTOS)并处理信号。在此之后,线程开始再次等待任何信号,并计划任务,ARM进入睡眠状态
情况2)另一种情况是:IRQ设置在os\u suspend()
和\u disable\u IRQ()
之间。我发现在\uu disable\u IRQ()
之前调用IRQ时,ARM处理IRQ的速度不够快,实际上是先执行\uu disable\u IRQ()
。所以IRQ会一直等到调用\u enable\u IRQ()
。因此,所有这些都进入了另一个案例
案例3)IRQ设置在\u启用\u IRQ()
之前。在启用IRQ之后,在CPU执行深度睡眠(\uu wfi();
)之前,执行ISR。信号已设置。但系统不能切换线程(我们称之为os\u suspend()
)。但很明显,WFI以某种神奇的方式(我仍在研究规范为什么)没有被执行。深度睡眠没有中断,代码继续os\u resume()
。然后操作系统开关任务和信号被正确处理
因此,唯一的错误情况是在指令之间放置某些内容:
__enable_irq();
// do not put anything here
__wfi();
如果你把任何东西放在那里,那么案例3的反应是这样的:ISR在\uu enable\u irq()
之后立即执行。ISR设置操作系统信号,但未计划发出信号的任务(因为我们以前调用了OS\u suspend()
)。然后通过\uuwfi()
进入深度睡眠。然后系统将永远休眠或直到LPTMR。但这是错误的,因为有信号需要尽快处理,而事实并非如此
所以结论是,反应中描述的序列似乎是安全的。只要不在\uu enable\u irq()之间插入任何指令代码>和\uuuwfi()代码>。此外,您不应将任何指令置于以下两者之间:os_suspend()代码>和禁用irq()代码>。这至少对MKL17Z256VFT4有效。不知道还有其他芯片。但是您可以使用函数NVIC\u setpendingingirq()
---编辑---
因此,我的朋友也告诉我,哪怕你禁用中断CPSID
时,手臂也会从WFI中唤醒。所以也许更安全的顺序是
__wfi(); // go to DeepSleep
// optionally enable peripherals that might been disabled
__enable_irq();
不要忘记调用\uuuu enable\u irq()代码>最迟在调用操作系统恢复(SLEPTIME)之前代码>否则在我的芯片上会出现硬故障
---编辑2---
我还发现我们可以使用\uuwfe()代码>说明,以确保没有赛车条件。WFE正在等待事件。它将CPU置于与WFI相同的睡眠模式。但它也检查“事件寄存器”。在每个ISR上设置此寄存器(在末尾)。当在WFE之前有IRQ时,WFE将不会入睡。您可以通过调用指令\uu 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略有不同。例如,如果设置了SEVONPEND,WFE也可以在挂起的IRQ上唤醒(请参阅)。(注意,如果中断优先级低于中配置的优先级,则中断处于挂起状态)另请参阅。这里也是非常感谢您的评论,但在案例1)我负担不起长时间后处理的信号。设置较短的时间意味着我不会睡太久(我的应用程序是用电池运行的,所以睡眠至关重要)。广告2)我有RTO。所以我不能只在ISR中运行代码,而在没有ISR的情况下睡觉。。。