3

您好,我想知道让 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(它只是在操作系统中设置信号,一些线程会根据优先级和调度来处理它——我想保持这种方式)。但是当 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; 
}
4

2 回答 2

2

所以我有时间对芯片 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的线程具有更高的优先级,因此(由 RTOS)切换到并处理信号。在该线程再次开始等待任何信号并且计划任务并且ARM进入睡眠状态之后。ID_Thread1event = osSignalWait(ANY_SIGNAL, osWaitForever);os_idle_demon

case 2) 另一种情况是:IRQ 设置在os_suspend()和之间__disable_irq()。我发现在__disable_irq()ARM 之前调用 IRQ 的速度不够快,无法处理 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 有效。不知道其他芯片。但是您可以通过使用函数强制执行 IRQ 标志来测试自己NVIC_SetPendingIRQ()

- - 编辑 - -

因此,我的朋友还向我展示了文档,即使您禁用中断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 规范)。(注意,如果优先级低于基本优先级屏蔽寄存器中配置的优先级,则中断挂起)另见进入睡眠模式。这也是关于 WFI 与 WFE 差异的非常好的表格

于 2016-06-30T16:27:52.787 回答
1

我推荐两种方法之一。

1) 当我在 OS 上下文切换中使用 wfi() 时,我启用了 SysTick 中断,以便在极少数情况下,中断到达 os_suspend() 和 wfi() 之间,系统只会在 SysTick 中断期间休眠,然后唤醒以检查操作系统状态。这种方法适用于大多数情况。

2)如果您有严格的实时要求,您可以使用此处记录的退出时睡眠功能:http: //infocenter.arm.com/help/index.jsp?topic=/ com.arm.doc.dui0552a /BABHHGEB.html。这可能实现起来更复杂,但是使用中断优先级可以保证 os_suspend() 和进入睡眠之间的原子操作。

于 2016-04-24T16:46:29.437 回答