微控制器睡眠竞争条件

电器工程 微控制器 C 低电量 C++ 睡觉
2022-01-26 07:08:32

给定一个运行以下代码的微控制器:

volatile bool has_flag = false;

void interrupt(void) //called when an interrupt is received
{
    clear_interrupt_flag(); //clear interrupt flag
    has_flag = true; //signal that we have an interrupt to process
}

int main()
{
    while(1)
    {
        if(has_flag) //if we had an interrupt
        {
            has_flag = false; //clear the interrupt flag
            process(); //process the interrupt
        }
        else
            sleep(); //place the micro to sleep
    }
}

假设if(has_flag)条件评估为,我们即将执行睡眠指令。 在我们执行sleep指令之前,我们收到一个中断。离开中断后,我们执行睡眠指令。

这个执行顺序是不可取的,因为:

  • 微控制器进入睡眠状态,而不是醒来并调用process()
  • 如果此后没有接收到中断,微控制器可能永远不会唤醒。
  • 调用process()被推迟到下一次中断。

如何编写代码来防止这种竞争条件的发生?

编辑

一些微控制器,例如 ATMega,有一个睡眠使能位,可以防止这种情况发生(感谢 Kvegaoro 指出这一点)。JRoberts 提供了一个示例实现来说明这种行为。

其他的micro,比如PIC18s,没有这个位,问题依旧。但是,这些微控制器的设计使得无论是否设置了全局中断使能位,中断仍然可以唤醒内核(感谢 supercat 指出这一点)。对于此类架构,解决方案是在进入睡眠状态之前禁用全局中断。如果在执行睡眠指令之前触发了中断,则不会执行中断处理程序,内核将被唤醒,并且一旦重新启用全局中断,将执行中断处理程序。在伪代码中,实现如下所示:

int main()
{
    while(1)
    {
        //clear global interrupt enable bit.
        //if the flag tested below is not set, then we enter
        //sleep with the global interrupt bit cleared, which is
        //the intended behavior.
        disable_global_interrupts();

        if(has_flag) //if we had an interrupt
        {
            has_flag = false; //clear the interrupt flag
            enable_global_interrupts();  //set global interrupt enable bit.

            process(); //process the interrupt
        }
        else
            sleep(); //place the micro to sleep
    }
}
3个回答

这种情况通常有某种硬件支持。例如,AVRsei启用中断的指令延迟启用,直到下一个指令完成。有了它,一个人可以做到:

forever,
   interrupts off;
   if has_flag,
      interrupts on;
      process interrupt;
   else,
      interrupts-on-and-sleep;    # won't be interrupted
   end
end

在这种情况下,本例中错过的中断将被推迟,直到处理器完成其睡眠序列。

在许多微控制器上,除了能够启用或禁用特定的中断原因(通常在中断控制器模块中)之外,CPU 内核中还有一个主标志,用于确定是否接受中断请求。如果中断请求到达内核,许多微控制器将退出睡眠模式,无论内核是否愿意实际接受它。

在这样的设计中,实现可靠睡眠行为的一个简单方法是让主循环检查清除一个标志,然后检查它是否知道处理器应该唤醒的任何原因。在此期间发生的任何可能影响任何这些原因的中断都应设置该标志。如果主循环没有找到任何保持唤醒的原因,并且标志未设置,则主循环应该禁用中断并再次检查标志[也许在几个 NOP 指令之后,如果有可能出现未决的中断在与以下指令关联的操作数提取已经执行之后,可能会处理禁用中断指令]。如果标志仍未设置,则进入睡眠状态。

在这种情况下,在主循环禁用中断之前发生的中断将在最终测试之前设置标志。在睡眠指令之前等待处理的中断太晚了,将阻止处理器进入睡眠状态。这两种情况都很好。

退出时休眠有时是一个很好的使用模型,但并非所有应用程序都真正“适合”它。例如,具有节能 LCD 的设备可能最容易使用如下代码进行编程:

void select_view_user(int default_user)
{
  int current_user;
  int ch;
  current_user = default_user;
  do
  {
    lcd_printf(0, "User %d");
    lcd_printf(1, ...whatever... );
    get_key();
    if (key_hit(KEY_UP)) {current_user = (current_user + 1) % MAX_USERS};
    if (key_hit(KEY_DOWN)) {current_user = (current_user + MAX_USERS-1) % MAX_USERS};
    if (key_hit(KEY_ENTER)) view_user(current_user);
  } while(!key_hit(KEY_EXIT | KEY_TIMEOUT));
}

如果没有按下任何按钮,并且没有其他任何事情发生,那么系统没有理由在get_key方法执行期间不应该进入睡眠状态。虽然可以让按键触发中断,并通过状态机管理所有用户界面交互,但上述代码通常是处理小型微控制器典型的高度模态用户界面流程的最合乎逻辑的方式。

对微控制器进行编程以在中断时唤醒。

具体细节将根据您使用的微机而有所不同。

然后修改 main() 例程:

int main()
{
    while(1)
    {
        sleep();
        process(); //process the interrupt
    }
}