由于试图通过一些我不知道的简单内容来调试我的代码,我损失了几天的时间,实际上是大约 25 小时的工作。
事实证明,在 AVR ATmega328 8 位微控制器 (Arduino) 上,用 C++ 递减单字节数组的元素不是原子操作,并且需要原子访问防护(即关闭中断)。为什么是这样???另外,确保对 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.
}
}
这是很好的循环版本:
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
}
}
注意“原子访问防护”,即:在递减之前禁用中断,然后在递减之后重新启用它们。
由于我在这里处理单个字节,因此我不知道我需要原子访问防护。为什么我在这个案例中需要它们?这是典型的行为吗?我知道如果这是一个 2 字节值的数组,我会需要它们,但为什么是 1 字节值???通常,对于 1 字节值,此处不需要原子访问防护......
Update:阅读此处的“原子访问”部分:http://www.gammon.com.au/interrupts http://www.gammon.com.au/interrupts。这是一个很好的来源。
相关(STM32 mcus的答案):
所以我们知道读取或写入任何单字节变量在AVR 8位单片机上是原子操作,但是对于STM32 32位单片机呢?STM32上哪些变量具有自动原子读写功能?答案在这里:哪些变量类型/大小在 STM32 微控制器上是原子的? https://stackoverflow.com/a/52785864/4561887.
The ATmega328 数据表 http://www.atmel.com/images/Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf表明:
ALU 支持寄存器之间或常量与寄存器之间的算术和逻辑运算
它没有提到 ALU 能够直接在内存位置上操作。因此,为了减少一个值,这意味着处理器必须执行几个操作:
因此,递减操作不是原子操作,除非您执行一些特殊操作使其成为原子操作,例如禁用中断。这种读/修改/写要求对于更新内存来说可能更常见。
如何使操作原子化的细节取决于平台。新版本的 C 和 C++ 标准明确支持原子操作;我不知道 ATmega 的工具链是否支持这些新标准。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)