Cortex (ARM) 微控制器上 WFI(等待中断)的最佳模式

电器工程 手臂 皮质-m3 睡觉
2022-01-30 23:52:42

我正在研究使用 EFM Gekko 控制器 (http://energymicro.com/) 开发电池供电的软件,并且希望控制器在没有任何用处时处于睡眠状态。WFI(等待中断)指令用于此目的;它将使处理器进入睡眠状态,直到发生中断。

如果睡眠是通过在某个地方存储某些东西来进行的,则可以使用 load-exclusive/store-exclusive 操作来执行以下操作:

  // dont_sleep 会在任何时候加载 2
  // 应该强制主循环至少循环一次。如果一个中断
  // 发生导致它在以下语句中重置为 2,
  // 行为就像中断发生在它之后一样。

  store_exclusive(load_exclusive(dont_sleep) >> 1);

  而(!dont_sleep)
  {
    // 如果在 next 语句和 store_exclusive 之间发生中断,不要休眠
    load_exclusive(SLEEP_TRIGGER);
    如果(!dont_sleep)             
      store_exclusive(SLEEP_TRIGGER);
  }

如果在 load_exclusive 和 store_exclusive 操作之间发生中断,效果将是跳过 store_exclusive,从而导致系统再循环一次(查看中断是否设置了 dont_sleep)。不幸的是,Gekko 使用 WFI 指令而不是写地址来触发睡眠模式;像这样写代码

  如果(!dont_sleep)
    WFI();

会冒在“if”和“wfi”之间发生中断并设置 dont_sleep 的风险,但 wfi 会继续执行。防止这种情况的最佳模式是什么?将 PRIMASK 设置为 1 以防止在执行 WFI 之前中断中断处理器,并在之后立即清除它?还是有什么更好的技巧?

编辑

我想知道事件位。根据一般描述,它希望它适用于多处理器支持,但想知道以下内容是否可行:

  如果 (dont_sleep)
    SEV(); /* 将使以下 WFE 清除事件标志但不休眠 */
  WFE();

每个设置 don't_sleep 的中断也应该执行一条 SEV 指令,所以如果中断发生在“if”测试之后,WFE 将清除事件标志但不会进入睡眠状态。这听起来像是一个很好的范例吗?

4个回答

把它放在一个关键部分。ISR 不会运行,因此您不会冒在 WFI 之前更改 dont_sleep 的风险,但它们仍会唤醒处理器,并且 ISR 将在关键部分结束后立即执行。

uint8 interruptStatus;
interruptStatus = EnterCriticalSection();
if (!dont_sleep)
  WFI();
ExitCriticalSection(interruptStatus);

你的开发环境可能有临界区函数,但大致是这样的:

EnterCriticalSection 是:

MRS r0, PRIMASK /* Save interrupt state. */
CPSID i /* Turn off interrupts. */
BX lr /* Return. */

ExitCriticalSection 是:

MSR PRIMASK, r0 /* Restore interrupt states. */
BX lr /* Return. */

你的想法很好,这正是 Linux 实现的。这里

来自上述讨论线程的有用引用,以阐明为什么即使在禁用中断的情况下 WFI 也能工作:

如果你打算空闲到下一次中断,你必须做一些准备。在该准备期间,可能会激活一个中断。这样的中断可能是您正在寻找的唤醒事件。

不管你的代码有多好,如果你不禁用中断,你总是会在准备进入睡眠和实际进入睡眠之间进行竞争,这会导致丢失唤醒事件。

这就是为什么我所知道的所有 ARM CPU 都会唤醒,即使它们在核心 CPU 上被屏蔽(CPSR I 位)。

其他任何事情,您都应该忘记使用空闲模式。

我没有完全理解这dont_sleep件事,但是您可以尝试的一件事是在 PendSV 处理程序中执行“主要工作”,并将其设置为最低优先级。然后,每次您需要完成某事时,只需从其他处理程序中安排一个 PendSV。看这里怎么做(它适用于 M1,但 M3 并没有太大的不同)。

您可以使用的另一件事(可能与以前的方法一起使用)是退出时睡眠功能。如果启用它,处理器将在退出最后一个 ISR 处理程序后进入睡眠状态,而无需调用 WFI。在此处查看一些示例

假如说:

  1. 主线程运行后台任务
  2. 中断只运行高优先级任务,不运行后台任务
  3. 主线程可以随时中断(它通常不会屏蔽中断)

那么解决方案是使用PRIMASK来阻止标志验证和WFI之间的中断:

mask_interrupts();
if (!dont_sleep)
    wfi();
unmask_interrupts();