STM32F4-Disc1:keil MDK 版本 5 中用户定义的软件延迟不起作用

STM32F4-Disc1: user defined software delay in keil MDK version 5 not working

我正在学习嵌入式系统,我尝试实现 blinky,但由于某种原因跳过了软件延迟。当我按下按钮时,我原以为它会闪烁,但 LED 却一直亮着。

我用过的代码如下,

#include Board_LED.h
#include Board_Buttons.h
#include <stdint.h>

void delay(void);

void delay(void) {
  int i;
  for (i = 0; i < 5000000; i++)
    ;
}

int main(void) {
  LED_Initialize();
  Buttons_Initialize();

  while (1) {
    if (Buttons_GetState() == 1) {
      LED_On(0);
      LED_On(1);
      LED_On(2);
      LED_On(3);
      delay();
      LED_Off(0);
      LED_Off(1);
      LED_Off(2);
      LED_Off(3);
      delay();
    }
  }
  return 0;
}

我正在使用板支持 LED 和按钮 API。

我该如何解决这个问题?

我的调试器启动如下:

在编译器设置中指定 -O0 作为优化标志,以避免无用循环(从编译器的角度来看)被优化掉。 或者检查 MDK 或 BSP 是否提供已知有效的 delay() 函数。

  1. 你是怎么发现循环被跳过的(可能是你的按钮功能没用)

  2. 测试:

void delay(volatile uint32_t del)
{
while(del--);
}


int main(void)
{
    LED_Initialize();
    Buttons_Initialize();

    while(1){
        if( 1 || Buttons_GetState() == 1){ //it skips the if checks
            LED_On(0);
            LED_On(1);
            LED_On(2);
            LED_On(3);
            delay(500000);
            LED_Off(0);
            LED_Off(1);
            LED_Off(2);
            LED_Off(3);
            delay(500000);
            }       
        }   
}
void delay(void)
    {
        volatile int i;
        for(i=0; i<5000000 ;i++);
    }

如果 Buttons_GetState() 工作正常,这应该可以工作。将变量 'i' 声明为 volatile,这样编译器就不会进行优化。

这里的问题是这是死代码,它什么都不做,什么都不交互,所以 can/should 被优化掉了。优化器通常会这样做。

void delay(void)
{
    int i;
    for(i=0; i<5000000 ;i++);
}

优化输出:

00000000 <delay>:
   0:   4770        bx  lr

一种方法是不优化

00000000 <delay>:
   0:   b580        push    {r7, lr}
   2:   b082        sub sp, #8
   4:   af00        add r7, sp, #0
   6:   2300        movs    r3, #0
   8:   607b        str r3, [r7, #4]
   a:   e002        b.n 12 <delay+0x12>
   c:   687b        ldr r3, [r7, #4]
   e:   3301        adds    r3, #1
  10:   607b        str r3, [r7, #4]
  12:   687b        ldr r3, [r7, #4]
  14:   4a04        ldr r2, [pc, #16]   ; (28 <delay+0x28>)
  16:   4293        cmp r3, r2
  18:   ddf8        ble.n   c <delay+0xc>
  1a:   46c0        nop         ; (mov r8, r8)
  1c:   46c0        nop         ; (mov r8, r8)
  1e:   46bd        mov sp, r7
  20:   b002        add sp, #8
  22:   bc80        pop {r7}
  24:   bc01        pop {r0}
  26:   4700        bx  r0

但这对于嵌入式平台来说有点残酷,所以另一个是请求编译器对变量做一些事情,将其保存在内存中并保持最新:

void delay(void)
{
    volatile int i;
    for(i=0; i<5000000 ;i++);
}

它还是有点难看,但会燃烧一些时间:

00000000 <delay>:
   0:   2300        movs    r3, #0
   2:   b082        sub sp, #8
   4:   9301        str r3, [sp, #4]
   6:   9b01        ldr r3, [sp, #4]
   8:   4a05        ldr r2, [pc, #20]   ; (20 <delay+0x20>)
   a:   4293        cmp r3, r2
   c:   dc05        bgt.n   1a <delay+0x1a>
   e:   9b01        ldr r3, [sp, #4]
  10:   3301        adds    r3, #1
  12:   9301        str r3, [sp, #4]
  14:   9b01        ldr r3, [sp, #4]
  16:   4293        cmp r3, r2
  18:   ddf9        ble.n   e <delay+0xe>
  1a:   b002        add sp, #8
  1c:   4770        bx  lr
  1e:   46c0        nop         ; (mov r8, r8)
  20:   004c4b3f    .word   0x004c4b3f

双赢的方法是在编译域之外再有一个函数,让优化器工作。

void dummy ( int );
void delay(void)
{
    int i;
    for(i=0; i<5000000 ;i++) dummy(i);
}

00000000 <delay>:
   0:   b570        push    {r4, r5, r6, lr}
   2:   2400        movs    r4, #0
   4:   4d04        ldr r5, [pc, #16]   ; (18 <delay+0x18>)
   6:   0020        movs    r0, r4
   8:   3401        adds    r4, #1
   a:   f7ff fffe   bl  0 <dummy>
   e:   42ac        cmp r4, r5
  10:   d1f9        bne.n   6 <delay+0x6>
  12:   bc70        pop {r4, r5, r6}
  14:   bc01        pop {r0}
  16:   4700        bx  r0
  18:   004c4b40    .word   0x004c4b40

更干净一点,会耗费一些时间但不过分,是的,请注意这是所有缩略图变体代码。被调用的函数可以简单地是一个 bx lr 因为你不关心它对调用做了什么。

00000000 <delay>:
   0:   b538        push    {r3, r4, r5, lr}
   2:   2400        movs    r4, #0
   4:   4d03        ldr r5, [pc, #12]   ; (14 <delay+0x14>)
   6:   4620        mov r0, r4
   8:   3401        adds    r4, #1
   a:   f7ff fffe   bl  0 <dummy>
   e:   42ac        cmp r4, r5
  10:   d1f9        bne.n   6 <delay+0x6>
  12:   bd38        pop {r3, r4, r5, pc}
  14:   004c4b40    .word   0x004c4b40

在 armv4t 或 5t 之后,为 mcu 构建会清除 pop,您可以将 pc 弹出到 return 任何一种模式,即使这只是拇指模式,您仍然可以使用这些工具处理它。

现在正如其他人所展示的那样,由于您不关心顺序只想计数,您可以根据体系结构(通常支持)倒数。我们要求编译器不要生成此死代码,因此它必须按照我们要求的顺序执行,成为 C 代码的函数表示。

void dummy ( int );
void delay(void)
{
    int i=5000000;
    while(--i) dummy(i);
}

00000000 <delay>:
   0:   b510        push    {r4, lr}
   2:   4c03        ldr r4, [pc, #12]   ; (10 <delay+0x10>)
   4:   4620        mov r0, r4
   6:   f7ff fffe   bl  0 <dummy>
   a:   3c01        subs    r4, #1
   c:   d1fa        bne.n   4 <delay+0x4>
   e:   bd10        pop {r4, pc}
  10:   004c4b3f    .word   0x004c4b3f

现在比较消失了(i-- vs --i 有所不同 i-- 需要更多代码)

具有易失性:

void delay(void)
{
    volatile int i=5000000;
    while(--i) continue;
}

00000000 <delay>:
   0:   b082        sub sp, #8
   2:   4b04        ldr r3, [pc, #16]   ; (14 <delay+0x14>)
   4:   9301        str r3, [sp, #4]
   6:   9b01        ldr r3, [sp, #4]
   8:   3b01        subs    r3, #1
   a:   9301        str r3, [sp, #4]
   c:   2b00        cmp r3, #0
   e:   d1fa        bne.n   6 <delay+0x6>
  10:   b002        add sp, #8
  12:   4770        bx  lr
  14:   004c4b40    .word   0x004c4b40


void delay(void)
{
    volatile int i=5000000;
    while(i--) continue;
}

00000000 <delay>:
   0:   b082        sub sp, #8
   2:   4b04        ldr r3, [pc, #16]   ; (14 <delay+0x14>)
   4:   9301        str r3, [sp, #4]
   6:   9b01        ldr r3, [sp, #4]
   8:   1e5a        subs    r2, r3, #1
   a:   9201        str r2, [sp, #4]
   c:   2b00        cmp r3, #0
   e:   d1fa        bne.n   6 <delay+0x6>
  10:   b002        add sp, #8
  12:   4770        bx  lr
  14:   004c4b40    .word   0x004c4b40

这并没有利用指令集,哦,好吧。 (更高或更低的计数并不重要,因为这真的不能't/won 调整循环,要在这样的平台上调整它,你真的需要使用 asm,即使在那里也很难调整).

更清洁的只是在汇编中做

.globl delay
delay:
   ldr r0,=5000000
dinner:
   sub r0,#1
   bne dinner
   bx lr

00000000 <delay>:
   0:   4801        ldr r0, [pc, #4]    ; (8 <dinner+0x6>)

00000002 <dinner>:
   2:   3801        subs    r0, #1
   4:   d1fd        bne.n   2 <dinner>
   6:   4770        bx  lr
   8:   004c4b40    .word   0x004c4b40

或使其通用

.globl delay
delay:
   sub r0,#1
   bne delay
   bx lr

00000000 <delay>:
   0:   3801        subs    r0, #1
   2:   d1fe        bne.n   0 <delay>
   4:   4770        bx  lr

然后用

从C调用它
delay(5000000);

很多选项,但其他人没有显示的是被优化掉的代码以及这些选项对代码的影响。使用这些工具可以很容易地在编译器输出中看到发生了什么以及为什么会发生这种情况。

并且有多种方法可以使它或要求它不是死代码。大多数人只是在波动中折腾并继续前进。通常没有错。