With the Rowley Crossworks software set up, it is time to make some code. As usual, it is easiest to start out with a simple LED flasher. Why break with tradition. The target board for this project is the IAR STM32-SK because I happen to have one after winning it in a contest…

This board has a common-or-garden 16×2 LCD, a number of LEDs, four push buttons and various connectors. It is an excellent starting point for STM32 projects. For this example, almost any setup will do so long as you can connect an LED and a pushbutton.

 

The processor on this board is the STM32F103RB.

 

In CrossStudio, select File | New Project. If you have the software set up as in the preceding post, You will be given a choice of project types. Select Generic STM32 and ‘An Executable for STMicroelsectronics STM32’ along with a suitable name and folder and press Next.

 

Now you get to choose your processor and crystal speed along with options for output files and how much you want printf() and scanf() to be able to do.

 

Again, select Next to see a list of the files that will be automatically linked in to your project.

 

A final click on Next lets you choose the configurations you want. For now, leave them all ticked.

 

At this point, you have a blank empty project with nothing but the system startup files included. These are not actually copied into the project folder. Instead, the project properties point to the original files held in the Crossworks installation folder. Consequently, do not edit these files or you will change the startup for every other project that uses them. The project Explorer pane should look like this:

 

Now we need to add a ‘main’ source file. Select File | New and create a suitable C source code file in the project folder:

 

And now for the tricky bit. What on earth needs to go in this file? Well, the STM32 can be a pretty tricky beast to configure. The clocks need setting up, the peripherals must be enabled and configured… There are loads of things to do. Rather than go through all the grisly details line by line, simply open the main.c file you just created and drop the following into it.

#include "stm32.h"
#include "targets/STM32F10x.h"
uint32_t state;

void delay(int count) {
  volatile int i = 0;
  while (i++ < count);
}

// IAR STM32-SK LED1 is on PA4.
// The WAKE-UP button is on PA0  

void board_init(void) {
  uint32_t temp;
  uint32_t mask;
  uint32_t mode;
  uint32_t pin;
  // RCC is the Reset and Clock Control system. Each peripheral needs to have a clock
  // signal enabled before it will work. This is achieved by setting a single bit
  // in the APB2ENR register of the RCC
  RCC_APB2ENR |= RCC_APB2ENR_IOPAEN;
  // Once it has a clock, we can configure the port pins as inputs and outputs
  // Each GPIO has a Configuration Register. Since there are 16 bits in each register
  // and each pin needs four bits to define its state, there are two CRs for each GPIO
  // There is a two bit CNF field (the higher two bits):
  // Input -
  // CNFy = 00 Analog Mode
  // CNFy = 01 Floating input (reset state)
  // CNFy = 10 Input with pull up/down
  // CNFy = 11 Reserved
  // Output -
  // CNFy = 00 General Purpose Output push-pull
  // CNFy = 01 General Purpose Output Open Drain
  // CNFy = 10 Alternate Function Output Push-pull
  // CNFy = 11 Alternate Function Output Open Drain
  //
  // There is also a two bit MODE (the lower two bits):
  // MODEy = 00 => Input Mode (reset state)
  // MODEy = 01 => Output Mode < 10MHz
  // MODEy = 10 => Output Mode < 2MHz
  // MODEy = 11 => Output Mode < 50MHz
  // We want just PA4 to be an open drain output at 10MHz (no reason) so we set
  //    CNF4 = 01, MODE4 = 01
  // so mode = 0b0101 = 0x05
  pin = 4;
  mode = (uint32_t) 0x05 << (pin * 4);
  mask = (uint32_t) 0x0f << (pin * 4);
  temp = GPIOA_CRL & ~mask;
  GPIOA_CRL = temp | mode;
  // And now for the push button WAKE-UP. It is on PA0. We are going to use
  // the pin as a standard input rather than use the WAKE-UP alternate function
  // on this board, the pin is pulled low by a resistor and the button pulls the input high
  // so we want to configure the pin as a floating input.
  // from the information above, you will see this is the default state for the pin but, just
  // the exercise, we can explicitly configure it.     mode = 0b0100 = 0x04
  pin = 0;
  mode = (uint32_t) 0x04 << (pin * 4);
  mask = (uint32_t) 0x0f << (pin * 4);
  temp = GPIOA_CRL & ~mask;
  GPIOA_CRL = temp | mode;
}

void set_leds(unsigned on) {
  if (on)
    GPIOA_BSRR = (1 << 4);
  else
    GPIOA_BRR = (1 << 4);
}

int get_button(void) {
  if (GPIOA_IDR & 0x01) {
    while ((GPIOA_IDR & 0x01) == 0x01);
    return 1;
  } else {
    return 0;
  }
}

void main(void) {
  board_init();
  state = 0;
  while (1) {
    switch (state) {
      case 0:
        set_leds(0);
        if (get_button()) {
          state = 1;
        }
        break;
      case 1:
        set_leds(1);
        if (get_button()) {
          state = 0;
        }
        break;
    }
  }
}

Notice that there are a number of ways of changing the state of an output pin. Each GPIO has two 32-bit data registers (GPIOx_IDR, GPIOx_ODR), a 32-bit set/reset register (GPIOx_BSRR), a 16-bit reset register (GPIOx_BRR) and a 32-bit locking register (GPIOx_LCKR).

GPIOx_ODR holds a copy of the data sent to the Output driver. You can write to it to set the value directly. That does not mean that the pin will assume that state. It may be configured as an input or some device connected to the pin may be holding it in a particular state. Reading the ODR tells you what you set the pin to, not what it is. To read what the pin actually is, read the appropriate GPIOx_IDR. When a pin is configured as an output, a Schmitt trigger is enabled on the input. Thus, the IDR will show the physical state of the pin. When a pin is configured as an input with pullup/pulldown, the value in the ODR is used to determine whether that pin is pulled up or down. these pullups/pulldowns are weak. A pin configured as an analogue input always returns a ‘0’.

It is common to want to change the state of particular pins on a GPIO without disturbing the value on the other pins. This can be achieved by reading the ODR, masking off the relevant pins, setting the values and writing back. Yes, that is tedious. And it is not atomic. That is, the value on the other pins may be changed by, for example, an interrupt while you are half way through the process and you may write back the wrong value. Instead, the STM32 lets you set or reset a pin with a single write. This is achieved by writing a ‘1’ to the appropriate bit in the GPIOx_BSRR (to set a pin) or GPIOx_BRR (to clear it). If you want to set all the pins in one go then, by all means, just write to the GPIOx_ODR.

The locking register is a ‘no turning back’ option. Writing a ‘1’ to a bit in the GPIOx_LCKR will ensure that the value on the corresponding pin cannot change until there is a processor reset.
There is often nothing more annoying that trying someone else’s example that won’t work on your kit but this is pretty straightforward I think. It does rely on you having the same toolchain as that will dictate the location of the relevant include files and the values of the constants defined therein.

 

If that is so, this should work. The comments should make it relatively easy to change the port pins for the LED and the button. If it woks, the button should toggle the state of the LED. There is no real attempt to debounce the switch. Maybe next time.

This Post Has 23 Comments

  1. Bruno

    Great example thanks! I’ve just tweaked it a little bit to make it work with Olimex’s STM32-P103 board (button on same GPIO, but LED on PC12, so I needed to use the GPIOC CRH instead GPIOA CLH) and it works ok 🙂

  2. Bruno

    Sorry, meaned CRL instead of CLH 😛

  3. Shannon

    It will work as is but…

    pin = 0; mode = (uint32_t)0x04 << (pin * 0); mask = (uint32_t)0x0f << (pin * 0); temp = GPIOA_CRL & ~mask; GPIOA_CRL = temp | mode;

    … for the mode and mask lines shouldn’t that be:

    .... << (pin * 4); .... << (pin * 4);

    I know…picky picky. Just want things to be clear for newbies.

  4. peteh

    Well spotted. Thank you.

    I will amend the code after dinner…

    Done!

  5. Suresh

    I wrote similar program for olimex STM32H103 board. But it is not working. The code is attached bellow. I complied with both flash debug as well as RAM debug. Help me please.

    with adv thanks.

    Suresh M

    #include <targetsSTM32F10x.h> main() { unsigned int k; RCC_APB2ENR |= RCC_APB2ENR_IOPAEN; RCC_APB2ENR |= RCC_APB2ENR_IOPCEN; GPIOC_CRH = 0x06000000; // portC. pin 12 as open drain GPIOA_CRH = 0x00000060; //PORTA pin 9 as push pull GPIOC_BRR = 0x1000; // reset PortC.pin12 GPIOA_BRR=0x0200; while(1) { GPIOC_BSRR = 0x00001000; // set the portC.pin12 GPIOA_BSRR=0x00000200; for(k=0; k<0x0FFFFF; k++); GPIOC_BSRR = 0x10000000; // reset the portC.pin12 GPIOA_BRR=0x0200; for(k=0; k<0x0FFFFF; k++); } }

  6. peteh

    What happens when you run it?
    Have you tried it in a simulator?
    I presume it builds on your system.

    I have only had a quick look so I might be wrong but…

    I see from the schematic for the STM32-H103 that a jumper is used to connect the status LED to the pin PORTC.PIN12. Is that connected?

    The line GPIOC_CRH = 0x06000000; // portC. pin 12 as open drain
    configures pin 14, not pin 12. It should read GPIOC_CRH = 0x00060000; // portC. pin 12 as open drain

    The line GPIOA_CRH = 0x00000060; //PORTA pin 9 as push pull
    actually configures PORTA.9 as open drain. If you want it to be push-pull, you will need GPIOA_CRH = 0x00000020; //PORTA pin 9 as push pull

    Setting and resetting the pins should use this code: GPIOC_BSRR = 0x00001000; // set PORTC.12 GPIOA_BSRR = 0x00000200; // set PORTA.09 GPIOC_BRR = 0x00001000; // reset PORTC.12 GPIOA_BRR = 0x00000200; // reset PORTA.09

    Your delays are very long. The outputs would change state every 2 seconds or so I think. Not a problem so long as you are expecting it.

  7. Suresh

    Dear peteh,

    Thanks for your valuable suggestions.

    The program is working well for PORTC. I wired myself another LED in PORTA, I’ll checkup, why it is not working. Thanks once again.

    with regards,
    Suresh M

  8. Suresh

    PORT A LED also working. Error in my wiring. thanks.

  9. Suresh

    Dear Peteh,

    With Wiggler ddebug, if I load the Blink LED program (compiler with Thumb debug), it is working. Once I remove the JTAG and reset the processor, it is not working. Is there anything extra I have to do for Flash debug / release? Thanks in advance.

    with regards,
    Suresh M

  10. Suresh

    Dear Peteh,

    I compiled the program in Flash Debug mode and downloaded the code using the JTAG. The program stated working. Once I reset the processor, the program is not working. I’m doubt is it writing in flash. But I verified the address with JTAG, it’s wring in 0x08000000 onwards. Hope it’s Flash space. Still anything I have to do to work after power-on-rest? Thanks in advance.

    with regards,
    Suresh M

  11. Suresh

    Dear Peteh,

    Thanks. Now the program works after power-on Reset.

  12. Anonymous

    Hey Bruno,

    I also have the STM32-P103 (like you) and cannot make it blink…

    #include "stdint.h" #include "targets/STM32F10x.h" uint32_t state; void delay(int count) { volatile int i=0; while (i++ < count); } // IAR STM32-SK LED1 is on PA4. // The WAKE-UP button is on PA0 void board_init(void) { uint32_t temp; uint32_t mask; uint32_t mode; uint32_t pin; // RCC is the Reset and Clock Control system. Each peripheral needs to have a clock // signal enabled before it will work. This is achieved by setting a single bit // in the APB2ENR register of the RCC RCC_APB2ENR |= RCC_APB2ENR_IOPAEN; // Once it has a clock, we can configure the port pins as inputs and outputs // Each GPIO has a Configuration Register. Since there are 16 bits in each register // and each pin needs four bits to define its state, there are two CRs for each GPIO // There is a two bit CNF field (the higher two bits): // Input - // CNFy = 00 Analog Mode // CNFy = 01 Floating input (reset state) // CNFy = 10 Input with pull up/down // CNFy = 11 Reserved // Output - // CNFy = 00 General Purpose Output push-pull // CNFy = 01 General Purpose Output Open Drain // CNFy = 10 Alternate Function Output Push-pull // CNFy = 11 Alternate Function Output Open Drain // // There is also a two bit MODE (the lower two bits): // MODEy = 00 => Input Mode (reset state) // MODEy = 01 => Output Mode < 10MHz // MODEy = 10 => Output Mode < 2MHz // MODEy = 11 => Output Mode < 50MHz // We want just PA4 to be an open drain output at 10MHz (no reason) so we set // CNF4 = 01, MODE4 = 01 // so mode = 0b0101 = 0x05 pin = 4; mode = (uint32_t)0x05 << (pin * 4); mask = (uint32_t)0x0f << (pin * 4); //temp = GPIOA_CRL & ~mask; temp = GPIOC_CRL & ~mask; //GPIOA_CRL = temp | mode; GPIOC_CRL = temp | mode; // And now for the push button WAKE-UP. It is on PA0. We are going to use // the pin as a standard input rather than use the WAKE-UP alternate function // on this board, the pin is pulled low by a resistor and the button pulls the input high // so we want to configure the pin as a floating input. // from the information above, you will see this is the default state for the pin but, just // the exercise, we can explicitly configure it. mode = 0b0100 = 0x04 pin = 0; mode = (uint32_t)0x04 << (pin * 4); mask = (uint32_t)0x0f << (pin * 4); temp = GPIOA_CRL & ~mask; GPIOA_CRL = temp | mode; } void set_leds(unsigned on) { if (on) //GPIOA_BSRR = (1<<4); GPIOC_BSRR = (1<<4); else //GPIOA_BRR = (1<<4); GPIOC_BRR = (1<<4); } int get_button(void) { if (GPIOA_IDR & 0x01){ while((GPIOA_IDR & 0x01) == 0x01); return 1; } else { return 0; } } void main(void) { board_init(); state = 1; while(1) { switch (state){ case 0: set_leds(0); if (get_button()) { state = 1; } break; case 1: set_leds(1); if (get_button()) { state = 0; } break; } } }

    And I cannot make it blink…I’m sure I missed something!

    Thoughts?…

    Thanks in advance!

  13. peteh

    At the beginning of board_init(), there is a line that reads:

    RCC_APB2ENR |= RCC_APB2ENR_IOPAEN;

    You will need to add the following:

    RCC_APB2ENR |= RCC_APB2ENR_IOPCEN;

    to turn on the clock for GPIO Port C.

    Don’t change the original line as it turns on the clock for port A for the button – if you have one.

    I think.

  14. Anonymous

    I just added that line, and still no go…

  15. Anonymous

    // this program should turn on the LED on the Olimex STM32-P103 board // It's wired to pin 53 (pc12) #include "targets/STM32F10x.h" void delay(int count) { int i=0; while (i++ < count); } main() { RCC_APB2ENR |= RCC_APB2ENR_IOPCEN; GPIOC_CRH = 0b1100000000000000000; GPIOC_BRR = 0b1000000000000; // reset PortC.pin12 while(1) { // turn on LED GPIOC_BSRR = 0b1000000000000;; // set PORTC.12 delay(100000); // turn off LED GPIOC_BRR = 0b1000000000000;; // reset PORTC.12 delay(100000); } }

  16. peteh

    I overlooked that.

    Your code was toggling pin 4 on port C rather than pin 12.

  17. Leandro

    Thanks for your post. Based on it, I wrote a similar example for LPC1768 boards. It may be useful for someone.

    // this example works on the NGX Blueboard LPC1768 and on the Keil MCB1700 #include "LPC1000.h" #define LED (1<<29) #define BUT (1<<10) int state; void board_init(void) { // Clear P1_29 FIO1CLR = LED; // Configure P1_29 as GPIO (00) PINSEL3 &= (~0x0C000000); // Configure P2_10 as GPIO (00) PINSEL4 &= (~0x00003000); // GPIO P1_29 is an output FIO1DIR |= LED; // GPIO P2_10 is an input FIO2DIR &= ~BUT; } void set_leds(unsigned on) { if (on) FIO1SET = LED; else FIO1CLR = LED; } int get_button(void) { if (FIO2PIN & BUT){ while(FIO2PIN & BUT); return 1; } else { return 0; } } void main(void) { board_init(); state = 0; while(1){ switch (state){ case 0: if (get_button()) { set_leds(1); state = 1; } break; case 1: if (get_button()) { set_leds(0); state = 0; } break; } } }

  18. LarryP

    Thanks much for this example; it helped me get started. I had only one trivial issue; the compiler (with as-installed settings in Rowley CrossStudio 2.0.7), didn’t like void main(void), so I changed the return type to int. Before reading the code carefully enough, I expected this to blink the LED without any user action. I added a comment to that effect, also indicating which switch to use, and which LED should change state. Modified code follows; thanks again:

    // this example works on the NGX Blueboard LPC1768 and on the Keil MCB1700 #include "LPC1000.h" #define LED (1<<29) #define BUT (1<<10) int state; void board_init(void) { // Clear P1_29 FIO1CLR = LED; // Configure P1_29 as GPIO (00) PINSEL3 &= (~0x0C000000); // Configure P2_10 as GPIO (00) PINSEL4 &= (~0x00003000); // GPIO P1_29 is an output FIO1DIR |= LED; // GPIO P2_10 is an input FIO2DIR &= ~BUT; } void set_leds(unsigned on) { if (on) FIO1SET = LED; else FIO1CLR = LED; } int get_button(void) { if (FIO2PIN & BUT){ while(FIO2PIN & BUT); return 1; } else { return 0; } } /* 09July2010/LEP: * This version (with switch statement) uses SW2 to toggle the state of * LED D8. Note that is does NOT do a free-running blink, as might first be expected. * This is working for me using target NGX ARM USB JTAG and an NGC blueboard LPC1768H, with a JTAG clock divider of 4. * Your mileage may vary. */ int main(void) { board_init(); state = 0; while(1){ switch (state){ case 0: if (get_button()) { set_leds(1); state = 1; } break; case 1: if (get_button()) { set_leds(0); state = 0; } break; } } }

  19. Anonymous

    Hello!

    Thanks for the nice kick-start. I’m afraid I have some newbie questions if somebody has the patience, I would really appreciate some help.

    Thanks in advacne,

    Joseph
    Mannheim, Germany

    -1-
    what are with the lines

      
    #include "stm32.h"
    
    I just get an error and it can't find the .h file.
    
    -2-
    I tried commenting the stm32.h out but just got a bunch of errors.
    Is there another basic include file that should be used? stdint.h doesn't work either.
  20. Peter Kelly

    Hi Peter, reading your “Crossworks Blinkey Project 1” AND owning Crossworks, AND being an old (now) programmer (6502,6800,6801,05,09 68kK etc) AND trying to make SOMETHING happen on a micro board after 15 years of being in abother field AND being confronted with a bewildering array of acronims and other things clearly beyond me AND having tried seberal times to just get a LED to blink, I turn to your tutorial in hope AND desperation, I note the key phrase in your tutorial “With the Rowley Crossworks software set up” — alas please point me to that gem of information, so that I may proceed without the now etched “FAILURE LOOMING” at the forefront of my tiny mind. Thanks in anticipation.
    Also thanks for being so considerate in sharing the new breed of black magic with the rest of us un-annointed hoards.

    Cheers from downunder,
    Peter Kelly

  21. Peter Kelly

    Could I have the link to the PREVIOUS project which sets up the Crossworks software. (if you have included this, please forgive me, my wife is callinf to have dinner carved, and I have only scanned your link contents and it appears to be the same one as I already have.

    Cheers,
    p

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.