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()和进入睡眠之间的原子操作。

+0

谢谢你的评论,但在情况下1)我买不起信号要处理很长一段时间。设定很短的时间意味着我不会睡得太久(我的应用程序从电池运行,所以睡眠是至关重要的)。广告2)我有RTOS。所以我不能在ISR中只运行代码,在没有ISR的情况下睡眠。 –