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


18

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

如果通过在某个地方存储东西来进行睡眠,则可以使用独占负载/独占存储操作来执行以下操作:

  //每当发生某事时,dont_sleep都会加载2
  //应该强制主循环至少循环一次。如果有中断
  //在以下语句中导致将其重置为2,
  //行为就像中断发生在它之后一样。

  store_exclusive(load_exclusive(dont_sleep)>> 1);

  while(!dont_sleep)
  {
    //如果下一条语句与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将清除事件标志,但不会进入睡眠状态。听起来像是一个好范例吗?


1
如果在执行指令时其唤醒条件为真,则WFI指令不会使内核进入睡眠状态。例如,如果在执行WFI时有未清除的IRQ,则它将充当NOP。
标记

@Mark:问题在于,如果在“ if(!dont_sleep)”和“ WFI”之间进行了中断,则在执行WFI时,中断条件将不再处于挂起状态,但是该中断可能已将dont_sleep设置为该中断做的事情可以证明主循环运行另一个迭代是合理的。在我的赛普拉斯PSOC应用程序上,如果主代码即将进入睡眠状态,则任何可能导致长时间唤醒的中断都会使堆栈混乱,但这似乎很棘手,而且我了解ARM会阻止此类堆栈操作。
supercat

@supercat执行WFI时可能会清除中断,也可能不会清除中断。它取决于您,您选择清除中断的时间/地点。摆脱dont_sleep变量,仅使用带掩码的中断来表示您想保持清醒或睡眠状态。您可以完全摆脱if语句,而将WFI留在主循环的末尾。如果已处理所有请求,请清除IRQ,以便您可以入睡。如果您需要保持清醒状态,请触发IRQ,将其屏蔽,这样就不会发生任何事情,但是当WFI尝试执行时,它会NOP。
标记

2
@supercat在更基本的层面上,您似乎试图将中断驱动的设计与“大主循环”设计混合使用,这通常不是时间紧迫的,通常基于轮询并且中断最少。将它们混合起来会变得非常难看而很快。尽可能选择一种设计范例或另一种使用。请记住,使用现代的中断控制器,您基本上可以在中断之间获得抢先式多任务处理,以及在任务队列中所占的比重(服务一个中断,然后处理下一个更高的优先级,等等)。利用这个优势。
标记

@Mark:我开发了一个在电池供电的应用中很好地使用了PIC 18x的系统。由于堆栈的限制,它不能在中断中处理太多,因此,绝大多数内容都是在主循环中方便地处理的。尽管有很多地方由于长时间运行的操作而导致一两秒的阻塞,但大多数情况下它都运行良好。如果迁移到ARM,则可以使用简单的RTOS来简化长时间运行的操作,但是我不确定是否要使用抢占式或协作式多任务处理。
supercat

Answers:


3

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

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


5
WFI指令不需要使能中断来唤醒处理器,CPSR中的F和I位将被忽略。
标记

1
@Mark我一定在文档中错过了这一点,您对此有一些链接/指针吗?唤醒内核的中断信号会如何处理?它是否保持挂起状态,直到再次允许中断?
伊戈尔·斯科钦斯基

ASM参考手册在这里:infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489c/…关于cortex-m3的更多特定信息在这里:infocenter.arm.com/help/ index.jsp?topic = / com.arm.doc.dui0552a /…简而言之,当屏蔽中断待处理时,内核将唤醒并在WFI指令后继续操作。如果您尝试发布另一个WFI而不清除待处理的中断,则WFI将充当NOP(由于WFI的唤醒条件为true,因此核心不会休眠)。
标记

@Mark:我正在考虑的一件事是让设置dont_sleep的任何中断处理程序也执行SEV(“设置事件”)指令,然后使用WFE(“ Wait For Event”)而不是WFI。Gekko的示例似乎使用了WFI,但我认为WFE也可能会起作用。有什么想法吗?
supercat

10

将其放在关键部分。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. */

2
奇怪的是,许多ARM库使用临界区实现,该实现使用全局计数器而不是在本地保存状态。我发现这很麻烦,因为计数器方法更加复杂,并且只有在系统范围内所有代码都使用相同的计数器的情况下,该方法才有效。
超级猫

1
在我们退出关键部分之前,不会禁用中断吗?如果是这样,WFI是否会导致CPU无限期等待?
Corneliu Zuzu

1
@Kenzi Shrimp的答案指向一个Linux讨论线程,该线程可以回答我之前的问题。我编辑了他和你的答案以澄清这一点。
Corneliu Zuzu

@CorneliuZuzu编辑别人的答案来插入您自己的讨论不是一个好主意。添加引号以改善“仅链接”答案是另一回事。如果您对自己的获胜有真正的疑问,也许可以将其作为一个问题提出,然后链接到此问题。
肖恩·霍利哈内

1
@SeanHoulihane我没有使她的回答无效或删除任何内容。简短说明为何会奏效不是单独讨论。老实说,如果没有WFI的说明,我不认为这个答案值得投票,但确实可以得到最大的评价。
Corneliu Zuzu

7

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

来自上述讨论线程的有用报价,用于阐明即使禁用了中断,WFI仍能工作的原因:

如果您打算闲置直到下一个中​​断,则必须做一些准备。在准备过程中,中断可能会激活。这样的中断可能是您正在寻找的唤醒事件。

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

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

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


1
您是指在WFI或WFE指令时禁用中断吗?在使用WFI或WFE达成目的之间,您是否看到有意义的区别?
supercat 2012年

1
@supercat:我肯定会使用WFI。WFE IMO主要用于多核系统中内核之间的同步提示(例如,在自旋锁上执行WFE使失败,并在自旋锁退出后发出SEV)。此外,WFE还考虑了中断屏蔽标志,因此在这里它不如WFI有用。这种模式在Linux中确实很好用。

2

假如说:

  1. 主线程运行后台任务
  2. 中断仅运行高优先级任务,无后台任务
  3. 主线程可以随时中断(通常不会掩盖中断)

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

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

0

那么退出模式下的睡眠呢?每当IRQ处理程序退出时,它都会自动进入睡眠状态,因此在配置了该请求后,实际上并没有运行任何“正常模式”。发生IRQ,它唤醒并运行处理程序,然后返回睡眠状态。无需WFI。


2
如何最好地处理这样一个事实,即处理器应进入的睡眠类型可能会根据中断期间发生的情况而有所不同?例如,引脚更改事件可能表示可能正在发送串行数据,因此处理器在等待数据时应保持主时钟振荡器运行。如果主循环清除事件标志,检查正在发生的情况,并通过WFI将处理器置于适当的睡眠模式,则任何可能影响哪种模式的中断都将设置事件标志...
supercat

...然后中止睡眠 由一个主循环处理程序控制睡眠模式似乎比每次中断都要担心都要干净。必须在每个中断上“旋转”主循环的那部分可能不是最佳效率,但是应该不会太糟,特别是如果所有可能影响睡眠行为的中断都达到某个标志。
supercat
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.