Search code examples
embeddedmicrocontrollerstm32keil

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


I am getting into learning embedded systems and I tried to implement blinky but the software delay gets skipped for some reason. I was expecting it to blink when I am pushing the button but instead the LEDs kept on.

Code I have used is shown below,

#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;
}

I'm using board support LED and button APIs.

How do I fix this?

My debugger starts as follows:

Debugger view


Solution

  • The problem here is this is dead code, it does nothing interacts with nothing so can/should be optimized out. And an optimizer will often do this.

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

    optimized output:

    00000000 <delay>:
       0:   4770        bx  lr
    

    One way is to not optimize

    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
    

    But that's a bit brutal for an embedded platform so another is to beg the compiler to do something with the variable, keep it in memory and up to date:

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

    It's still a bit ugly but that will burn some time:

    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
    

    The win-win way is to have another function outside the compile domain and let the optimizer work.

    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
    

    A little cleaner, burns some time but isn't excessive, yes note this is all-thumb-variants code. The called function can simply be a bx lr since you don't care what it does with the call.

    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
    

    Building for the mcu cleans up the pop as after armv4t or 5t you could pop the pc to return to either mode, even though this is thumb mode only you still deal with that with these tools.

    Now as shown by others, since you don't care about order just want to count you can, depending on architecture (often this is supported) count down. We are asking the compiler to not make this dead code so it has to do it in the order we asked, to be a functional representation of the C code.

    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
    

    And now the compare went away (i-- vs --i made a difference i-- makes for more code)

    With volatile:

    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
    

    And that doesn't take advantage of the instruction set, oh well. (Being higher or lower one count doesn't matter as this really can't/won't be a tuned loop, to tune it on a platform like this you really need to use asm and even there it is difficult to tune).

    Even cleaner just do it in assembly

    .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
    

    or make it generic

    .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
    

    and then call it from C with

    delay(5000000);
    

    Lots of options, but what others didn't show is the code being optimized away and what the choices do to the code. It is quite easy to see in the compiler output using the tools what is going on and why this happened.

    And there are various ways to make it or request it to not be dead code. Most people just toss in a volatile and move on. Nothing wrong with that, usually.