How to set up smartphones and PCs. Informational portal

Connecting a standard peripheral library to any STM32 family.

When creating your first application on an STM32 microcontroller, there are several ways to go. The first, classic, we take the exact description of the controller on the website www.st.com, which appears under the name "Reference Manual" and read the description of the peripheral registers. Then we try to write them down and see how the peripherals work. Reading this document is very useful, but at the first stage of mastering the microcontroller, you can refuse this, oddly enough. STMicroelectronics engineers have written a library of standard peripherals drivers. Moreover, they wrote many examples of using these drivers, which can reduce programming your application to pressing the Ctrl + C and Ctrl + V keys, followed by a small edit of the example of using the driver to suit your needs. Thus, connecting a peripheral driver library to your project is the second method of building an application. In addition to the speed of writing, there are other advantages of this method: the universality of the code and the use of other proprietary libraries such as USB, Ethernet, drive control, etc., which are provided in the source code and using a standard peripheral driver. There are also disadvantages of this method: Where you can get by with one line of code, the standard STM32 peripheral driver will write 10. The peripheral library itself is also provided in the form of source files, so you can trace which bit of which register a particular function changes. If you wish, you can go from the second method of writing a program to the first by commenting out a part of the code that uses the standard library on its own, which controls directly the peripheral register. As a result of this action, you will gain in control speed, the amount of RAM and ROM, and lose in the universality of the code. In any case, the engineers of the Promelectronica company recommend using the library of standard peripherals at least at the first stage.

The greatest difficulties await the developer when connecting the library to his project. If you don't know how to do this, you can spend a lot of time on this event, which contradicts the very idea of ​​using a ready-made driver. The material is devoted to connecting the standard library to any STM32 family.

Each STM32 family has its own standard peripheral library. This is due to the fact that the periphery itself is different. For example, the periphery of STM32L controllers has a power saving function as one of the tasks, which entails the addition of control functions. A classic example can be considered an ADC, which in STM32L has the ability to turn off hardware, in the absence of a conversion command for a long time - one of the consequences of the energy saving task. ADCs of STM32F family controllers do not have this function. As a matter of fact, due to the hardware difference in the periphery, we have different driver libraries. In addition to the obvious difference in controller functions, there is an improvement in the periphery. So, the peripherals of controllers of families that were released later can be more thoughtful and convenient. For example, the peripherals of the STM32F1 and STM32F2 controllers have differences in control. In the author's opinion, control of STM32F2 peripherals is more convenient. And this is understandable why: the STM32F2 family was released later and this allowed the developers to take into account some of the nuances. Accordingly, for these families - individual peripheral control libraries. The idea behind the above is simple: on the page of the microcontroller you are going to use there is a suitable peripheral library for it.

Despite the difference in peripherals in families, drivers hide 90% of the differences within themselves. For example, the tuning function of the ADC mentioned above looks the same for all families:

void ADC_Init (ADC_Nom, ADC_Param),

where ADC_Nom is the ADC number in the form of ADC1, ADC2, ADC3, etc.

ADC_Param - a pointer to the data structure, how the ADC should be configured (what to start from, how many channels to digitize, whether to perform it cyclically, etc.)

10% of the differences between families, in this example, that will have to be corrected when moving from one STM32 family to another, are hidden in the ADC_Param structure. The number of fields in this structure may vary depending on the family. The general part has the same syntax. Thus, the translation of an application for one STM32 family, written on the basis of standard peripheral libraries to another, is quite simple. In terms of universalization of solutions on microcontrollers, STMicroelectronics is irresistible!

So, we have downloaded the library for the used STM32. What's next? Next, we need to create a project and connect the required files to it. Let's consider creating a project using the IAR Embedded Workbench development environment as an example. We start the development environment and go to the “Project” tab, select the “Create project” item for creating a project:

In the new project that appears, enter the settings by hovering the cursor over the name of the project, pressing the right mouse button and selecting "Options" in the drop-down menu:

RAM and ROM memory areas:

When you click the “Save” button, the environment will offer to write a new controller description file to the project folder. The author recommends creating an individual * .icp file for each project and storing it in the project folder.

If you are going to debug your project in-circuit, which is recommended, then we enter the type of debugger to use:

On the tab of the selected debugger, we indicate the interface for connecting the debugger (in our case, ST-Link is selected) to the controller:



From now on, our project without libraries is ready to be compiled and loaded into the controller. In other environments such as Keil uVision4, Resonance Ride7, etc., you will need to do the same.

If you write the line in the main.c file:

#include "stm32f10x.h" or

#include "stm32f2xx.h" or

#include "stm32f4xx.h" or

#include "stm32l15x.h" or

#include "stm32l10x.h" or

#include "stm32f05x.h"

specifying the location of this file, or copying this file into the project folder, then some memory areas will be associated with the peripheral registers of the corresponding family. The file itself is located in the folder of the standard peripheral library in the section: \ CMSIS \ CM3 \ DeviceSupport \ ST \ STM32F10x (or a similar name for other families). From now on, you replace the peripheral register address as a number with its name. Even if you do not intend to use the functions of the standard library, it is recommended to make such a connection.

If you are going to use interrupts in your project, it is recommended to connect the startup file with the * .s extension, which is located along the path \ CMSIS \ CM3 \ DeviceSupport \ ST \ STM32F10x \ startup \ iar, or similar for other families. It is important to note that the file is different for each environment. Accordingly, if we use IAR EWB, then we must take a file from the IAR folder. This is due to the slightly different syntax of the environments. Therefore, in order for the project to start immediately, STMicroelectronics engineers wrote several options for starting files for several of the most popular development environments. Most STM32 families have one file. The STM32F1 family has several launch files:

  • startup_stm32f10x_cl.s - for STM32F105 / 107 microcontrollers
  • startup_stm32f10x_xl.s - for STM32F101 / STM32F103 microcontrollers 768kb and more
  • startup_stm32f10x_hd.s - for STM32F101 / STM32F103 microcontrollers with 256-512 kb Flash memory
  • startup_stm32f10x_md.s - for STM32F101 / STM32F102 / STM32F103 microcontrollers with 64-128 kb Flash memory
  • startup_stm32f10x_ld.s - for STM32F101 / STM32F102 / STM32F103 microcontrollers with Flash memory less than 64kb
  • startup_stm32f10x_hd_vl.s for STM32F100 microcontrollers with 256-512 kb Flash memory
  • startup_stm32f10x_md_vl.s for STM32F100 microcontrollers with 64-128 kb Flash memory
  • startup_stm32f10x_ld_vl.s for STM32F100 microcontrollers with 32KB Flash memory or less

So, depending on the family, subfamily and development environment, add the launch file to the project:

This is where the microcontroller turns out to be when the program starts. The interrupt sequentially calls the SystemInit () function, and then __iar_program_start. The second function zeroes or writes the predefined values ​​of global variables, after which it switches to the main () user program. The SystemInit () function adjusts the clock of the microcontroller. It is she who gives answers to the questions:

  • Should I switch to Outer Quartz (HSE)?
  • How to multiply frequency from HSI / HSE?
  • Do you need to connect the command download queue?
  • How much delay is required when loading a command (due to the low speed of Flash memory)
  • How to divide the clocking of the peripheral buses?
  • Do you need to place the code in external RAM?

The SystemInit () function can be written manually in your project. If you arrange this function as empty, then the controller will operate on an internal RC oscillator with a frequency of about 8 MHz (depending on the type of family). Option 2 - connect the system_stm32f10x.c file to the project (or similar in name, depending on the type of family used), which is located in the library along the path: Libraries \ CMSIS \ CM3 \ DeviceSupport \ ST \ STM32F10x. This file contains the SystemInit () function. Pay attention to the frequency of the external crystal HSE_VALUE. This parameter is set in the stm32f10x.h header file. The standard value is 8 and 25MHz, depending on the STM32 family. The main task of the SystemInit () function is to switch clocking to an external crystal and multiply this frequency in a certain way. What happens if the HSE_VALUE value is specified as 8MHz, the core must be clocked at 72MHz, but in fact there is a 16MHz crystal on the board? As a result of such incorrect actions, the core will receive a clock of 144 MHz, which may be beyond the guaranteed operation of the system on STM32. Those. when connecting the system_stm32f10x.c file, you will need to specify the HSE_VALUE value. All this means that the files system_stm32f10x.c, system_stm32f10x.h and stm32f10x.h (or similar in name for other families) must be individual for each project. AND

STMicroelectronics engineers have created a Clock Configuration Tool that allows you to correctly configure the system clocking. This is an Excel file that generates the system_stm32xxx.c file (similar in name for a given family of families) after specifying the input and output parameters of the system. Let's see how it works using the STM32F4 family as an example.

Options are an internal RC oscillator, an internal RC oscillator with frequency multiplication, or an external crystal with frequency multiplication. After selecting the clock source, enter the parameters of the desired system configuration, such as the input frequency (when using an external crystal), the core clock frequency, the peripheral bus clock frequency dividers, the operation of the command fetch buffer, and others. By clicking on the "Generate" button, we get a window


Connecting the system_stm32f4xx.c file and its analogs will require connecting one more file of the standard peripheral library. To control the clock, there is a whole set of functions that are called from the system_stm32xxxxxx.c file. These functions are located in the stm32f10x_rcc.c file and its header. Accordingly, when connecting the file system_stm32xxxxxx.c to the project, you need to connect stm32f10x_rcc.c, otherwise the environment linker will report the absence of descriptions of functions with the name RCC_xxxxxxx. The specified file is located in the peripheral library under the path: Libraries \ STM32F10x_StdPeriph_Driver \ src, and its header \ Libraries \ STM32F10x_StdPeriph_Driver \ inc.

The header files of the peripheral driver are connected in the stm32f10x_conf.h file, which is referenced by stm32f10x.h. The stm32f10x_conf.h file is simply a set of header files for specific controller peripheral drivers to be included in the project. Initially, all "#include" headers are marked as comments. Connecting the peripheral header file consists in removing the comment from the corresponding file name. In our case, this is the #include "stm32f10x_rcc.h" line. Obviously, the stm32f10x_conf.h file is individual for each project, since different projects use different peripherals.

And the last thing. It is necessary to specify several directives to the compiler preprocessor and the paths to the header files.



The paths to the header files may be different, depending on the location of the peripheral library relative to the project folder, but the presence of “USE_STDPERIPH_DRIVER” is mandatory when connecting the peripheral drivers of the standard library.

So, we have connected the standard library to the project. Moreover, we have connected one of the standard peripheral drivers to the project, which controls the system clock.

We learned how the structure of the library looks from the inside, now a couple of words about how it looks from the outside.



Thus, the connection of the stm32f10x.h header file in the application entails the connection of other header files and code files. Some of the ones shown in the figure are described above. A few words about the rest. STM32F10x_PPP.x files are peripheral driver files. An example of connecting such a file is shown above, this is RCC - the system clock control peripherals. If we want to connect drivers of other peripherals, then the name of the connected files is obtained by replacing "PPP" with the name of the peripherals, for example, ADC - STM32F10x_ADC.с, or I / O ports STM32F10x_GPIO.с, or DAC - STM32F10x_DAC.с. In general, it is intuitively clear which file needs to be connected when connecting a given peripheral. Files "misc.c", "misc.h" are basically the same STM32F10x_PPP.x, only they control the kernel. For example, setting the interrupt vectors, which is built into the kernel, or managing the SysTick timer, which is part of the kernel. The xxxxxxx_it.c files describe the vectors of the controller's nonmasked interrupts. They can be supplemented with peripheral interrupt vectors. The core_m3.h file describes the CortexM3 core. This core is standardized and can be found in microcontrollers from other manufacturers. For cross-platform universalization, STMicroelectronics worked to create a separate CortexM core library, after which ARM standardized it and extended it to other microcontroller manufacturers. So the transition to STM32 from other manufacturers' controllers with CortexM core will be a little easier.

So, we can connect the standard peripheral library to any STM32 family. Those who have learned to do this will receive a prize: very simple programming of microcontrollers. The library, in addition to drivers in the form of source files, contains many examples of the use of peripherals. For example, consider creating a project involving timer comparison outputs. With the traditional approach, we will carefully study the description of the registers of this periphery. But now we can study the text of the running program. We go to the folder of examples of standard peripherals, which is located along the path ProjectSTM32F10x_StdPeriph_Examples. Here you will find example folders with the name of the peripherals used. We go into the "TIM" folder. Timers in STM32 have many functions and settings, so it is impossible to demonstrate with one example of the controller's capabilities. Therefore, within the specified directory there are many examples of the use of timers. We are interested in the generation of a PWM signal by a timer. We go to the folder "7PWM_Output". Inside there is a description of the program in English and a set of files:

main.c stm32f10x_conf.h stm32f10x_it.h stm32f10x_it.c system_stm32f10x.c

If the project has no interruptions, then the content is completely located in the main.c. file. We copy these files to the project directory. Having compiled the project, we will get a program for STM32 that will configure the timer and I / O ports to generate 7 PWM signals from timer 1. Next, we can adapt the already written code for our task. For example, reduce the number of PWM signals, change the duty cycle, counting direction, etc. The functions and their parameters are well described in the stm32f10x_stdperiph_lib_um.chm file. The names of the functions and their parameters are easily associated with their purpose for those who know a little English. For clarity, we present part of the code of the example taken:

/ * Time Base configuration * / TIM_TimeBaseStructure.TIM_Prescaler = 0; // there is no pre-allocation of counting pulses (16-bit register) TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // direction of the count up TIM_TimeBaseStructure.TIM_Period = TimerPeriod; // count to the TimerPeriod value (constant in the program) TIM_TimeBaseStructure.TIM_ClockDivision = 0; // there is no counting pre-allocation TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; // overflow counter for generating events (not used in the program) TIM_TimeBaseInit (TIM1, & TIM_TimeBaseStructure); // input of TimeBaseStructure values ​​into timer 1 registers (data input to this // variable is above) / * Channel 1, 2,3 and 4 Configuration in PWM mode * / // setting PWM outputs TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; // PWM2 operating mode TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // enable the output of the PWM timer signals TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; // enable complimentary output of the PWM timer TIM_OCInitStructure.TIM_Pulse = Channel1Pulse; // pulse width Channel1Pulse - constant in the program TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; // setting the output polarity TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; // setting the polarity of the complimentary output TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set; // set the safe state of the PWM output TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset; // set the safe state of the complementary PWM output TIM_OC1Init (TIM1, & TIM_OCInitStructure); // input the values ​​of the TIM_OCInitStructure variable into the PWM registers of channel 1 // timer1 TIM_OCInitStructure.TIM_Pulse = Channel2Pulse; // change the pulse width in the OCInitStructure variable and enter it in TIM_OC2Init (TIM1, & TIM_OCInitStructure); // PWM registers of channel 2 of timer1 TIM_OCInitStructure.TIM_Pulse = Channel3Pulse; // change the pulse width in the OCInitStructure variable and enter it in TIM_OC3Init (TIM1, & TIM_OCInitStructure); // PWM registers of channel 3 of timer1 TIM_OCInitStructure.TIM_Pulse = Channel4Pulse; // change the pulse width in the OCInitStructure variable and enter it in TIM_OC4Init (TIM1, & TIM_OCInitStructure); // registers PWM channel 4 timer1 / * TIM1 counter enable * / TIM_Cmd (TIM1, ENABLE); // start timer1 / * TIM1 Main Output Enable * / TIM_CtrlPWMOutputs (TIM1, ENABLE); // enable timer 1 comparison outputs

On the right side, the author left a comment in Russian for each line of the program. If you open the same example in the description of the functions of the stm32f10x_stdperiph_lib_um.chm libraries, we will see that all used function parameters have a link to their own description, where their possible values ​​will be indicated. The functions themselves also have a link to their own description and source code. This is very useful because knowing what the function does, we can trace how it does it, which bits of the peripheral registers and how it affects. This is, firstly, another source of information for mastering the controller, based on the practical use of the controller. Those. you first solve the technical problem, and then study the solution itself. Secondly, this is a field for program optimization for those who are not satisfied with the library in terms of the speed of work and the amount of code.



So, we have already got on our feet, in the sense that everything we need is connected to the conclusions of the microcontroller on the STM32VL Discovery board, we have learned to speak, in the C programming language, it would be time to create a project in the first class.

Writing a program

Once you've finished creating and setting up your project, you can start writing a real program. As is customary for all programmers, the first program written to work on a computer is a program that displays the inscription "HelloWorld", and for all microcontrollers, the first program for a microcontroller blinks the LED. We will not be an exception to this tradition and will write a program that will control the LD3 LED on the STM32VL Discovery board.

After creating an empty project in IAR, it generates minimal program code:

Now our program will always "spin" in a loop while.

In order for us to be able to control the LED, we need to enable clocking of the port to which it is connected and configure the corresponding pin of the microcontroller port to output. As we discussed earlier in the first part, for allowing the clocking of the port WITH the bit responds IOPCEN register RCC_APB2ENR... According to the document " RM0041Referencemanual.pdf"To enable clocking of the port bus WITH required in register RCC_APB2ENR set the bit IOPCEN per unit. So that when this bit is set, we do not reset the others set in this register, we need to apply a logical addition operation (logical "OR") to the current state of the register and then write the resulting value into the contents of the register. In accordance with the structure of the ST library, access to the register value for reading and writing it is made through a pointer to the structure RCC-> APB2 ENR... Thus, recalling the material from the second part, you can write the following code that sets the bit IOPCEN in the register RCC_APB2ENR:

As you can see from the file "stm32f10x.h", the bit value IOPCEN defined as 0x00000010, which corresponds to the fourth bit ( IOPCEN) register APB2ENR and matches the value specified in the datasheet.

Now let's configure the output in the same way. 9 port WITH... To do this, we need to configure this port pin to output in push-pull mode. The register is responsible for setting the port mode for input / output GPIOC_CRH, we have already considered it in, its description is also in the section "7.2.2 Port configuration register high" of the datasheet. To configure the output to the output mode with a maximum speed of 2 MHz, it is necessary in the register GPIOC_CRH install MODE9 to one and reset the bit MODE9 to zero. Bits are responsible for setting the operating mode of the output as the main function with the push-pull output. CNF9 and CNF9 , to configure the required mode of operation, both of these bits must be cleared to zero.

Now the pin of the port to which the LED is connected is set to output, to control the LED, we need to change the state of the port pin by setting the output to a logic one. There are two ways to change the port pin state, the first is to write directly to the port status register the changed contents of the port register, just as we did the port setting. This method is not recommended due to the possibility of a situation in which an incorrect value may be written to the port register. This situation can arise if, during a change in the state of the register, from the moment when the state of the register was read and until the moment when the changed state is written to the register, any peripheral device or interrupt will change the state of this port. Upon completion of the operation to change the state of the register, the value will be written to the register without taking into account the changes that have occurred. Although the likelihood of this situation occurring is very low, it is still worth using another method in which the described situation is excluded. For this, there are two registers in the microcontroller GPIOx_BSRR and GPIOx_BRR... When writing a logical unit to the required bit of the register GPIOx_BRR the corresponding port pin will be reset to logic zero. Register GPIOx_BSRR can both set and reset the state of the port pins, to set the port pin to a logical unit, it is necessary to set the bits BSn, corresponding to the number of the required bit, these bits are located in the lower registers of the byte. To reset the state of the port output to logical zero, it is necessary to write the bits BRn corresponding pins, these bits are located in the high-order bits of the port register.

LD3 LED is connected to pin 9 port WITH... To turn on this LED, we need to apply a logical unit to the corresponding port pin in order to "light up" the LED.

Let's add the code for setting the output of the LED port to our program, and also add a software delay function to reduce the switching frequency of the LED:

// Do not forget to connect the header file with the description of the microcontroller registers

#include "stm32f10x.h"

void Delay ( void);

void Delay ( void)
{
unsigned long i;
for(i = 0; i<2000000; i++);
}

// Our main function

void main ( void)
{


RCC-> APB2ENR | = RCC_APB2ENR_IOPCEN;

// clear the MODE9 bits (reset the MODE9_1 and MODE9_0 bits to zero)
GPIOC-> CRH & = ~ GPIO_CRH_MODE9;

// Set the MODE9_1 bit to configure the output to the output with a speed of 2MHz
GPIOC-> CRH | = GPIO_CRH_MODE9_1;

// clear the CNF bits (configure as general purpose output, balanced (push-pull))
GPIOC-> CRH & = ~ GPIO_CRH_CNF9;

while(1)
{

// Set pin 9 of port C to a logical unit (LED is on)
GPIOC-> BSRR = GPIO_BSRR_BS9;


Delay ();


GPIOC-> BSRR = GPIO_BSRR_BR9;


Delay ();

}
}

You can download the archive with the source code of the program written using direct control of the registers of the microcontroller by following the link.

Our first workable program was written, when writing it, to work and configure the peripherals, we used data from the official datasheet " RM0041Referencemanual.pdf", This source of information about the registers of the microcontroller is the most accurate, but in order to use it you have to re-read a lot of information, which complicates the writing of programs. To facilitate the process of configuring microcontroller peripherals, there are various code generators, the Microxplorer program is presented as an official utility from ST company, but it is still not very functional and for this reason third-party developers have created an alternative program “STM32 Program Code Generator » ... This program allows you to easily get the peripheral configuration code using a convenient, intuitive graphical interface (see Fig. 2).


Rice. 2 Screenshot of STM32 code generator program

As you can see from Figure 2, the LED output setting code generated by the program coincides with the code we wrote earlier.

To run the written program, after compiling the source code, you need to load our program into the microcontroller and see how it is executed.

Debug mode video of LED blinking program

Video of the LED blinking program on the STM32VL Discovery board

Library functions for working with peripherals

To simplify the work with setting up the registers of the microcontroller's peripherals, the ST company has developed libraries, thanks to the use of which, it is not required to read the datasheet so thoroughly, since when using these libraries, the work on writing a program will become closer to writing high-level programs, since everything low-level functions are implemented at the level of library functions. However, one should not completely abandon the use of direct work with the registers of the microcontroller, in view of the fact that library functions require more processor time for their execution, as a result of which their use in time-critical sections of the program is not justified. But still, in most cases, things like peripheral initialization are not runtime critical, and the usability of library functions is preferred.

Now let's write our program using the ST library. The program needs to configure the input / output ports, to use the library functions for configuring ports, you need to connect the header file " stm32f10x_gpio.h"(See Table 1). This file can be connected by uncommenting the corresponding line in the included header configuration file “ stm32f10x_conf.h". At the end of the file " stm32f10x_gpio.h»There is a list of function declarations for working with ports. A detailed description of all available functions can be found in the file " stm32f10x_stdperiph_lib_um.chm», A brief description of the most commonly used is given in Table 2.

Table 2: Description of Basic Port Configuration Functions

Function

Description of the function, passed and returned parameters

GPIO_DeInit (
GPIO_TypeDef * GPIOx)

Sets the values ​​of the GPIOx port settings registers to their default values

GPIO_Init (
GPIO_TypeDef * GPIOx,

Installs the GPIOx port settings registers in accordance with the specified parameters in the GPIO_InitStruct structure

GPIO_StructInit (
GPIO_InitTypeDef * GPIO_InitStruct)

Fills all fields of the GPIO_InitStruct structure with default values

uint8_t GPIO_ReadInputDataBit (
GPIO_TypeDef * GPIOx,
uint16_t GPIO_Pin);

Read the input value of the GPIO_Pin of the GPIOx port

uint16_t GPIO_ReadInputData (
GPIO_TypeDef * GPIOx)

Reading the input values ​​of all pins of the GPIOx port

GPIO_SetBits (
GPIO_TypeDef * GPIOx,
uint16_t GPIO_Pin)

Setting the output value of the GPIO_Pin pin of the GPIOx port to a logical unit

GPIO_ResetBits (
GPIO_TypeDef * GPIOx,
uint16_t GPIO_Pin)

Reset the output value of the GPIO_Pin pin of the GPIOx port to logical zero

GPIO_WriteBit (
GPIO_TypeDef * GPIOx,
uint16_t GPIO_Pin,
BitAction BitVal)

Writing the BitVal value to the GPIO_Pin pin of the GPIOx port

GPIO_Write (
GPIO_TypeDef * GPIOx,
uint16_t PortVal)

Writing the PortVal value to the GPIOx port

As you can see from the description of the functions, as parameters of the port settings, etc., not many different individual parameters are passed to the function, but one structure. Structures are combined data that have some logical relationship. Unlike arrays, structures can contain data of different types. In other words, a structure is a collection of different variables with different types combined into one kind of variable. The variables in this structure are called the fields of the structure, and they are accessed as follows, first the name of the structure is written, then the dot and the name of the structure field (the name of the variable in this structure) are written.

The list of variables included in the structures for the functions of working with ports are described in the same file slightly above the description of the functions. So, for example, the structure “ GPIO_InitTypeDef"Has the following structure:

typedef struct
{

uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define * /

GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef * /

GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef * /

) GPIO_InitTypeDef;

The first field of this structure contains the variable " GPIO_ Pin»Type unsigned short, in this variable it is necessary to write the flags of the numbers of the corresponding pins, for which it is supposed to make the necessary settings. You can configure several pins at once by setting several constants as a parameter using the operator bitwise OR(cm. ). Bitwise OR will "collect" all the ones from the listed constants, and the constants themselves are a mask just intended for such use. Macro definitions of constants are indicated in the same file below.

The second field of the structure " GPIO_InitTypeDef"Sets the maximum possible port output speed. The list of possible values ​​for this field is listed above:

Description of possible values:

  • GPIO_Mode_AIN- analog input (English Analog INput);
  • GPIO_Mode_IN_FLOATING- input without pull-up, dangling (English Input float) in the air
  • GPIO_Mode_IPD- Input Pull-down
  • GPIO_Mode_IPU- Input Pull-up
  • GPIO_Mode_Out_OD- Output Open Drain
  • GPIO_Mode_Out_PP- output by two states (English Output Push-Pull - back and forth)
  • GPIO_Mode_AF_OD- open drain output for alternate functions. Used in cases where the pin should be controlled by peripherals attached to this port pin (for example, Tx USART1 pin, etc.)
  • GPIO_Mode_AF_PP- the same, but with two states

Similarly, you can see the structure of variables of other structures required to work with library functions.

To work with structures, just like variables, you must declare and assign them a unique name, after which you can refer to the fields of the declared structure by the name assigned to it.

// Declare the structure

/*
Before starting filling in the structure fields, it is recommended to initialize the contents of the structure with the default data, this is done in order to prevent writing incorrect data if, for some reason, not all the structure fields have been filled.

To pass the values ​​of a structure to a function, you must put the & symbol in front of the structure name. This symbol tells the compiler that it is necessary to pass to the function not the values ​​contained in the structure themselves, but the memory address at which these values ​​are located. This is done in order to reduce the number of necessary processor actions for copying the contents of the structure, and also allows you to save RAM. Thus, instead of passing a set of bytes contained in the structure to the function, only one will be transferred containing the address of the structure.
*/

/ * Write in the GPIO_Pin field of the GPIO_Init_struct structure the port pin number, which we will configure further * /

GPIO_Init_struct.GPIO_Pin = GPIO_Pin_9;

/ * Similarly, fill in the GPIO_Speed ​​field * /

/*
After we have filled in the required fields of the structure, this structure must be passed to a function that will make the necessary entry into the corresponding registers. In addition to the structure with the settings of this function, it is also necessary to pass the name of the port for which the settings are intended.
*/

Almost all peripherals are configured in approximately the same way, the differences are only in the parameters and commands specific to each device.

Now let's write our LED blinking program using only library functions.

// Do not forget to connect the header file with the description of the microcontroller registers

#include "stm32f10x.h"
#include "stm32f10x_conf.h"

// declare a program delay function

void Delay ( void);

// the program delay function itself

void Delay ( void)
{
unsigned long i;
for(i = 0; i<2000000; i++);
}

// Our main function

void main ( void)
{

// Enable clock bus port C
RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOC, ENABLE);

// Declare the structure for configuring the port
GPIO_InitTypeDef GPIO_Init_struct;

// Fill the structure with initial values
GPIO_StructInit (& GPIO_Init_struct);

/ * Write in the GPIO_Pin field of the GPIO_Init_struct structure the port pin number, which we will configure further * /
GPIO_Init_struct.GPIO_Pin = GPIO_Pin_9;

// Similarly, fill in the fields GPIO_Speed ​​and GPIO_Mode
GPIO_Init_struct.GPIO_Speed ​​= GPIO_Speed_2MHz;
GPIO_Init_struct.GPIO_Mode = GPIO_Mode_Out_PP;

// Pass the filled structure to perform actions to set up registers
GPIO_Init (GPIOC, & GPIO_Init_struct);

// Our main infinite loop
while(1)
{
// Set pin 9 of port C to a logical unit (LED is on)
GPIO_SetBits (GPIOC, GPIO_Pin_9);

// Add a software delay to keep the LED on for a while
Delay ();

// Reset the state of pin 9 of port C to logical zero
GPIO_ResetBits (GPIOC, GPIO_Pin_9);

// Add again a software delay
Delay ();
}
}

link.

From the above example, it can be seen that the use of library functions for working with peripherals allows you to bring writing programs for the microcontroller closer to object-oriented programming, and also reduces the need for frequent access to the datasheet to read the description of the microcontroller registers, but the use of library functions requires a higher knowledge of the programming language ... In view of this, for people who are not particularly familiar with programming, a simpler option for writing programs will be a way of writing programs without using library functions, with direct access to the registers of the microcontroller. For those who know the programming language well, but are poorly versed in microcontrollers, in particular STM32, the use of library functions greatly simplifies the process of writing programs.

This circumstance, as well as the fact that ST has taken care of a high degree of compatibility, both in hardware and software, of its various microcontrollers, contributes to their easier study, in view of the fact that there is no need to delve into the structural features of various controllers the STM32 series and allows you to choose any of the microcontrollers available in the STM32 line as a microcontroller for study.

Interrupt handler

Microcontrollers have one remarkable ability - to stop the execution of the main program at a certain event, and go on to execute a special subroutine - interrupt handler... Interrupt sources can be both external events - interrupts for receiving / transmitting data through any data transfer interface, or changing the output state, and internal - timer overflow, etc. The list of possible interrupt sources for STM32 series microcontrollers is given in datasheet " RM0041 Reference manual" In chapter " 8 Interrupts and events».

Since the interrupt handler is also a function, then it will be written as a normal function, but so that the compiler knows that this function is a handler for a specific interrupt, you should choose the predefined names as the function name, to which redirects of interrupt vectors are indicated. A list of the names of these functions with a short description can be found in the assembler file " startup_stm32f10x_md_vl.s". One interrupt handler can have several sources of interrupts, for example, the interrupt handler function " USART1_IRQHandler"Can be called in case of end of reception and end of transmission of a byte, etc.

To start working with interrupts, you need to configure and initialize the NVIC interrupt controller. In the Cortex M3 architecture, each interrupt can be assigned its own priority group for cases when several interrupts occur simultaneously. Then you should configure the interrupt source.

The NVIC_IRQChannel field indicates which interrupt we want to configure. The constant USART1_IRQn denotes the channel responsible for interrupts associated with USART1. It is defined in the file " stm32f10x.h", Other similar constants are also defined there.

The next two fields indicate the priority of the interrupts (the maximum values ​​of these two parameters are determined by the selected priority group). The last field actually enables the use of an interrupt.

Into function NVIC_Init, as well as when setting up ports, a pointer to the structure is passed to apply the settings made and write them to the corresponding registers of the microcontroller.

Now, in the module settings, you need to set the parameters for which this module will generate an interrupt. First, you need to turn on the interrupt, this is done by calling the function name_ITConfig () which is located in the header file of the peripheral.

// Enable interrupts at the end of the byte transfer on USART1
USART_ITConfig (USART1, USART_IT_TXE, ENABLE);

// Enable interrupts at the end of receiving a byte by USART1
USART_ITConfig (USART1, USART_IT_RXNE, ENABLE);

The description of the parameters passed to the function can be found in the source code file of the peripheral device, just above the location of the function itself. This function enables or disables interrupts for various events from the specified peripheral module. When this function is executed, the microcontroller will be able to generate interrupts for the events we need.

After we get into the interrupt handling function, we need to check from which event the interrupt occurred, and then clear the cocked flag, otherwise, upon exiting the interrupt, the microcontroller will decide that we have not processed the interrupt, since the interrupt flag is still set.

To perform various, small, repetitive actions with a precise period, microcontrollers with a Cortex-M3 core have a specially designed system timer. The functions of this timer include only an interrupt call at strictly specified time intervals. Typically, in the interrupt called by this timer, code is placed to measure the duration of various processes. The declaration of the function for setting the timer is located in the file “ core_ cm3. h". The argument passed to the function is the number of system bus ticks between the intervals when the system timer interrupt handler is called.

SysTick_Config (clk);

Now that we have dealt with interrupts, we will rewrite our program using the system timer as a time-setting element. Since the timer " SysTick"Is systemic and it can be used by various functional blocks of our program, it would be reasonable to move the interrupt handling function from the system timer to a separate file, from this function to call functions for each function block separately.

An example of a file "main.c" of a program for flashing an LED using an interrupt:

// Connect the header file with the description of the microcontroller registers

#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "main.h"

unsigned int LED_timer;

// Function called from the system timer interrupt handler function

void SysTick_Timer_main ( void)
{
// If the variable LED_timer has not yet reached 0,
if(LED_timer)
{
// Check its value, if it is more than 1500, turn on the LED
if(LED_timer> 1500) GPIOC-> BSRR = GPIO_BSRR_BS9;

// otherwise, if less than or equal to 1500 then turn off
else GPIOC-> BSRR = GPIO_BSRR_BR9;

// Decrement the LED_timer variable
LED_timer--;
}

// If the value of the variable reaches zero, set the new value to 2000
else LED_timer = 2000;
}

// Our main function

void main ( void)
{

// Enable clock bus port C
RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOC, ENABLE);

// Declare the structure for configuring the port
GPIO_InitTypeDef GPIO_Init_struct;

// Fill the structure with initial values
GPIO_StructInit (& GPIO_Init_struct);

/ * Write in the GPIO_Pin field of the GPIO_Init_struct structure the port pin number, which we will configure further * /
GPIO_Init_struct.GPIO_Pin = GPIO_Pin_9;

// Similarly, fill in the fields GPIO_Speed ​​and GPIO_Mode
GPIO_Init_struct.GPIO_Speed ​​= GPIO_Speed_2MHz;
GPIO_Init_struct.GPIO_Mode = GPIO_Mode_Out_PP;

// Pass the filled structure to perform actions to set up registers
GPIO_Init (GPIOC, & GPIO_Init_struct);

// select the priority group for interrupts
NVIC_PriorityGroupConfig (NVIC_PriorityGroup_0);

// Configure the operation of the system timer with an interval of 1ms
SysTick_Config (24000000/1000);

// Our main infinite loop
while(1)
{
// This time it's empty, all LED control occurs in interrupts
}
}

Part of the source code in the "stm32f10x_it.c" file:


#include "main.h"

/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/

void SysTick_Handler ( void)
{
SysTick_Timer_main ();
}

An example of a working draft of a LED blinking program using an interrupt can be downloaded from the link.

This concludes my story about the basics of developing programs for the STM32 microcontroller. I have provided all the information you need to be able to further self-study STM32 microcontrollers. The provided material is only a starting point, since a full description of working with microcontrollers cannot be described within the framework of any article. In addition, it is impossible to study microcontrollers without gaining practical experience, and real experience comes gradually over the years of work, experiments, with the accumulation of various software and hardware developments, as well as reading various articles and documentation on microcontrollers. But do not let this scare you, since the information provided in the article is quite enough to create your first device on a microcontroller, and you can acquire further knowledge and experience on your own, developing more and more complex and better devices each time and improving your skills.

I hope I could interest you in studying microcontrollers and developing devices on them, and my works will be useful and interesting to you.

Up to this point, we have used the standard kernel library - CMSIS. To configure a port to the desired mode of operation, we had to turn to in order to find the register responsible for a certain function, as well as look for other information related to this process across a large document. Things will get even more excruciating and mundane when we get to work with the timer or ADC. The number of registers there is much larger than that of the I / O ports. Manual configuration takes a lot of time and increases the chance of making a mistake. Therefore, many people prefer to work with the standard peripheral library - StdPeriph. What does it give? It's simple - the level of abstraction rises, you don't need to dig into the documentation and think about most of the registers. In this library, all operating modes and parameters of the MC peripherals are described in the form of structures. Now, to configure a peripheral device, you just need to call the device initialization function with a filled structure.

Below is a picture with a schematic representation of the levels of abstraction.

We worked with CMSIS (which is "closest" to the kernel) to show how the microcontroller works. The next step is the standard library, which we will learn how to use now. Next are the device drivers. They are understood as * .c \ * .h -files that provide a convenient software interface for controlling any device. So, for example, in this course, we will provide you with drivers for the max7219 chip and the esp8266 WiFi module.

A standard project will include the following files:


First, of course, these are the CMSIS files that allow the standard library to work with the kernel, we have already talked about them. Second, the standard library files. And third, custom files.

The library files can be found on the page dedicated to the target MK (for us it is stm32f10x4), in the section Design Resources(in the CooCox IDE, these files are downloaded from the development environment repository). Each peripheral corresponds to two files - header (* .h) and source code (* .c). A detailed description can be found in the support file, which is in the archive with the library on the site.

  • stm32f10x_conf.h - library configuration file. The user can enable or disable modules.
  • stm32f10x_ppp.h - peripheral header file. Ppp can be gpio or adc instead.
  • stm32f10x_ppp.c is a peripheral device driver written in C language.
  • stm32f10x_it.h - header file that includes all possible interrupt handlers (their prototypes).
  • stm32f10x_it.c is a template source file containing interrupt service routine (ISR) for Cortex M3 exceptions. The user can add their own ISRs for the peripherals used.

The standard library and peripherals have a convention for naming functions and notation.

  • PPP is an acronym for peripherals such as ADC.
  • System files, header files, and source files - all start with stm32f10x_.
  • Constants used in one file are defined in this file. Constants used in more than one file are defined in the header files. All constants in the peripheral library are most often written in UPPERCASE.
  • Registers are treated as constants and are also named in CAPITAL letters.
  • Peripheral function names contain an acronym such as USART_SendData ().
  • To configure each peripheral device, the PPP_InitTypeDef structure is used, which is passed to the PPP_Init () function.
  • To deinitialize (set the default value), you can use the PPP_DeInit () function.
  • The function to enable or disable peripherals is called PPP_Cmd ().
  • The interrupt enable / disable function is named PPP_ITConfig.

For a complete list, see the library support file again. Now let's rewrite the LED blinking using the standard peripheral library!

Before starting work, let's look into the stm32f10x.h file and find the line:

#define USE_STDPERIPH_DRIVER

If you are setting up a project from scratch using the library files from the downloaded archive, then you will need to uncomment this line. It will allow you to use the standard library. This definition (macro) will command the preprocessor to include the stm32f10x_conf.h file:

#ifdef USE_STDPERIPH_DRIVER #include "stm32f10x_conf.h" #endif

Modules are included in this file. If you only need specific ones, disable the rest, this will save compilation time. We, as you might have guessed, need RTC and GPIO modules (however, in the future, we will also need _bkp.h, _flash, _pwr.h, _rtc.h, _spi.h, _tim.h, _usart.h):

#include "stm32f10x_flash.h" // for init_pll () #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h"

Like last time, first you need to enable clocking of port B. This is done by the function declared in stm32f10x_rcc.h:

Void RCC_APB2PeriphClockCmd (uint32_t RCC_APB2Periph, FunctionalState NewState);

The FunctionalState enumeration is defined in stm32f10x.h:

Typedef enum (DISABLE = 0, ENABLE =! DISABLE) FunctionalState;

Let's declare a structure for setting up our leg (you can find it in the stm32f10x_gpio.h file):

GPIO_InitTypeDef LED;

Now we have to fill it in. Let's take a look at the contents of this structure:

Typedef struct (uint16_t GPIO_Pin; GPIOSpeed_TypeDef GPIO_Speed; GPIOMode_TypeDef GPIO_Mode;) GPIO_InitTypeDef;

All necessary enumerations and constants can be found in the same file. Then the rewritten init_leds () function will take the following form:

Void led_init () (// Turn on clocking RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOB, ENABLE); // We declare the structure and fill it GPIO_InitTypeDef LED; LED.GPIO_Pin = GPIO_Pin_0; LED.GPIO_Pin_0; LED.GPIO_Pin_0; LED.GPIO_Pin_0; GPIOB, & LED);)

Let's rewrite the main () function:

Int main (void) (led_init (); while (1) (GPIO_SetBits (GPIOB, GPIO_Pin_0); delay (10000000); GPIO_ResetBits (GPIOB, GPIO_Pin_0); delay (10000000);))

The main thing is to get a feel for the initialization order: turn on the peripheral clocking, declare the structure, fill in the structure, and call the initialization method. Other peripheral devices are usually configured in a similar manner.

Software required for development. In this article, I will show you how to properly configure and link it. All commercial environments such as IAR EWARM or Keil uVision usually perform this integration themselves, but in our case everything will have to be configured manually, spending a lot of time on it. The advantage is that you have a chance to understand how it all works from the inside, and in the future you can flexibly customize everything for yourself. Before starting the setup, consider the structure of the environment in which we will work:

Eclipse will be used for convenient editing of function implementation files ( .c), header files ( .h) as well as assembly files ( .S). By "convenient" I mean the use of code completion, syntax highlighting, refactoring, navigation through functions and their prototypes. The files are automatically fed to the correct compilers that generate the object code (in files .o). So far, this code does not contain absolute addresses of variables and functions, and therefore is not suitable for execution. The resulting object files are collected together by a linker. To know which parts of the address space to use, the builder uses a special file ( .ld), which is called a linker script. It usually contains the definition of section addresses and their sizes (code section displayed on flash, variable section displayed on RAM, etc.).

In the end, the linker generates an .elf file (Executable and Linkable Format), which contains, in addition to instructions and data, debugging information (Debugging information) used by the debugger. For regular firmware vsprog, this format is not suitable, since this requires a more primitive memory image file (for example, Intel HEX - .hex). There is also a tool from the Sourcery CodeBench set (arm-none-eabi-objcopy) to generate it, and they integrate perfectly into eclipse using the installed ARM plugin.

To carry out the debugging itself, three programs are used:

  1. eclipse itself, which allows the programmer to "visually" use debugging, walk through lines, hover the mouse cursor over variables to view their values, and other conveniences
  2. arm-none-eabi-gdb - GDB client - debugger, which is secretly controlled by eclips (via stdin) as a reaction to the actions specified in paragraph 1. In turn, GDB connects to the OpenOCD Debug server, and all incoming commands are translated by the GDB debugger into commands that OpenOCD understands. GDB channel<->OpenOCD is implemented over the TCP protocol.
  3. OpenOCD is a debug server that can communicate directly with the programmer. It runs in front of the client and waits for a TCP connection.

This scheme may seem very useless to you: why use the client and server separately and perform one more time translation of commands, if all this could be done with one debugger? The fact is that such an architecture theoretically makes it possible to conveniently interchange the client and the server. For example, if you need to use another programmer instead of versaloon, which will not support OpenOCD, but will support another special Debug server (for example, texane / stlink for the stlink programmer - which is in the STM32VLDiscovery debug board), then instead of running OpenOCD you will simply run the required server and everything should work, without any additional gestures. At the same time, the opposite situation is possible: let's say you wanted to use the IAR EWARM environment together with versaloon instead of the Eclipse + CodeBench bundle. The IAR has its own built-in Debug client, which will successfully connect to OpenOCD and control it, as well as receive the necessary data in response. However, all this sometimes remains only in theory, since the standards for communication between the client and the server are not strictly regulated, and may differ in places, however, the configurations I indicated with st-link + eclipse and IAR + versaloon were successful for me.

Typically, the client and server run on the same machine and connect to the server at the address localhost: 3333(For openocd), or localhost: 4242(for texane / stlink st-util). But no one bothers to open port 3333 or 4242 (and forward this port on the router to the external network) and your colleagues from another city will be able to connect and debug your piece of hardware. This trick is often used by embedders working on remote objects, access to which is limited.

Let's get started

Launch eclipse and select File-> New-> C Project, select the project type ARM Linux GCC (Sorcery G ++ Lite) and the name "stm32_ld_vl" (If you have STV32VLDiscovery, it would be more logical to name it "stm32_md_vl"):

Click Finish, minimize or close the Welcome window. So, the project is created, and the stm32_ld_vl folder should appear in your workspace. Now it needs to be filled with the necessary libraries.

As you understood from the name of the project, I will create a project for the ruler view low-density value line(LD_VL). To create a project for other microcontrollers, you must replace all files and defines in the name of which there is _LD_VL (or_ld_vl) to the ones you need, in accordance with the table:

Ruler view Designation Microcontrollers (x may vary)
Low-density value line _LD_VL STM32F100x4 STM32F100x6
Low-density _LD STM32F101x4 STM32F101x6
STM32F102x4 STM32F102x6
STM32F103x4 STM32F103x6
Medium-density value line _MD_VL STM32F100x8 STM32F100xB
Medium-density
_MD
STM32F101x8 STM32F101xB
STM32F102x8 STM32F102xB
STM32F103x8 STM32F103xB
High density Value line _HD_VL STM32F100xC STM32F100xD STM32F100xE
High density _HD STM32F101xC STM32F101xD STM32F101xE
STM32F103xC STM32F103xD STM32F103xE
XL-density _XL STM32F101xF STM32F101xG
STM32F103xF STM32F103xG
Connectivity line _CL STM32F105xx and STM32F107xx

To understand the logic of the table, you must be familiar with STM32 labeling. That is, if you have VLDiscovery, then you will have to replace everything related to _LD_VL with _MD_VL, since the STM32F100RB chip, which belongs to the Medium-density value line, is soldered in the discovery.

Adding the CMSIS and STM32F10x Standard Peripherals Library to the project

CMSIS(Cortex Microcontroller Software Interface Standard) is a standardized library for working with Cortex microcontrollers that implements the HAL (Hardware Abstraction Layer) level, that is, it allows you to abstract from the details of working with registers, finding register addresses by datasheets, etc. The library is a collection of C and Asm sources. The core part of the library is the same for all Cortexes (be it ST, NXP, ATMEL, TI or whatever), and is developed by ARM. The other part of the library is responsible for peripherals, which are naturally different for different manufacturers. Therefore, in the end, the full library is still distributed by the manufacturer, although the core part can still be downloaded separately from the ARM website. The library contains address definitions, the clock generator initialization code (conveniently configurable by define-s), and everything else that saves the programmer from manually entering into his projects the definition of addresses of all peripheral registers and the definition of the bits of the values ​​of these registers.

But the guys from ST went further. Besides CMSIS support, they provide another library for STM32F10x called Standard Peripherals Library(SPL), which can be used in addition to CMSIS. The library provides faster and more convenient access to peripherals, and also controls (in some cases) the correctness of work with peripherals. Therefore, this library is often called a set of drivers for peripheral modules. It comes with a pack of examples, categorized for different peripherals. There is also a library not only for STM32F10x, but also for other series.

You can download the entire SPL + CMSIS version 3.5 here: STM32F10x_StdPeriph_Lib_V3.5.0 or on the ST website. Unzip the archive. Create CMSIS and SPL folders in the project folder and start copying files to your project:

What to copy

Where to copy (considering,
that's the project folder stm32_ld_vl)

File Description
Libraries / CMSIS / CM3 /
CoreSupport / core_cm3.c
stm32_ld_vl / CMSIS / core_cm3.c Description of the Cortex M3 core
Libraries / CMSIS / CM3 /
CoreSupport / core_cm3.h
stm32_ld_vl / CMSIS / core_cm3.h Kernel description headers

ST / STM32F10x / system_stm32f10x.c
stm32_ld_vl / CMSIS /system_stm32f10x.c Initialization functions and
clock control
Libraries / CMSIS / CM3 / DeviceSupport /
ST / STM32F10x / system_stm32f10x.h
stm32_ld_vl / CMSIS /system_stm32f10x.h Headings to these functions
Libraries / CMSIS / CM3 / DeviceSupport /
ST / STM32F10x / stm32f10x.h
stm32_ld_vl / CMSIS /stm32f10x.h Basic description of the periphery
Libraries / CMSIS / CM3 / DeviceSupport /
ST / STM32F10x / startup / gcc_ride7 /
startup_stm32f10x_ld_vl.s
stm32_ld_vl / CMSIS /startup_stm32f10x_ld_vl.S
(!!! Attention file extension CAPITAL S)
Vector table file
interrupts and init-s on asm
Project / STM32F10x_StdPeriph_Template /
stm32f10x_conf.h
stm32_ld_vl / CMSIS / stm32f10x_conf.h Template for customization
peripheral modules

inc / *
stm32_ld_vl / SPL / inc / * SPL Headers
Libraries / STM32F10x_StdPeriph_Driver /
src / *
stm32_ld_vl / SPL / src / * SPL implementation

After copying, go to Eclipse and do Refresh in the context menu of the project. As a result, in the Project Explorer you should get the same structure as in the picture on the right.

You may have noticed that in the Libraries / CMSIS / CM3 / DeviceSupport / ST / STM32F10x / startup / folder there are folders for different IDEs (different IDEs use different compilers). I chose IDE Ride7 because it uses the GNU Tools for ARM Embedded compiler, which is compatible with our Sourcery CodeBench.

The entire library is configured using a preprocessor (using define-s), this will allow solving all the necessary branches at the compilation stage (or rather, even before it) and avoiding the load in the controller itself (which would be observed if the configuration was performed in RunTime). For example, all equipment is different for different rulers, and therefore in order for the library to "find out" which ruler you want to use, you are asked to uncomment in the file stm32f10x.h one of the defines (corresponding to your ruler):

/ * #define STM32F10X_LD * / / *!< STM32F10X_LD: STM32 Low density devices */
/ * #define STM32F10X_LD_VL * / / *!< STM32F10X_LD_VL: STM32 Low density Value Line devices */
/ * #define STM32F10X_MD * / / *!< STM32F10X_MD: STM32 Medium density devices */

Etc...

But I do not recommend doing this. We will not touch the library files for now, but we will make the define later using the compiler settings in Eclipse. And then Eсlipse will call the compiler with the key -D STM32F10X_LD_VL, which for the preprocessor is absolutely equivalent to the situation if you uncommented "#define STM32F10X_LD_VL"... Thus, we will not change the code, as a result, if you wish, someday you will be able to move the library into a separate directory and not copy it to the folder of each new project.

Linker script

In the context menu of the project, select New-> File-> Other-> General-> File, Next. Select the root folder of the project (stm32_ld_vl). Enter the file name "stm32f100c4.ld" (or "stm32f100rb.ld" for discovery). Now copy and paste into eclipse:

ENTRY (Reset_Handler) MEMORY (FLASH (rx): ORIGIN = 0x08000000, LENGTH = 16K RAM (xrw): ORIGIN = 0x20000000, LENGTH = 4K) _estack = ORIGIN (RAM) + LENGTH (RAM); MIN_HEAP_SIZE = 0; MIN_STACK_SIZE = 256; SECTIONS (/ * Interrupt vector table * / .isr_vector: (. = ALIGN (4); KEEP (* (. Isr_vector)). = ALIGN (4);)> FLASH / * The program code and other data goes into FLASH * / .text: (. = ALIGN (4); / * Code * / * (. text) * (. text *) / * Constants * / * (. rodata) * (. rodata *) / * ARM-> Thumb and Thumb-> ARM glue code * / * (. glue_7) * (. glue_7t) KEEP (* (. init)) KEEP (* (. fini)). = ALIGN (4); _etext =.;)> FLASH. ARM.extab: (* (. ARM.extab * .gnu.linkonce.armextab. *))> FLASH .ARM: (__exidx_start =.; * (. ARM.exidx *) __exidx_end =.;)> FLASH .ARM. attributes: (* (. ARM.attributes))> FLASH .preinit_array: (PROVIDE_HIDDEN (__preinit_array_start =.); KEEP (* (. preinit_array *)) PROVIDE_HIDDEN (__preinit_array_end =.);)> FLASH: .init_array_end =.);)> FLASH: .init_array_end =.);)> FLASH: .init_array_end =.); KEEP (* (SORT (.init_array. *))) KEEP (* (. Init_array *)) PROVIDE_HIDDEN (__init_array_end =.);)> FLASH .fini_array: (PROVIDE_HIDDEN (__fini_array_start = *.); KEEP (.fini_array *)) KEEP (* (SORT (.fini_array. *))) PROVIDE_HIDDEN (__fini_array_end =.); )> FLASH _sidata =.; / * Initialized data * / .data: AT (_sidata) (. = ALIGN (4); _sdata =.; / * Create a global symbol at data start * / * (. Data) * (. Data *). = ALIGN (4); _edata =.; / * Define a global symbol at data end * /)> RAM / * Uninitialized data * /. = ALIGN (4); .bss: (/ * This is used by the startup in order to initialize the .bss secion * / _sbss =.; / * define a global symbol at bss start * / __bss_start__ = _sbss; * (. bss) * (. bss *) * (COMMON). = ALIGN (4); _ebss =.; / * Define a global symbol at bss end * / __bss_end__ = _ebss;)> RAM PROVIDE (end = _ebss); PROVIDE (_end = _ebss); PROVIDE (__ HEAP_START = _ebss); / * User_heap_stack section, used to check that there is enough RAM left * / ._user_heap_stack: (. = ALIGN (4);. =. + MIN_HEAP_SIZE;. =. + MIN_STACK_SIZE;. = ALIGN (4);)> RAM / DISCARD /: (libc.a (*) libm.a (*) libgcc.a (*)))

Given l Inker script will be designed specifically for the STM32F100C4 controller (which has 16 KB of flash and 4 KB of RAM), if you have a different one, you will have to change the LENGTH parameters for the FLASH and RAM areas at the beginning of the file (for STM32F100RB, which is in Discovery: Flash 128K and RAM 8K).

We save the file.

Build Setup (C / C ++ Build)

Go to Project-> Properties-> C / C ++ Build-> Settings-> Tool Settings, and start configuring build tools:

1) Target Precessor

We choose which Cortex core the compiler will work for.

  • Processor: cortex-m3

2) ARM Sourcery Linux GCC C Compiler -> Preprocessor

Add two define-a by passing them through the -D switch to the compiler.

  • STM32F10X_LD_VL - defines a ruler (I wrote about this define above)
  • USE_STDPERIPH_DRIVER - instructs the CMSIS library that it should use the SPL driver

3) ARM Sourcery Linux GCC C Compiler -> Directories

Add paths to the includ libraries.

  • "$ (workspace_loc: / $ (ProjName) / CMSIS)"
  • "$ (workspace_loc: / $ (ProjName) / SPL / inc)"

Now, for example, if we write:

#include "stm32f10x.h

Then the compiler must first look for the file stm32f10x.h in the project directory (he always does this), he will not find it there and will start searching in the CMSIS folder, the path to which we indicated, well, he will find it.

4) ARM Sourcery Linux GCC C Compiler -> Optimization

Turn on feature and data optimization

  • -ffunction-sections
  • -fdata-sections

As a result, all functions and data elements will be placed in separate sections, and the collector will be able to understand which sections are not used and will simply throw them out.

5) ARM Sourcery Linux GCC C Compiler -> General

Add the path to our linker script: "$ (workspace_loc: / $ (ProjName) /stm32f100c4.ld)" (or whatever you call it).

And set the options:

  • Do not use standard start files - do not use standard start files.
  • Remove unused sections - remove unused sections

That's it, the setup is complete. OK.

Since the creation of the project, we have done a lot, and there are a few things that Eclipse may have missed, so we need to tell him to revise the project file structure. To do this, from the context menu of the project you need to do Index -> rebuild.

Hello LEDs on STM32

It's time to create the main project file: File -> New -> C / C ++ -> Source File. Next. Source file name: main.c.

Copy and paste the following into the file:

#include "stm32f10x.h" uint8_t i = 0; int main (void) (RCC-> APB2ENR | = RCC_APB2ENR_IOPBEN; // Enable PORTB Periph clock RCC-> APB1ENR | = RCC_APB1ENR_TIM2EN; // Enable TIM2 Periph clock // Disable JTAG for release LED PIN RCC-> APB2ENB2 | = RRCCEN AFIO-> MAPR | = AFIO_MAPR_SWJ_CFG_JTAGDISABLE; // Clear PB4 and PB5 control register bits GPIOB-> CRL & = ~ (GPIO_CRL_MODE4 | GPIO_CRL_CNF4 | GPIO_CRL_MODE5 | GPIO_Cull_output max as PBF5) 10Mhz GPIOB-> CRL | = GPIO_CRL_MODE4_0 | GPIO_CRL_MODE5_0; TIM2-> PSC = SystemCoreClock / 1000 - 1; // 1000 tick / sec TIM2-> ARR = 1000; // 1 Interrupt / 1 sec TIM2-> DIER | = TIM_DIER_UIE; // Enable tim2 interrupt TIM2-> CR1 | = TIM_CR1_CEN; // Start count NVIC_EnableIRQ (TIM2_IRQn); // Enable IRQ while (1); // Infinity loop) void TIM2_IRQHandler (void) (TIM2-> SR & = ~ TIM_SR_UIF ; // Clean UIF Flag if (1 == (i ++ & 0x1)) (GPIOB-> BSRR = GPIO_BSRR_BS4; // Set PB4 bit GPIOB-> BSRR = GPIO_BSRR_BR5; // Reset PB5 bit) else (GPIOB-> BSRR = GPIO_BSRR_B S5; // Set PB5 bit GPIOB-> BSRR = GPIO_BSRR_BR4; // Reset PB4 bit))

Although we included the SPL libraries, it was not used here. All calls to fields like RCC-> APB2ENR are fully described in CMSIS.

You can run Project -> Build All. If everything worked out, then the stm32_ld_vl.hex file should appear in the Debug folder of the project. It was automatically generated from elf by built-in tools. We flash the file and see how the LEDs flash with a frequency of once per second:

Vsprog -sstm32f1 -ms -oe -owf -I /home/user/workspace/stm32_ld_vl/Debug/stm32_ld_vl.hex -V "tvcc.set 3300"

Naturally, instead of / home / user / workspace / you must enter your path to the workspace.

For STM32VLDiscovery

The code is slightly different from the one I gave above for my debug board. The difference lies in the pins on which the LEDs "hang". If in my board they were PB4 and PB5, then in Discovery they were PC8 and PC9.

#include "stm32f10x.h" uint8_t i = 0; int main (void) (RCC-> APB2ENR | = RCC_APB2ENR_IOPCEN; // Enable PORTC Periph clock RCC-> APB1ENR | = RCC_APB1ENR_TIM2EN; // Enable TIM2 Periph clock // Clear PC8 and PC9 control register bits GPIOC-> CRH & = ~ (GPIO_CRH_MODE8 | GPIO_CRH_CNF8 | GPIO_CRH_MODE9 | GPIO_CRH_CNF9); // Configure PC8 and PC9 as Push Pull output at max 10Mhz GPIOC-> CRH | = GPIO_CRH_MODE8_0 | GPIO_CRH_MODE8 sec - tick - PIO_CRH> 1000 TIMCore TIM2-> ARR = 1000; // 1 Interrupt / sec (1000/100) TIM2-> DIER | = TIM_DIER_UIE; // Enable tim2 interrupt TIM2-> CR1 | = TIM_CR1_CEN; // Start count NVIC_EnableIRQ (TIM2_IRQn); // Enable IRQ while (1); // Infinity loop) void TIM2_IRQHandler (void) (TIM2-> SR & = ~ TIM_SR_UIF; // Clean UIF Flag if (1 == (i ++ & 0x1)) (GPIOC-> BSRR = GPIO_BSRR_BS8 ; // Set PC8 bit GPIOC-> BSRR = GPIO_BSRR_BR9; // Reset PC9 bit) else (GPIOC-> BSRR = GPIO_BSRR_BS9; // Set PC9 bit GPIOC-> BSRR = GPIO_BSRR_BR8; // Reset PC8 bit))

Under Windows, you can flash the resulting hex (/workspace/stm32_md_vl/Debug/stm32_md_vl.hex) using a utility from ST.

Well, under linux, the st-flash utility. BUT!!! The utility does not hack the hex of the Intel HEX format (which is generated by default), so it is extremely important to select the binary format in the settings for creating a Flash image:

The file extension will not change (hex will remain as it was), but the file format will change. And only after that you can execute:

St-flash write v1 /home/user/workspace/stm32_md_vl/Debug/stm32_md_vl.hex 0x08000000

By the way, about the extension and format: usually binary files are labeled with the .bin extension, while Intel HEX files are named with the .hex extension. The difference in these two formats is rather technical than functional: the binary format contains just bytes of instructions and data, which will simply be written to the controller by the programmer "as is". IntelHEX, on the other hand, has not a binary format, but a textual one: exactly the same bytes are split into 4 bits and are represented character by character in ASCII format, and only the characters 0-9, AF are used (bin and hex are number systems with multiple bases, that is, 4 bits in bin can be represented as a single digit in hex). So the ihex format is more than 2 times the size of a regular binary file (every 4 bits are replaced by a byte + line breaks for easy reading), but it can be read in a regular text editor. Therefore, if you are going to send this file to someone, or use it in other programming programs, then it is advisable to rename it to stm32_md_vl.bin so as not to mislead those who will look at its name.

So we have configured the firmware build for stm32. Next time I will tell you how

When you just start programming microcontrollers or haven’t done programming for a long time, it’s not easy to understand someone else’s code. Questions "What is it?" and "Where did this come from?" appear on almost every combination of letters and numbers. And the faster the understanding of the logic "what? Why? And where?" Comes, the easier it is to study someone else's code, including examples. True, sometimes it takes more than one day to "jump through the code" and "look through the manuals" for this.

All STM32F4xx microcontrollers have quite a lot of peripherals. Each microcontroller peripheral is assigned a specific, specific and non-relocatable memory area. Each memory area consists of memory registers, and these registers can be 8-bit, 16-bit, 32-bit, or whatever, depending on the microcontroller. In the STM32F4 microcontroller, these registers are 32-bit and each register has its own purpose and its own specific address. Nothing prevents their programs from accessing them directly by specifying the address. At what address this or that register is located and to which peripheral device it is indicated in the memory card. For STM32F4, such a memory card is in the document DM00031020.pdf, which can be found at st.com. The document is called

RM0090
Reference manual
STM32F405xx / 07xx, STM32F415xx / 17xx, STM32F42xxx and STM32F43xxx advanced ARM-based 32-bit MCUs

In chapter 2.3 Memory map on page 64, a table begins with the addresses of the register areas and their belonging to the peripheral device. In the same table, there is a link to a section with more detailed memory allocation for each peripheral.

On the left of the table, the address range is indicated, in the middle is the name of the periphery, and in the last column, where there is a more detailed description of the memory allocation.

So for the GPIO general-purpose I / O ports in the memory allocation table, you can find that addresses are allocated for them starting from 0x4002 0000. The GPIOA general-purpose I / O port occupies the address range from 0x4002 000 to 0x4002 03FF. The GPIOB port occupies the address range 0x4002 400 - 0x4002 07FF. Etc.

In order to see a more detailed distribution in the range itself, you just need to follow the link.

There is also a table here, but with a memory card for the GPIO address range. According to this memory map, the first 4 bytes belong to the MODER register, the next 4 bytes belong to the OTYPER register, and so on. Register addresses are counted from the beginning of the range belonging to a specific GPIO port. That is, each GPIO register has a specific address that can be used when developing programs for the microcontroller.

But the use of register addresses for humans is inconvenient and fraught with a lot of errors. Therefore, microcontroller manufacturers create standard libraries that make it easier to work with microcontrollers. In these libraries, physical addresses are assigned their letter designation. For STM32F4xx these correspondences are specified in the file stm32f4xx.h... File stm32f4xx.h belongs to the library CMSIS and is located in the Libraries \ CMSIS \ ST \ STM32F4xx \ Include \ folder.

Let's see how the GPIOA port is defined in the libraries. Everything else is defined similarly. It is enough to understand the principle. File stm32f4xx.h quite large and therefore it is better to use search or the possibilities that your toolchain provides.

For the GPIOA port, we find the line that mentions GPIOA_BASE

GPIOA_BASE is defined via AHB1PERIPH_BASE

AHB1PERIPH_BASE, in turn, is defined through PERIPH_BASE

And in turn, PERIPH_BASE is defined as 0x4000 0000. If you look at the memory map of peripheral devices (in the section 2.3 Memory map on page 64), we will see this address at the very bottom of the table. The registers of the entire periphery of the STM32F4 microcontroller begin from this address. That is, PERIPH_BASE is the starting address of the entire periphery of the STM32F4xx microcontrollers in general, and the STM32F407VG microcontroller in particular ..

AHB1PERIPH_BASE is defined as the sum (PERIPH_BASE + 0x00020000). (see pictures back). This will be the address 0x4002 0000. In the memory card, the GPIO general purpose I / O ports begin at this address.

GPIOA_BASE is defined as (AHB1PERIPH_BASE + 0x0000), that is, this is the starting address of the GPIOA port register group.

Well, the GPIOA port itself is defined as a structure of registers, the placement of which in memory begins with the GPIOA_BASE address (see the line #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE).

The structure of each GPIO port is defined as type GPIO_TypeDef.

Thus, the standard libraries, in this case the file stm32f4xx.h, simply humanize machine addressing. If you see the entry GPIOA-> ODR = 1234, then this means that the number 1234 will be written at the address 0x40020014. GPIOA has a starting address of 0x40020000 and the ODR register has an address of 0x14 from the beginning of the range, therefore GPIOA-> ODR has an address of 0x40020014.

Or for example, you do not like the GPIOA-> ODR entry, then you can define #define GPIOA_ODR ((uint32_t *) 0x40020014) and get the same result by writing GPIOA_ODR = 1234 ;. Just how expedient is it? If you really want to enter your own designations, then it's better to just reassign the standard ones. How this is done can be seen in the file stm32f4_discovery.h For example, this is how one of the LEDs is defined there:

#define LED4_PIN GPIO_Pin_12
#define LED4_GPIO_PORT GPIOD
#define LED4_GPIO_CLK RCC_AHB1Periph_GPIOD

A more detailed description of the periphery of the ports is in stm32f4xx_gpio.h

Top related articles