2

I have just started exploring STM32 MCUs. I want to blink the LED on the BluePill(having STM32F103C8T6 MCU) board. I suspect I have been mislead by something. As per the Reference Manual of F1 series, There are 3 main steps:

  • Enable Clock for the PORT (here PORTC)
  • Configure The CNF/MODE registers
  • Configure ODR register as required i.e. HIGH/LOW on the pin.

I have written the code in KEIL MDK as per the manual but after it is loaded, The code do not run, I press the reset button and then LED turns ON, even though I have changed Settings to RESET & RUN in KEIL.

Here is the code and The parts of reference manual.

#include<stm32f10x.h>

int main(){
    
    RCC->APB2ENR |= 1<<4; //PORTC is on APB2 bus
    GPIOC->CRH |= (1<<20);
    
    while(1){
        GPIOC->ODR |= 0x00002000;
        for(int i = 0; i < 500000;i++); //dummy delay
        GPIOC->ODR &= ~0x00002000;
        for(int i = 0; i < 500000;i++); // dummy delay

    }

}

Reference manual: ODR register APB2 bus enable CRH register

When I am using the Debug mode, I noticed one thing that the clock is not enabled for PORTC after execution of RCC->APB2ENR |= (1<<4). GPIOC debug

The LED does not blink. I am unable to find the error in this whole process.

Sajil
  • 87
  • 8
  • 3
    Did you check the generated code? The delay might be getting optimized out. Maybe you can try declaring the loop counter(s) as volatile. – th33lf Nov 23 '20 at 14:28
  • @th33lf I am sorry I did not understand what you mean. Actually the LED toggles inside the while loop ,but only in debug mode. Also it shows the clock is not enabled for PORTC. – Sajil Nov 23 '20 at 14:32
  • 2
    Do you mean that if you step through, the LED toggles, but if you let the code run continuously, the LED just stays lit? If so, the delay might be getting removed by the compiler. – th33lf Nov 23 '20 at 14:35
  • Yes , not lit but off. If the code is correctly compiled and uploaded, the LED should blink as soon as i power the board but that's not the case. – Sajil Nov 23 '20 at 14:38
  • I still don't understand exactly what you are facing. Does it blink in debug mode and only not work when you simply flash and reset? Or does it work only when you step through line by line? If so, I would try first with the loop counters declared as volatile. Also, the port configuration seems to be a two-step process. You should configure the direction (input/output) and then what kind of output it should be. For LEDs, it is usually open drain output, but depends on your board. I am not sure if just doing (1 << 20) sets both of these correctly. – th33lf Nov 23 '20 at 14:44
  • the loop is dead code either call an external dummy function or make i volatile, examine the disassembly you will see what happened. (or compile with no optimizations). – old_timer Nov 23 '20 at 15:19

2 Answers2

4

When you write a dummy delay loop, a smart compiler will usually figure out that there is nothing worthwhile happening in this piece of code and optimize the whole thing away.

If you want to know this has happened, the best way is to take a look at the disassembly of the generated binary.

Thankfully, C provides the volatile keyword to get around exactly this sort of problem. It tells the compiler explicitly to not optimize memory accesses to variables declared with this qualifier.

Here you can see some sample code that shows the difference between generated assembly code with and without volatile keyword using the excellent godbolt tool. Without volatile, the for loop gets optimized to nothing and no incrementing or checking of i ever happens.

Hence the loops should have been written as:

for(volatile int i = 0; i < 500000; i++); //dummy delay

You may run into this kind of issue on embedded systems also in other instances such as when you have a variable being accessed from multiple contexts/threads.

th33lf
  • 2,177
  • 11
  • 15
  • 1
    Oh this was a very good explanation with the linked reference! Previously I did not understand what you tried to explain. Thanks a lot! Actually I am quite new to ARM Hardware level and thus cant understand disassembly much(though am trying). – Sajil Nov 24 '20 at 06:28
  • @Sajil Nice to hear that! I was kind of hoping you would take a look at it to gain a deeper understanding although your problem was already solved. – th33lf Nov 30 '20 at 05:38
-1

Actually the LED toggles inside the while loop ,but only in debug mode

Yes, because that's the only time the generated machine code includes those delay loops. In release mode, the LED still toggless, except you need an oscilloscope or a logic analyzer to look at the output pin's state to see that it's toggling - you won't see it with just your eyes :)

In release mode, the delay loops are removed, because you can't implement delays that way. Ideally you should use a timer, but as a quick hack this would work:

const int N = 500000;
while(1){
    for(int i = 0; i < N;i++) GPIOC->ODR |= 0x00002000;
    for(int i = 0; i < N;i++) GPIOC->ODR &= ~0x00002000;
}

It'll work since GPIOC is points to a volatile object, and the compiler can't optimize the accesses out.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Okay, Now it works! However, ```constexpr``` is in C++ which was not supported here. I did ```int N``` and it works. Also is it like you have to configure the pin every iteration of the loop to mimic the delay? – Sajil Nov 23 '20 at 15:05
  • 2
    @Sajil Register access is `volatile` qualified, that's why this version works. You could alternatively do `for(volatile int i = 0; i < N;i++){}` too. But don't write crap loops like that, use hardware timers. – Lundin Nov 23 '20 at 15:12
  • 2
    @Lundin baby steps are best get one thing working (gpio output) then figure out timers. Not two things at once. busy counter loop is best for the first step then advance to timers after that. – old_timer Nov 23 '20 at 15:18
  • 1
    No, you do not have to configure the pin every iteration. That is misleading about this answer. If you're going to use a dummy delay loop then you must write a delay loop that won't get optimized away by the compiler. You can do that by turning off compiler optimizations or declaring the loop counter variable as [volatile](https://stackoverflow.com/questions/246127/why-is-volatile-needed-in-c). – kkrambo Nov 23 '20 at 15:28
  • @Lundin `for(int i = 0; i < N;i++) asm("");` – 0___________ Nov 24 '20 at 17:09
  • @P__JsupportswomeninPoland Unlike volatile, that snippet isn't guaranteed to actually generate any executable machine code. Not even a loop with `asm("NOP");` is guaranteed to do that. – Lundin Nov 24 '20 at 18:19
  • @Lundin it actually does guarantee. It works as a barrier. If someone is very strict: `asm(":::memory");` instead. No instruction does not mean no action! – 0___________ Nov 24 '20 at 20:43
  • @P__JsupportswomeninPoland You are assuming some very specific system and compiler here. – Lundin Nov 25 '20 at 07:26
  • @Lundin Yes STM32 and existing compilers. – 0___________ Nov 25 '20 at 09:51
  • @P__JsupportswomeninPoland As in gcc-arm-none-eabi? OP seems to be using Keil. – Lundin Nov 25 '20 at 09:54
  • @Lundin asm("") or __asm("") works in any known by me compilers. gcc, keil, IAR, GHC. Other are not in the common use., – 0___________ Nov 25 '20 at 10:00