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 ;
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();.
Comments
Post a Comment