在 ST32 ARM Cortex M4 上,我有一个简单的裸机闪烁灯,可以使 LED 闪烁。当我使用旋转等待时效果很好:
typedef volatile uint32_t vuint32_t;
#define SET(addr, bits) (*((vuint32_t*) (addr)) |= (bits))
#define CLR(addr, bits) (*((vuint32_t*) (addr)) &= ~(bits))
for (;;) {
SET(GPIOA+_ODR, BIT(5));
// spin(100);
for (volatile int i = 100 * 1000; i; --i) asm("nop");
CLR(GPIOA+_ODR, BIT(5));
//spin(1000);
for (volatile int i = 1000 * 1000; i; --i) asm("nop");
}
上面的代码工作得很好——它周期性地打开 LED。而且,如果我取消注释 spin()
调用中的
one,并注释掉相应的
for
调用,它也能正常工作。但如果我两者都使用spin()
,则灯会反转:主要是开,闪烁关。
使用
gdb
,看起来它正在切换调用的顺序(或者以与预期不同的方式执行它们)。不过,我不明白为什么它会这样做,因为我将所有内容都标记为易失性:
void spin(uint32_t ms) {
uint32_ticks = (CPU_HZ / 1000) * ms;
(*(vuint32_t*) (STK+_LOAD)) = ticks - 1;
SET((STK+_CTRL), (BIT(2) | BIT(0)));
while (! ((*(vuint32_t*) STK+_CTRL) & COUNTFLAG)) asm volatile("nop");
}
像
GPIOA
、STK
和_LOAD
这样的宏是因为我正在做这个裸机,没有外部库。但我已经对它们进行了测试,它们在隔离状态下都能正常工作。原因似乎是编译器正在更改顺序(或省略?)。我至少在某些时候能够使用 objdump
确认这一点。
我正在使用
arm-none-eabi-gcc 10.3.1
。另请参阅:gcc 11.1 中 volatile 的不合格优化。
仅对
spin
进行一次调用,spin(100)
按预期运行,spin(1000)
按预期运行,但 spin(5000)
似乎只旋转 1 秒(据我所知)。
我正在使用
-Os
。切换到 -O0
并不能解决问题。
不要责怪编译器!
spin
功能错误。您需要在 while 循环之前重置 COUNTFLAG。
void spin(uint32_t ms)
{
uint32_ticks = (CPU_HZ / 1000) * ms;
(*(vuint32_t*) (STK+_LOAD)) = ticks + 1;
SET((STK+_CTRL), (BIT(2) | BIT(0)));
CLR((STK+_CTRL), BIT(16));
while (! ((*(vuint32_t*) STK+_CTRL) & COUNTFLAG));
}