C++递减单字节(易失性)数组的元素不是原子的!为什么?(另外:我如何在Atmel AVR mcus/Arduino中强制原子性)

Gab*_*les 2 c++ avr atomic arduino interrupt

我只是失去了几天,从字面上看,大约25个小时的工作,因为我试图调试我的代码而不是我不知道的简单.

事实证明,在C++中减少单字节数组的元素,在AVR上,ATmega328 8位微控制器(Arduino)不是原子操作,需要原子访问保护(即关闭中断).为什么是这样???此外,在Atmel AVR微控制器上确保原子访问变量的所有C技术是什么?

这是我所做的一个愚蠢的版本:

//global vars:
const uint8_t NUM_INPUT_PORTS = 3;
volatile uint8_t numElementsInBuf[NUM_INPUT_PORTS];

ISR(PCINT0_vect) //external pin change interrupt service routine on input port 0
{
  //do stuff here
  for (uint8_t i=0; i<NUM_INPUT_PORTS; i++)
    numElementsInBuf[i]++;
}

loop()
{
  for (uint8_t i=0; i<NUM_INPUT_PORTS; i++)
  {
    //do stuff here
    numElementsInBuf[i]--; //<--THIS CAUSES ERRORS!!!!! THE COUNTER GETS CORRUPTED.
  }
}
Run Code Online (Sandbox Code Playgroud)

这是循环的版本,没关系:

loop()
{
  for (uint8_t i=0; i<NUM_INPUT_PORTS; i++)
  {
    //do stuff here
    noInterrupts(); //globally disable interrupts 
    numElementsInBuf[i]--; //now it's ok...30 hrs of debugging....
    interrupts(); //globally re-enable interrupts 
  }
}
Run Code Online (Sandbox Code Playgroud)

注意"原子访问保护",即:在递减之前禁用中断,然后在之后重新启用它们.

由于我在这里处理单个字节,我不知道我需要原子访问保护.为什么在这种情况下我需要它们?这是典型的行为吗?我知道如果这是一个2字节值的数组,我需要它们,但为什么1字节值???? 通常对于1字节值,此处不需要原子访问保护......


更新:请阅读"原子访问"部分:http://www.gammon.com.au/interrupts.这是一个很好的来源.


相关(答案为STM32 mcus):

所以我们知道在AVR 8位mcus上读取或写入任何单字节变量都是原子操作,但STM32 32位mcus呢?哪些变量在STM32上具有自动原子读写?答案是:STM32微控制器上哪些变量类型/大小是原子的?.

Mic*_*urr 6

ATmega328数据表表示:

ALU支持寄存器之间或常量和寄存器之间的算术和逻辑运算

它没有提到ALU能够直接在内存位置上运行.因此,为了减少值,这意味着处理器必须执行多个操作:

  • 将值加载到寄存器中
  • 递减寄存器
  • 将值存回

因此,减量操作不是原子操作,除非你做一些特殊操作使其成为原子,例如禁用中断.这种读/修改/写入要求可能比更新内存更常见.

如何使操作成为原子的细节取决于平台.较新版本的C和C++标准明确支持原子操作; 我不知道ATmega的工具链是否支持这些新标准.


Ish*_*ael 5

我对Arduino和中断知之甚少,所以我可能不会在这里回答您的特定问题,但在多线程环境中使用递减和递增--并且++从不是原子的.而且,在C++中一般volatile也不代表atomic(证明).虽然我知道volatile在编程微控制器时这是有意义的,但我怀疑我的答案可能不适用于您的情况.

如果volatile uint8_t用三个独立的volatile uint8_ts 替换s 数组,它是否有效?

  • @GabrielStaples它不是原子的原因是因为为增量生成的汇编代码最终是多个指令.在没有锁定机制的情况下执行多个指令本质上不是线程安全的. (2认同)