STM32 blink LED – first programming steps in Keil

STM32 first steps – blink LED

On board of STM32 (some people call it “blue pill”) there is green LED, or at other boards there is blue LED. First step is to get familiar with Keil environment, set everything properly and start programming. It is not difficult, but require some time to get used to this ‘new way’ aside ‘Arduino way’ or making ‘sketch’-es. This is true C programming, which is maybe less popular than Arduino, but Keil has some import functions so that you can import .ino file and convert it to new way. I found it works faster and has many cool functions. The problem is that there is almost no ‘helper’ libraries, or those are hidden or not available in free version of the Keil IDE.

Use ST-Link V2 programmer, support debug and goes up to 4 MHz uploat to the board with MCU. Do not forget ST-Link driver.

Before everything, here are links to reference manual and two MCUs (both STM32, different model) showed on the video below.

STM32F103C8  and

STM32F103VET6

Main reference manual for STM32F101 to STM32f107.

First blink program:

#include "stm32f10x.h"

void delay(long cycles)
{
  while(cycles >0)
  cycles--; // Some stupid delay, it is not in milliseconds or microseconds, but rather in some 'wasted clock cycles'
}

void gpio_ports_enable(void)
{
  RCC->APB2ENR |= RCC_APB2ENR_IOPAEN |     RCC_APB2ENR_IOPBEN|RCC_APB2ENR_IOPCEN; //ports A & B clock enabled
  GPIOC->CRH = 0x44344444; //Pin C13 enable. If you don't like number 13, use pin C_1_P.M. LOL
}

int main(void)
{
	gpio_ports_enable();

	for(;;)  //main loop - read "forever", or you may use 'while(1)'
	{
		  GPIOC->BSRR = GPIO_BSRR_BS13; 
		  delay(5000000);
		  GPIOC->BSRR = GPIO_BSRR_BR13; 
		  delay(5000000); 
		}	
	}
}

Thee thing with enabling port, if you remember Arduino way it is ‘PinMode(13, OUTPUT);’, here is not the case, we can write in few ways, for example long one:

GPIOC->CRH |= GPIO_CRH_MODE13_0|GPIO_CRH_MODE13_1;
GPIOC->CRH &= ~(GPIO_CRH_CNF13_0|GPIO_CRH_CNF13_1);

Which will ‘yield’ the same effect as simple expression:

GPIOC->CRH = 0x44344444;

Where numbers 4 are default or reset value which make pins input, floating – or least amount of current consumption. To make your task easier, use calculator in “Programmers” mode (at leas Windows has it). First type in the calculator HEX value you found in the datasheet for the register of interest, then click on icon next to DWORD (initially there is QWORD – click until DWORD appears), and it will show you map with groups of four bits – ‘nibbles’. One byte contains two nibbles. Click on that table to change values and compare with values that is needed for your application and compare it with the datasheet.  Here is how it looks after setting pin PC13 (or just C13) in the calculator:

STM32

To get this calculator in Windows, pres Windows key and type ‘calc’, or if it does not works, press and hold windows key and key ‘R’ to get command prompt, then type ‘calc’. On calculator, there is option ‘standard’, ‘scientific’, but we need ‘programmers’.

Another PWM blink program:

#include "stm32f10x.h"

void delay(long cycles)
{
  while(cycles >0)
  cycles--; // Some stupid delay, it is not in milliseconds or microseconds, but rather in some 'wasted clock cycles'
}

void gpio_ports_enable(void)
{
	RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN|RCC_APB2ENR_IOPCEN; //ports A & B clock enabled
	GPIOC->CRH = 0x44344444; //Pin C13 enable. If you don't like number 13, use pin C_1_P.M. LOL
}

int main(void)
{
	gpio_ports_enable();

	for(;;)  //main loop - read "forever"
	{
		
		for (int i=0;i<3000;i++)
		{
		  GPIOC->BSRR = GPIO_BSRR_BS13; 
		  delay(i);
		  GPIOC->BSRR = GPIO_BSRR_BR13; 
		  delay(3000-i); 
		}
				for (int i=0;i<3000;i++)
		{
		  GPIOC->BSRR = GPIO_BSRR_BS13; 
		  delay(3000-i);
		  GPIOC->BSRR = GPIO_BSRR_BR13; 
		  delay(i); 
		}
	}
}

This time the same LED on board slowly going bright, then dim at the same rate on STM32 boards. So far, I have only two versions: STM32F103C8 and STM32F103VET6. Firs one is cheapo popular “Blue Pill”, while second one is some advanced version with more pins, more memory and what not. Also more expensive. And I did not found pinout of this board. For this purpose, you may wish to install CubeMX program. This program generates file for starting, but that is some weird “HAL” structure, which I am not familiar, nor want to mess with that, since it is so confusing. But, at least it has MCU with designations on each pin after you click on it. Example:

 STM32Note that this is not programming environment, just “initialization code generator” for STM32.

Be sure to use Notepad++ for saving copy of the codes. In this type of Notepad, if you save program as for example ‘.c’ extension, it will gives you ‘colored’ nice looking text. I usually print from this thing and also saving this code to another place on the disk in the case that I lost trace of the original program which is somewhere in Keil directory.

Make it clean

Now it is time to move all setting functions and everything else to separate file (.c file and .h file). First click on “Add new item….” and chose “C file (.c)” and type name. On my YouTube video I made typo and instead ‘auxiliary’ I typed ‘auxilary’ – no a problem as long as .c file has the same name as .h file. Also, be sure that both files are in the same directory. Later you may wish to ZIP or RAR those file, so that there is not a mess. Here is one example how to move ‘gpio_ports_enable()’ and ‘delay(long cycles)’ function in separate files and what you need to include in the file that contains main function ‘int main()’. First example of blink LED with PWM thing, but this time just few rows:

#include "stm32f10x.h"
#include "auxiliary.h"

int main()
{
  gpio_ports_enable();
	
while(1)  //main loop - read "forever" 
	{

		for (int i=0;i<3000;i++)
		{
		  GPIOC->BSRR = GPIO_BSRR_BS13; 
		  delay(i);
		  GPIOC->BSRR = GPIO_BSRR_BR13; 
		  delay(3000-i); 
		}
				for (int i=0;i<3000;i++)
		{
		  GPIOC->BSRR = GPIO_BSRR_BS13; 
		  delay(3000-i);
		  GPIOC->BSRR = GPIO_BSRR_BR13; 
		  delay(i); 
		}
	}
}

Note that most of the things ‘vanished’, and new “auxiliary.h” appear in main code. So, first we move all unwanted codes in “auxiliary.c”:

#include "stm32f10x.h"

void delay(long cycles)
{
  while(cycles >0)
  cycles--; // Some stupid delay, it is not in milliseconds or microseconds, but rather in some 'wasted clock cycles'
}

void gpio_ports_enable(void)
{
	RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; //ports A & B & C clock enabled
	GPIOC->CRH = 0x44344444; //Pin C13 enable. If you don't like number 13, use pin C_1_P.M. LOL
}

It is still needed to be there “stm32f10x.h” library which actually recognizes register names. If you include just this auxiliary.c file, it will not work until you add “auxiliary.h” header file. This file tells compiler what are ‘exported’ functions (not imported as I said on the video, sorry). It is very simple:

#ifndef auxiliary_h_
#define auxiliary_h_

void delay(long cycles);
void gpio_ports_enable(void);

#endif

If you decide to make another project for STM32, providing that is related to that one, you may include those files and add new functions together. I found that this way is easier to manage in the case that we are lost and want for example to print it on laser or other printer to see whole code. For small codes, typically one page it is not a problem that it is everything together, but as program ‘grows’ and we are adding more and more stuff inside, it becomes very much populated. Enjoy programming STM32 stuff!

Let’s recap

First open project and everything as is on the video, then add #include “stm32f10x.h” header file in main code. Next, get used to write int main() { // } first and leave one row below all codes without pressing space on your keyboard, else compiler will complain. Next, write functions you want to have, and you may add prototypes of the function above. The difference between functions and prototypes is in presence or absence of curled parenthesis behind/below, and instead using semi-column ‘;’ at the end. So, for example:

void myFunction(void);

is a prototype. This is reminder for later movement of that functions in your header file(s). Actually in auxiliariy.c function (chose name that you will remember). Then below that your function:

void myFunction(void)

{

//some code here

}

When you want to call this function from main loop (it may be while(1){} or for(;;){}, whatever you chose it is infinite or forever loop) then you call it:

myFunction();  without anything else (if function does not return anything and does not need anything). For delay function in our case, we need to specify how many ‘wasted’ cycles we want, so calling this function is simple:

delay(500000);

From main loop.

STM32 is not that difficult to program, but the problem is absence of example codes. Take your time and make more codes. The more codes you make, the faster you will get used to it.

Leave a Reply

Your email address will not be published.

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