gcc是否优化了我的等待代码,尽管将其标记为volatile?

9fkzdhlc  于 2023-10-16  发布在  其他
关注(0)|答案(3)|浏览(133)

在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");
}

GPIOASTK_LOAD这样的宏是因为我在做这个裸机,没有外部库。但我测试过了它们在隔离状态下都能正常工作。原因似乎是编译器改变了顺序(或者省略了?)。我能够使用objdump来确认这一点,至少在某些时候是这样。
我用的是arm-none-eabi-gcc 10.3.1参见:Non-conforming optimizations of volatile in gcc 11.1

更新:

只有一次调用spinspin(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
...
ar7v8xwq

ar7v8xwq1#

不要责怪编译器!!spin函数错误。你需要在while循环之前重置COUNTFLAG(你也可以通过写入瓦尔寄存器来实现)。

#define SCS_BASE            (0xE000E000UL)                            
#define SysTick_BASE        (SCS_BASE +  0x0010UL) 
#define SysTick             ((SysTick_Type   *)     SysTick_BASE  ) 

#define     __IM     volatile const      /*! Defines 'read only' structure member permissions */
#define     __OM     volatile            /*! Defines 'write only' structure member permissions */
#define     __IOM    volatile            /*! Defines 'read / write' structure member permissions */

typedef struct
{
  __IOM uint32_t CTRL;                   /*!< Offset: 0x000 (R/W)  SysTick Control and Status Register */
  __IOM uint32_t LOAD;                   /*!< Offset: 0x004 (R/W)  SysTick Reload Value Register */
  __IOM uint32_t VAL;                    /*!< Offset: 0x008 (R/W)  SysTick Current Value Register */
  __IM  uint32_t CALIB;                  /*!< Offset: 0x00C (R/ )  SysTick Calibration Register */
} SysTick_Type;

void spin(uint32_t ms) 
{
    SysTick -> LOAD = (CPU_HZ / 1000) * ms - 1;
    
    SysTick->VAL   = 0UL;                                             
    SysTick->CTRL  = (1 << 0) | (1 << 2)
    whille(!(SysTick -> CTRL & (1 << 16)));
    SysTick->CTRL = 0;
}

生成的代码:

spin:
        mov     r3, #8000
        mov     r2, #-536813568
        mul     r0, r3, r0
        subs    r0, r0, #1
        movs    r1, #0
        movs    r3, #5
        str     r0, [r2, #20]
        str     r1, [r2, #24]
        str     r3, [r2, #16]
.L2:
        ldr     r3, [r2, #16]
        lsls    r3, r3, #15
        bpl     .L2
        movs    r3, #0
        str     r3, [r2, #16]
        bx      lr

https://godbolt.org/z/5rbndcv1e

g6ll5ycj

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()来做这样的事情:

void Spin(int ms)
{
  while(ms>1000)
  {
     spin(1000);
     ms -= 1000;
  } 
  spin(ms);
}
dwbf0jvd

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(""),导致意外的行为。
这是一个很好的例子,说明调试是多么的棘手,尤其是。在嵌入式平台上。感谢大家的帮助。

相关问题