微控制器和 FSM 示例中的中断处理

电器工程 微控制器 嵌入式 中断 状态机
2022-02-02 17:43:25

最初的问题

我有一个关于微控制器中断处理的一般性问题。我正在使用 MSP430,但我认为这个问题可能会扩展到其他 uC。我想知道在代码中频繁启用/禁用中断是否是一种好习惯。我的意思是,如果我有一部分代码对中断不敏感(或者更糟糕的是,无论出于何种原因,都不能听中断),那么最好:

  • 在临界区之前禁用中断,然后在临界区之后重新启用它们。
  • 在相应的 ISR 中放置一个标志(而不是禁用中断),在临界区之前将标志设置为 false,然后将其重置为 true。防止ISR的代码被执行。
  • 两者都不是,所以欢迎提出建议!

更新:中断和状态图

我会提供一个具体的情况。假设我们要实现一个状态图,它由 4 个块组成:

  1. 过渡/效果。
  2. 退出条件。
  3. 入场活动。
  4. 做活动。

这是一位教授在大学里教给我们的。可能不是最好的方法是遵循这个方案:

while(true) {

  /* Transitions/Effects */
  //----------------------------------------------------------------------
  next_state = current_state;

  switch (current_state)
  {
    case STATE_A:
      if(EVENT1) {next_state = STATE_C}
      if(d == THRESHOLD) {next_state = STATE_D; a++}
      break;
    case STATE_B:
      // transitions and effects
      break;
    (...)
  }

  /* Exit activity -> only performed if I leave the state for a new one */
  //----------------------------------------------------------------------
  if (next_state != current_state)
  {
    switch(current_state)
    {
      case STATE_A:
        // Exit activity of STATE_A
        break;
      case STATE_B:
        // Exit activity of STATE_B
        break;
        (...)
    }
  }

  /* Entry activity -> only performed the 1st time I enter a state */
  //----------------------------------------------------------------------
  if (next_state != current_state)
  {
    switch(next_state)
    {
      case STATE_A:
        // Entry activity of STATE_A
        break;
      case STATE_B:
        // Entry activity of STATE_B
        break;
      (...)
    }
  }

  current_state = next_state;

  /* Do activity */
  //----------------------------------------------------------------------
  switch (current_state)
  {
    case STATE_A:
      // Do activity of STATE_A
      break;
    case STATE_B:
      // Do activity of STATE_B
      break;
    (...)
  }
}

让我们也假设从,比如说STATE_A,我想对来自一组按钮(使用 debouce 系统等)的中断敏感。当有人按下其中一个按钮时,会产生一个中断,并且与输入端口相关的标志被复制到一个变量buttonPressed中。如果以某种方式(看门狗定时器、定时器、计数器……)将去抖动设置为 200 毫秒,我们确信buttonPressed在 200 毫秒之前无法使用新值进行更新。这就是我要问你(和我自己:)当然)

我是否需要在 DO 活动中启用中断STATE_A并在离开前禁用?

/* Do activity */
//-------------------------------------
switch (current_state)
{
  case STATE_A:
    // Do activity of STATE_A
    Enable_ButtonsInterrupt(); // and clear flags before it
    // Do fancy stuff and ...
    // ... wait until a button is pressed (e.g. LPM3 of the MSP430)
    // Here I have my buttonPressed flag ready!
    Disable_ButtonsInterrupt();
    break;
  case STATE_B:
    // Do activity of STATE_B
    break;
  (...)
}

在某种程度上,我确信下次我在下一次迭代中执行块 1(转换/效果)时,我确信沿着转换检查的条件不是来自后续中断,该中断已经覆盖了buttonPressed我之前的值需要(尽管这不可能发生,因为必须经过 250 毫秒)。

4个回答

第一种策略是构建整体固件,以便随时发生中断。必须谨慎地关闭中断以便前台代码可以执行原子序列。通常有一种围绕它的架构方式。

但是,机器是为您服务的,而不是相反。一般的经验法则只是为了防止糟糕的程序员编写非常糟糕的代码。更好地了解机器的工作原理,然后设计一种利用这些功能执行所需任务的好方法。

请记住,除非您真的对周期或内存位置非常严格(肯定会发生),否则您希望优化代码的清晰度和可维护性。例如,如果您有一台 16 位机器,它在时钟滴答中断中更新一个 32 位计数器,您需要确保前台代码读取计数器时,它的两半是一致的。一种方法是关闭中断,读取这两个字,然后重新打开中断。如果中断延迟不重要,那么这是完全可以接受的。

在您必须具有低中断延迟的情况下,您可以例如读取高位字、读取低位字、再次读取高位字并在它改变时重复。这会稍微减慢前台代码的速度,但根本不会增加中断延迟。有各种小技巧。另一种可能是在中断例程中设置一个标志,指示计数器必须递增,然后在前台代码的主事件循环中执行此操作。如果计数器中断速率足够慢,这样事件循环将在再次设置标志之前进行增量,则该方法可以正常工作。

或者,使用单字计数器代替标志。前台代码保留一个单独的变量,该变量包含它已将系统更新到的最后一个计数器。它对实时计数器减去保存的值进行无符号减法,以确定一次必须处理多少滴答声。这允许前台代码一次最多错过 2 N -1 个事件,其中 N 是 ALU 可以原子处理的本机字中的位数。

每种方法都有自己的优点和缺点。没有唯一的正确答案。再次,了解机器的工作原理,那么您将不需要经验法则。

如果你需要一个临界区,你必须确保保护你的临界区的操作是原子的并且不能被中断。

因此,禁用通常由单个处理器指令处理(并使用编译器内在函数调用)的中断是您可以采取的最安全的选择之一。

根据您的系统,可能会出现一些问题,例如可能会错过中断。一些微控制器设置标志而不考虑全局中断启用的状态,并且在离开临界区后,中断被执行并且只是被延迟。但是,如果您有一个高速发生的中断,如果您阻止中断太长时间,您可能会错过第二次中断发生。

如果您的关键部分只要求不执行一个中断,但应该执行另一个中断,则另一种方法似乎可行。

我发现自己编写的中断服务程序尽可能短。所以他们只是设置一个标志,然后在正常的程序例程中检查它。但是如果你这样做,在等待设置该标志时要小心竞争条件。

有很多选择,当然没有一个正确的答案,这是一个需要仔细设计的话题,值得比其他事情更多的思考。

如果您确定一段代码必须不间断地运行,那么除非在不寻常的情况下,您应该在可能完成任务的最短持续时间内禁用中断,然后重新启用它们。

在相应的 ISR 中放置一个标志(而不是禁用中断),在临界区之前将标志设置为 false,然后将其重置为 true。防止ISR的代码被执行。

这仍然允许发生中断、代码跳转、检查,然后返回。如果您的代码可以处理这么多的中断,那么您可能应该简单地设计 ISR 来设置一个标志而不是执行检查 - 它会更短 - 并在您的正常代码例程中处理标志。这听起来像是有人在中断中放入了太多代码,并且他们正在使用中断来执行应该在常规代码中发生的更长的操作。

如果您正在处理中断很长的代码,那么您建议的标志可能会解决问题,但是如果您无法重新分解代码以消除中断中的过多代码,那么简单地禁用中断仍然会更好.

以标志方式执行此操作的主要问题是您根本不执行中断 - 这可能会在以后产生影响。即使中断被全局禁用,大多数微控制器也会跟踪中断标志,然后在您重新启用中断时执行中断:

  • 如果在临界区期间没有发生中断,则之后不会执行任何中断。
  • 如果在临界区发生一个中断,则在之后执行一个。
  • 如果在临界区发生多个中断,则之后只执行一个。

如果您的系统很复杂并且必须更完整地跟踪中断,则您必须设计一个更复杂的系统来跟踪中断并进行相应的操作。

但是,如果您始终将中断设计为执行实现其功能所需的最少工作,并将其他一切延迟到常规处理中,那么中断很少会对您的其他代码产生负面影响。如果需要,让中断捕获或释放数据,或根据需要设置/重置输出等,然后让主代码路径注意中断影响的标志、缓冲区和变量,以便可以在主循环中完成冗长的处理,而不是中断。

除了极少数情况下,您可能需要不间断的代码部分,这应该会消除所有情况。

如您所描述的那样在 ISR 中放置一个标志可能不起作用,因为您基本上忽略了触发中断的事件。全局禁用中断通常是更好的选择。正如其他人所说,您不必经常这样做。请记住,通过单个指令完成的任何读取或写入都不需要受到保护,因为该指令要么执行,要么不执行。

很大程度上取决于您要共享的资源类型。如果您将数据从 ISR 提供​​给主程序(反之亦然),您可以实现类似 FIFO 缓冲区的东西。唯一的原子操作是更新读取和写入指针,这可以最大限度地减少您在禁用中断时花费的时间。