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


9

最初的问题

我对微控制器中的中断处理有一个一般性的问题。我正在使用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,我想对来自一组按钮(带有反推系统等)的中断敏感。当有人按下这些按钮之一时,将产生一个中断,并将与输入端口相关的标志复制到变量中buttonPressed。如果通过某种方式(看门狗定时器,定时器,计数器等)将防抖动设置为200 ms,我们确信buttonPressed在200 ms之前不能用新值进行更新。当然,这就是我要问你(和我自己:)

我是否需要在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该I 的先前值需要(尽管由于必须经过250毫秒,所以不可能发生这种情况)。


3
在不了解您的情况的情况下很难提出建议。但是有时必须禁用嵌入式系统中的中断。最好仅在短时间内禁用中断,以免丢失中断。可能仅禁用特定的中断,而不禁用所有的中断。我不记得曾经使用过像您描述的那样使用flag-inside-ISR技术,因此我怀疑这是否是最佳解决方案。
kkrambo 2015年

Answers:


17

第一种策略是设计整体固件,以便可以随时发生中断。必须谨慎执行中断操作,以便前台代码可以执行原子序列。周围通常有一种架构方法。

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

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

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

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

每种方法都有其自己的优点和缺点。没有一个正确的答案。同样,了解机器的工作原理,则不需要经验法则。


7

如果需要关键部分,则必须确保保护关键部分的操作是原子操作,并且不能被中断。

因此,禁用中断通常是最安全的选择之一,而中断通常由单个处理器指令处理(并使用编译器固有函数调用)。

根据您的系统,可能会有一些问题,例如可能会错过中断。一些微控制器会设置标志,而与全局中断启用状态无关,并且在离开关键部分后,中断将被执行并被延迟。但是,如果中断发生率很高,那么如果阻塞中断时间太长,您可能会错过第二次中断发生。

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

我发现自己编写的中断服务程序越短越好。因此,他们只是设置一个标志,然后在常规程序例程中对其进行检查。但是,如果这样做,请在等待设置该标志时提防竞争条件。

有很多选择,而且肯定没有一个正确的答案,这是一个需要仔细设计的主题,比其他事情应多加考虑。


5

如果您确定一段代码必须不间断地运行,那么除非有特殊情况,否则应在尽可能短的时间内禁用中断以完成任务,然后再重新启用它们。

在相应的ISR中放置一个标志,并(而不是禁用该中断),在关键部分之前将标志设置为false,然后在紧要部分之前将其重置为true。防止执行ISR的代码。

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

如果您正在处理中断时间较长的代码,则建议使用一个标志来解决问题,但是如果您无法重构代码以消除中断中过多的代码,则最好只是禁用中断。 。

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

  • 如果在关键部分没有发生中断,则此后将不执行任何中断。
  • 如果在关键部分发生一个中断,则在此之后执行一次。
  • 如果在关键部分发生多个中断,则此后仅执行一次。

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

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

这将消除几乎所有可能需要不间断代码段的情况,但很少。


我已经更新了帖子,以更好地说明我的工作状况:)
Umberto D.

1
在您的示例代码中,我将考虑在不需要时禁用特定的按钮中断,并在需要时启用它。经常执行此操作不是设计问题。保留全局中断,以便以后可以根据需要向代码中添加其他中断。或者,只需在进入状态A时重置标志,否则将其忽略。如果按下按钮并设置了标志,谁在乎?忽略它,直到你回到状态A.
亚当戴维斯

是! 那可能是一个解决方案,因为在实际设计中,我经常在启用了全局中断的情况下进入LPM3(MSP430),并在检测到中断后立即退出LPM3,恢复执行。因此,您提出的解决方案是我认为已在代码的第二部分中报告的解决方案:一旦我开始执行需要它的状态的do活动并在转到转换块之前将其禁用,就启用中断。另一种可能的解决方法是在离开“活动块”之前禁用中断,然后在某个时间(何时)重新启用吗?
Umberto D.

1

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

在很大程度上取决于您要共享哪种资源。如果要将数据从ISR馈送到主程序(反之亦然),则可以实现类似FIFO缓冲区的功能。唯一的原子操作将是更新读取和写入指针,从而最大程度地减少了在禁用中断的情况下花费的时间。


0

您需要考虑一个细微的差异。您可以选择“延迟”中断的处理或“忽略”并中断。

我们经常在代码中说禁用中断。由于硬件功能,可能发生的情况是,一旦启用中断,它就会触发。这在某种程度上延迟了中断。为了使系统可靠地工作,我们需要知道可能会延迟处理这些中断的最长时间。然后确保所有禁用中断的情况都将在更短的时间内完成。

有时我们想忽略中断。最好的方法可能是在硬件级别阻止中断。通常会有一个中断控制器或类似的控制器,我们可以说哪些输入应该产生中断。

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.