在STM32 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 Flink 。而且,如果我取消注解spin()
调用中的一个,并注解掉相应的for
调用,它也能很好地工作。但是如果我对两者都使用spin()
,光线就会反转:它主要是 * 开 *, Flink * 关 *。
使用gdb
,看起来它在调用顺序之间切换(或者以不同于预期的方式执行它们)。我不明白为什么它会这样做,因为我把所有东西都标记为volatile:
void spin(uint32_t ms) {
uint32_t 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
参见:Non-conforming optimizations of volatile in gcc 11.1。
更新:
只有一次调用spin
,spin(100)
按预期运行,spin(1000)
按预期运行,但**spin(5000)
似乎只旋转了1秒**(就我所能计算的时间而言)。
我使用-Os
。切换到-O0
并不能解决问题。
按照要求,这是spin
的反汇编。请注意,根据建议,我已经尝试了各种变化,没有一个表现得像预期的那样-下面是当前的一个。
void spin(uint32_t ms) {
80001ac: b480 push {r7}
80001ae: b085 sub sp, #20
80001b0: af00 add r7, sp, #0
80001b2: 6078 str r0, [r7, #4]
uint32_t ticks = (CPU_HZ / 1000) * ms;
80001b4: 687b ldr r3, [r7, #4]
80001b6: f44f 527a mov.w r2, #16000 ; 0x3e80
80001ba: fb02 f303 mul.w r3, r2, r3
80001be: 60fb str r3, [r7, #12]
(*(vuint32_t*) (STK+_LOAD)) = ticks - 1; // The -1 is necessary as per.
80001c0: 4a11 ldr r2, [pc, #68] ; (8000208 <spin+0x5c>)
80001c2: 68fb ldr r3, [r7, #12]
80001c4: 3b01 subs r3, #1
80001c6: 6013 str r3, [r2, #0]
(*(vuint32_t*)(STK+_VAL)) = 0;
80001c8: 4b10 ldr r3, [pc, #64] ; (800020c <spin+0x60>)
80001ca: 2200 movs r2, #0
80001cc: 601a str r2, [r3, #0]
SET(STK + _CTRL, BIT(2) | BIT(0)); // Enable SysTick @CPU_HZ
80001ce: 4b10 ldr r3, [pc, #64] ; (8000210 <spin+0x64>)
80001d0: 681b ldr r3, [r3, #0]
80001d2: 4a0f ldr r2, [pc, #60] ; (8000210 <spin+0x64>)
80001d4: f043 0305 orr.w r3, r3, #5
80001d8: 6013 str r3, [r2, #0]
CLR((STK+_CTRL), (BIT(16)));
80001da: 4b0d ldr r3, [pc, #52] ; (8000210 <spin+0x64>)
80001dc: 681b ldr r3, [r3, #0]
80001de: 4a0c ldr r2, [pc, #48] ; (8000210 <spin+0x64>)
80001e0: f423 3380 bic.w r3, r3, #65536 ; 0x10000
80001e4: 6013 str r3, [r2, #0]
while (! ((*(vuint32_t*) STK+_CTRL) & COUNTFLAG)) asm("");
80001e6: e7ff b.n 80001e8 <spin+0x3c>
80001e8: 4b09 ldr r3, [pc, #36] ; (8000210 <spin+0x64>)
80001ea: 681b ldr r3, [r3, #0]
80001ec: f403 3380 and.w r3, r3, #65536 ; 0x10000
80001f0: 2b00 cmp r3, #0
80001f2: d0f9 beq.n 80001e8 <spin+0x3c>
(*(vuint32_t*) (STK+_CTRL)) = 0;
80001f4: 4b06 ldr r3, [pc, #24] ; (8000210 <spin+0x64>)
80001f6: 2200 movs r2, #0
80001f8: 601a str r2, [r3, #0]
}
80001fa: bf00 nop
80001fc: 3714 adds r7, #20
80001fe: 46bd mov sp, r7
8000200: f85d 7b04 ldr.w r7, [sp], #4
8000204: 4770 bx lr
8000206: bf00 nop
8000208: e000e014 .word 0xe000e014
800020c: e000e018 .word 0xe000e018
8000210: e000e010 .word 0xe000e010
以及main
的相关部分:
08000214 <main>:
int main(void) {
...
SET((GPIOA + _ODR), BIT(5));
8000262: 4b0e ldr r3, [pc, #56] ; (800029c <main+0x88>)
8000264: 681b ldr r3, [r3, #0]
8000266: 4a0d ldr r2, [pc, #52] ; (800029c <main+0x88>)
8000268: f043 0320 orr.w r3, r3, #32
800026c: 6013 str r3, [r2, #0]
spin(100);
800026e: 2064 movs r0, #100 ; 0x64
8000270: f7ff ff9c bl 80001ac <spin>
//for (volatile int i = 1000 * 1000; i; --i) asm("nop");
CLR((GPIOA + _ODR), BIT(5));
8000274: 4b09 ldr r3, [pc, #36] ; (800029c <main+0x88>)
8000276: 681b ldr r3, [r3, #0]
8000278: 4a08 ldr r2, [pc, #32] ; (800029c <main+0x88>)
800027a: f023 0320 bic.w r3, r3, #32
800027e: 6013 str r3, [r2, #0]
spin(10000);
8000280: f242 7010 movw r0, #10000 ; 0x2710
8000284: f7ff ff92 bl 80001ac <spin>
SET((GPIOA + _ODR), BIT(5));
8000288: e7eb b.n 8000262 <main+0x4e>
800028a: bf00 nop
...
800029c: 40020014 .word 0x40020014
...
3条答案
按热度按时间ar7v8xwq1#
不要责怪编译器!!
spin
函数错误。你需要在while循环之前重置COUNTFLAG(你也可以通过写入瓦尔寄存器来实现)。生成的代码:
https://godbolt.org/z/5rbndcv1e
g6ll5ycj2#
根据B.Pantalone所说的
SYSTICK
寄存器为24位(在此确认为https://developer.arm.com/documentation/dui0552/a/cortex-m3-peripherals/system-timer--systick/systick-current-value-register),CPU_MHZ
为16 MHz,spin()
函数将在任何大于1047的输入值上静默溢出。((1<<24)/16000 = 1048
)这就是你问题的根源
你可以用几种方法来解决它(我建议重写
spin()
来做这样的事情:dwbf0jvd3#
这里有3个不同的问题,这使得它很难调试:
1.当“0_”写入时,我需要清除
STK_VAL
,每次都将其设置为0
。没有这个,spin
第一次工作,但不是(一定?)之后。1.正如B. Pantalone和Russ Scholtz所写的那样,SysTick被限制在24位(在这种情况下约为1000 ms),导致溢出。这并没有影响我的原始代码,只是在我尝试调试
ms
瓦尔几秒钟时才出现(这当然会使调试变得非常混乱)。1.最后,在某个时刻,gcc优化了我的一些代码。原因是gcc可能会消除一个
asm("nop")
(即使在-O0
?然而,它不会消除asm("")
,也不会消除asm volatile("nop")
。我没有意识到这种区别,所以有时写asm("nop")
,有时只写asm("")
,导致意外的行为。这是一个很好的例子,说明调试是多么的棘手,尤其是。在嵌入式平台上。感谢大家的帮助。