Writing code for the STM32


Writing Code for the STM32F

Introduction

Following my previous post on setting a project and all the necessary files for an STM32F, I will now guide you to setup the code that needs to be written. In case you get lost in the steps below please refer to the following post Introduction to STM32F
 
All the code will be placed in the files which were created in Introduction to STM32F will be reused here. Before we start writing any code, first you need to understand how everything will be structured. Below is a summary of how the code will be organized:
 

Preparing to write the code

In order to code the STM32F C programming language will be used. In C programming language we don't write all the code in a continuous set of lines in a single file. This makes the code difficult to maintain and re-use. The proper way to proceed is to first decide what the microcontroller needs to do. For this post I will keep things simple I just want to make pin PB8 an output and then make it output a "high" value i.e. the pin will output 3.3V(3.3V being the voltage by which the STM32F is powered). The other action that can be made with a pin set as output is make it output "Low" i.e. 0V
 
So lets focus on how we can make this simple action using a clean approach.
 

 Structuring code into files

Now that our target is set we now need to focus on where we will put the code. In C programming language code is placed in two files which are described below:
 
 
File Type

File extension

Usage
Header file

.h
Declares the functions that will be used in the source file

Source file

.c
 Implements the functions that have been declared in the header file
 So in our case we will divide our code into two files:
 
GPIOTest.h
GPIOTest.c
 
You could get away with that but normally C programming language requires us to use another file by convention. That file is normally called Main.c. The reason why you need this file is that for any C program needs a starting point and that starting point is always put into the file Main.c. Without that starting point whatever code you have written will not be executed because nothing will make it start.
 
So to conclude on this we need a third file:
Main.c
 
So this bring us to creating 3 files in total GPIOTest.h, GPIOTest.c and Main.c
 
The steps to create these files have already been mentioned here: Introduction to STM32F 
 

Deciding on the code that will be placed into each file

The next step now is to decide which code will go into which file. In order to be able to set a pin as an output and set its value to high, there are some actions which need to be done first. These actions are more exactly configurations that must be set. Once the configurations are complete, we can then proceed to our aim. The table below contains the order in which the steps must be taken:

No.

Action

Purpose
1
Initialize the PLL
Define the clock speed to be used by the STM32F
2
Initialize the peripheral clock
The output pin is connected to a bus. For the bus to function its clock must be initialized.
3
Define the output pin
The microcontroller has several pins. The code needs to define which of the pins will be used as an output
4
Set the output pin high
The desired action to be achieved
Now that our steps have been defined the next thing to do is to decide how to structure them in code. In C programming language specific actions are coded into what's called function. A function is a block of code that performs a predefined action.
 
Another concept to bear in mind is that in C programming language, functions have to be declared in a header file. This involves giving a unique name to the function without any spaces. It also involves defining if the function will require any input and will send back any output. To keep things simple we will use functions which will take action directly on the STM32F i.e. they don't need any input and will not send back any output. All we will need to do is define the function names. We will proceed as per below:
 

No.

Action

Function Name
1
Initialize the PLL
Init_PLL
2
Initialize the peripheral clock
Init_RCC
3
Define the output pin
Init_GPIO
4
Set the output pin high
Set_Pin_High
You will notice I have used a specific naming convention, the reasoning behind being that the function name should describe what its doing. It will then avoid you to read all the code which has been placed into the function to understand its purpose.
 
Now that we have settled on the function name, the next thing is to decide where to place them. To keep things simple in our case we will define all our functions in the GPIOTest.h header file.
 
In practice and more complex implementations, several header and source file will be used e.g. lets say you want to use the ADC on the STM32F then you could create a header and source file for initialization of the STM32F only, another header and source file for the ADC and a main file to call your implemented functions.
 

Coding GPIOTest.h

Based on the above you need to write the following lines of code in your GPIOTest.h file:
 
#include "stm32f10x.h"
 
void Init_PLL(void);
void Init_RCC(void);
void Init_GPIO(void);
 
 
 Now to explain what you see above.
 
#include "stm32f10x.h": The #include statement might seem like a mystery but look closely you will see that it talks about "stm32f10x.h" yes it ends with a .h extension. And yes its a header file. First of all the #include is a C statement that means combine with its normally used with header files. The actions we want to take on the microcontroller namely: Initializing the PLL, Initializing the peripheral clock e.t.c. could be done directly but to make our life simpler ST has already made all the complicated job for us at the back and coded in a file called stm32f10x.h.
 
Next we get this line: void Init_PLL(void); this is what is called a function prototype. We already discuss why Init_PLL was chosen above. The void keyword you see at the beginning means there is no output expected from this function. The last part (void) is used to define values that must be input to the function. Like I mentioned above we do not want to feed any input to the function that's why the void keyword has been used again here. You will also note that I ended the line with a ; character, this means that I have completed my function declaration and we can now move on to something new. The same concept I have just explained applies to all the other lines you see above.
 

Coding GPIOTest.c

Now we will take care of GPIOTest.c. This file will contain the code we want to put into each of our function. But first we need to do something to say that we are implementing the functions found specifically in GPIOTest.h in order to do that we will use the #include statement. A side note here naming the files with the same name does not do anything automatically for you. Its the #include statement which will handle that job. So we need to do the following in GPIOTest.c:
 
#include "GPIOTest.h"
 
 

Coding Init_PLL

 
Now the system understands that we mean to implement the functions defined in that header file.
Next we need to code the Init_PLL function and we do it as follows:
 

#include "GPIOTest.h"
 
void Init_PLL(void)
{
  RCC_DeInit();
  RCC_HSEConfig(RCC_HSE_ON);
 
 
  if (RCC_WaitForHSEStartUp() == SUCCESS)
  {
    FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
    FLASH_SetLatency(FLASH_Latency_2);
   
    RCC_HCLKConfig(RCC_SYSCLK_Div1);
    RCC_PCLK2Config(RCC_HCLK_Div1);
    RCC_PCLK1Config(RCC_HCLK_Div1);
 
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
   
    RCC_PLLCmd(ENABLE);
   
    while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
    while (RCC_GetSYSCLKSource() != 0x08);
  }
  else
  {
    for(;;);
  }
}


 
 Now if this is your first encounter with an STM32F don't panic! Its absolutely normal as a reaction because you have no acquaintance to what all that means. First of all what you see applied to a STM32F103XX series you might need to change it for the STM32F4XXX series.
 
You will see that after void Init_PLL(void) there is a {  character. This denotes where the function code begins and the }  denotes where the  function ends as the last line above. The same characters appear after the following if (RCC_WaitForHSEStartUp() == SUCCESS) the if  keyword does a check and in case the check is successfully the set of code above is executed again these are enclosed in the same characters { and }. The else keyword always accompanies the if  keyword. In case the check made by the if  fails then the  else  takes over but both will never execute only one or the other is executed based on the result of the check.
 
Again we will keep things simple here and I will explain things simply as from this line:
 
RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
 
Found it above? Great. So basics first, every microcontroller requires a clock to work. This clock is derived externally from a crystal oscillator in most circumstances. Some microcontrollers also have an internal clock. To switch this clock on a set of devices must be initialized into the microcontroller, that's all the lines you see before  RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
 
Now the line that says RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9); is doing something very specific for us. In my example my STM32F has an 8MHz oscillator on its oscillator pins. If you look at the datasheet for the STM32FXX series it says 72MHz but how de we achieve this? We use the PLL module. This is done my first dividing the 8MHz clock and them multiplying it with a specific value.
 
The line RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9); simply means take the 8MHz crystal divide it by one and then multiply by 9 i.e. (8MHz/1) X 9 =72MHz. In case you are wondering how will the STM32F will know you have an 8MHz oscillator outside well it can't you have to know this part and from there define the RCC_PLLSource_HSE_DivX and the RCC_PLLMul_X by yourself. If you do it wrong then your STM32F will simply not start up at all.
 
For the last part which is after the line RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9); this means that we are now applying our settings and checking if the STM32F accepts our configurations.
 
 

Coding Init_RCC

Next we can now take care of the function Init_RCC. The code that goes into this function is shown below:
 
 
void Init_RCC(void)
{
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
}
 To explain the above code remember I wanted to set pin PB8 high. The STM32F has its pins grouped under a port name designated with an alphabet. On an STM32F the pins are grouped into ports named A, B and so on. The pin in my example is found on port B commonly referred to as GPIOB. All theses ports or GPIO's are inactive by default, to use them the clock to run these GPIO must be enabled. The code above does just that. If you want to use a pin found on GPIOA as well then this GPIOA clock must be enabled as well.  
 
 

 Coding Init_GPIO

 If you notice from the code above I only activated GPIOB but I did not mention which pin on GPIOB I wanted to use. The next part is handled in the code below.
void Init_GPIO(void)
{
  GPIO_InitTypeDef GPIO_Var;
 
  GPIO_Var.GPIO_Pin=GPIO_Pin_8 ;
  GPIO_Var.GPIO_Mode=GPIO_Mode_Out_PP;
  GPIO_Var.GPIO_Speed=GPIO_Speed_50MHz;
  GPIO_Init(GPIOB,&GPIO_Var);
 
}
 
 The line  GPIO_InitTypeDef GPIO_Var; is creating a structure called GPIO_Var of type GPIO_InitTypeDef. The GPIO_InitTypeDef contains all the parts needed to define how we want to use a pin on the microcontroller. Not that we know this we can say we want to use pin 8 using the following line:
GPIO_Var.GPIO_Pin=GPIO_Pin_8 ;
 
Next we need to define that the port has to be an output using the following:
GPIO_Var.GPIO_Mode=GPIO_Mode_Out_PP;
 
We then need to define the port speed as follows:
GPIO_Var.GPIO_Speed=GPIO_Speed_50MHz;
 
Note you will never be able to achieve 50MHz with that pin but at least we want to set it to operate at its maximum possible speed.
 
Finally to apply all the configuration the following line of code is used:
GPIO_Init(GPIOB,&GPIO_Var);
 

Complete GPIOTest.c code

To sum it up your GPIOTest.c code should look as per below:
 
#include "GPIOTest.h"
 
 
void Init_PLL(void)
{
  RCC_DeInit();
  RCC_HSEConfig(RCC_HSE_ON);
 
 
  if (RCC_WaitForHSEStartUp() == SUCCESS)
  {
    FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
    FLASH_SetLatency(FLASH_Latency_2);
   
    RCC_HCLKConfig(RCC_SYSCLK_Div1);
    RCC_PCLK2Config(RCC_HCLK_Div1);
    RCC_PCLK1Config(RCC_HCLK_Div1);
 
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
   
    RCC_PLLCmd(ENABLE);
   
    while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
    while (RCC_GetSYSCLKSource() != 0x08);
  }
  else
  {
    for(;;);
  }
}
 
 
void Init_RCC(void)
{
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
}
 
 
void Init_GPIO(void)
{
  GPIO_InitTypeDef GPIO_Var;
 
  GPIO_Var.GPIO_Pin=GPIO_Pin_8|GPIO_Pin_9 ;
  GPIO_Var.GPIO_Mode=GPIO_Mode_Out_PP;
  GPIO_Var.GPIO_Speed=GPIO_Speed_50MHz;
  GPIO_Init(GPIOB,&GPIO_Var);
 
}
 
 
void Init_STM(void)
{
  Init_PLL();
  Init_RCC();
  Init_GPIO();
 
}
 

Coding Main.c

Now that we have completed all the above we now need to call these functions and they need to be called in the order we talked about before i.e.
 
No.
Action
1
Initialize the PLL
2
Initialize the peripheral clock
3
Define the output pin
4
Set the output pin high
You will note here I did not write any code yet for the fourth action we will come to that.
 
So in order to accomplish all the four actions we mentioned above there needs to be a starting place where we can call our functions. That starting point is called the main function. All code that need to be executed when the STM32F starts running is placed here.
 
So how do we achieve this? First of all you need to understand how to call a function. Remember I mentioned above that a function  needs to have a unique name, one of the reasons behind is that you can uniquely call it. And how do you call the function? Well simply use its name.
 
This being cleared lets implement our functions call into main. The code is listed below for the Main.c file:
 
 
#include "GPIOTest.h"
 
int main()
{
  Init_PLL();
  Init_RCC();
  Init_GPIO();
  GPIO_SetBits(GPIOB,GPIO_Pin_8);
 
  return 0;
}
 
To explain the code I will start with the first line, #include "GPIOTest.h" means combine the functions we have declared in GPIOTest.h with the Main.c file.
 
Next this line shows up:
int main()
{
 
The int main() is where the main function or starting point of our program is implemented. As you notice compared to other functions main does not need to be defined in a header file. The { symbol indicates that the contents that we wish to place into main will be defined next.
 
 

Init_PLL(); will cause the Init_PLL(); function that we declared in GPIOTest.h to be looked for. We have implemented this function in GPIOTest.c and the lines of code we defined there will be executed. The same applies to Init_RCC(); and Init_GPIO();.
 
GPIO_SetBits(GPIOB,GPIO_Pin_8); is  new line here we did not encounter before. Remember I was saying I did not code my fourth action which sets pin 8 on GPIOB high, well this line does this job. GPIO_SetBits is one of the function that's built by ST for us. SetBits means set high, what you see in between the brackets i.e. (GPIOB,GPIO_Pin_8);  are called arguments or parameters, the first part says we want to interact with GPIOB and the second part deals with the specific pin we want to set high i.e. pin8.
 
The last line is return 0; The return is a reserved keyword used to push a value out of a function and it works in conjunction with the int keyword used before main(). We could also have define main as being void main() and avoid the return statement like we did for all other functions in GPIOTest.c
 
The main function then completes with a } character, which works in pair with the { character after int main() and indicates the end of the main function.
 
That's it its time now to compile, make and program your STM32F. If you have followed all the instructions I provided here  Introduction to STM32F and placed the above code in the proper files you should have the pin8 pin on GPIOB output high. You can check this by connection a led + resistor between PB8 and ground, measure it with a voltmeter or even see a flat 3.3V trace on an oscilloscope.
 

Conclusion

I hope that this post has been able to shed some lights on the code required to do a "Hello world" application with an STM32F. There certainly is more to coding in C that what I explained above but at least you have an understanding of the code segments.
 
The above files i.e. GPIOTest.h and GPIOTest.c can also be copied to the Template structure I mentioned here Introduction to STM32F and be reused in all projects.

 
Good luck and happy coding.
 

 
 
 

Comments

Popular posts from this blog

Beginner's Guide to STM8

Beginner's Guide to STM32CubeMX