8
static void RadioReleaseSPI(void) {
    __disable_interrupt();
    spiTxRxByteCount &= ~0x0100;
    __enable_interrupt();
}

我了解多个任务可能会尝试使用 SPI 资源。spiTxRxByteCount是一个全局变量,用于跟踪 SPI 当前是否被另一个任务使用。当任务需要 SPI 时,它可以检查 SPI 的状态spiTxRxByteCount以查看是否正在使用 SPI。当使用 SPI 完成任务时,它会调用此函数并清除该位,以指示 SPI 现在空闲。但是为什么要先禁用中断,然后再重新启用它们呢?只是偏执狂?

4

2 回答 2

16

&= 将执行读取-修改-写入操作 - 它不是原子的。您不希望在中间发生中断改变事情,从而导致写入覆盖的值不正确。

于 2012-11-28T21:04:18.710 回答
10

您需要禁用中断以确保原子访问。您不希望任何其他进程在您阅读它时访问并可能修改该变量。

介绍到嵌入式计算

原子访问的需求

想象一下这种情况:在 8 位 uC 上运行的前台程序需要检查一个 16 位变量,将其称为 X。因此它先加载高字节,然后再加载低字节(或者相反,顺序不没关系),然后检查 16 位值。现在想象一个带有修改该 16 位变量的关联 ISR 的中断。进一步想象,在程序执行的给定时间,变量的值恰好是 0x1234。这是可能发生的非常糟糕的事情:

  • 前台加载高字节(0x12)
  • ISR 发生,修改 X 为 0xABCD
  • 前台加载低字节(0xCD)
  • 前台程序看到 16 位值 0x12CD。

问题是,我们的变量 X 在访问它的过程中实际上被修改了,因为访问该变量的 CPU 指令是可分割的。因此,我们的变量 X 负载已损坏。您可以看到变量读取的顺序无关紧要。如果在我们的示例中顺序颠倒,变量将被错误地读取为 0xAB34 而不是 0x12CD。无论哪种方式,读取的值都不是旧的有效值 (0x1234) 也不是新的有效值 (0xABCD)。

编写 ISR 引用的数据也好不到哪里去。这次假设前台程序为了 ISR 的利益已经写入了之前的值 0x1234,然后需要写入一个新的值 0xABCD。在这种情况下,VBT 如下:

  • 前台存储新的高字节(0xAB)
  • ISR 发生,将 X 读取为 0xAB34
  • 前台存储新的低字节(0xCD)

再一次,代码(这次是 ISR)既没有看到之前的有效值 0x1234,也没有看到新的有效值 0xABCD,而是看到了无效的 0xAB34 值。

虽然spiTxRxByteCount &= ~0x0100;可能看起来像 C 中的一条指令,但它实际上是 CPU 的几条指令。在 GCC 中编译,程序集清单如下所示:

  57:atomic.c      ****     spiTxRxByteCount &= ~0x0100;
  68                    .loc 1 57 0
  69 004d A1000000      movl    _spiTxRxByteCount, %eax
  69      00
  70 0052 80E4FE        andb    $254, %ah
  71 0055 A3000000      movl    %eax, _spiTxRxByteCount
  71      00

如果在任何这些指令之间出现中断并修改数据,您的第一个 ISR 可能会读取错误的值。因此,您需要在对其进行操作之前禁用中断并声明变量volatile

于 2012-11-28T21:23:43.990 回答