所以我有时间对芯片 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_Thread1
event = 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 差异的非常好的表格