Pros & cons of using STM32CubeMX code generation tool insead of manually writing drivers for an ARM Cortex-M microcontroller
A new trend is emerging from several microcontroller manufacturers. Driver code can now be configured and generated using provided tools. This article will take a closer look at a tool named STM32Cube-MX (from here on called Cube) from ST Microelectronics. It is made for their STM32, an ARM based family of microcontrollers. Cube is a graphical tool for selecting, configuring and generating project reports and code. It currently supports all STM32 microcontrollers, both the STM32F and STM32L series, but is only available for the Windows operating system so far. It can be run as a stand-alone application or as a plugin for the Eclipse integrated development environment (IDE).
Cube is a great tool for narrowing down the possible choices when selecting a microcontroller. It let the user choose a microcontroller based on the required peripherals, family of microcontrollers, package type, flash size, ram size, minimum number of input/output pins and so on. For getting started quick and easy with prototyping, a board support package and a lot of example project are readily available for the three IDEs: Keil uVision, IAR Embedded Workbench and Atollic TrueStudio.
Configuration of drivers
After selecting the correct microcontroller for the application, the user interface has four main views. Following is a brief explanation of these four views.
This view is shown in figure 1. It includes a vizualization of the microcontroller and its pins and has a vertical side toolbar. This is where the required peripherals are selected. Selection of drivers can be made by choosing a pin that support the peripheral directly or by selecting the particular peripheral from the toolbar. The tool will automatically assign the peripheral to the appropriate pins. When using the toolbar, it will solve pin conflicts by moving a conflicted peripheral to unused pins that also support the peripheral. Sometimes this automatic conflict resolver might not be wanted and therefore it is possible to lock a peripheral to a pin if necessary. Both ways of selecting peripherals do not allow a combination that is not supported by the selected microcontroller. This view has a focus on pinout and therefore only settings related to each pins possible configuration are set. For example, one can choose to enable SPI in either full duplex, receive only or transmit only. The three possible selections all enables the SPI peripheral, but they also have an effect on which pins are utilized. In this view, it is not possible to set the baud rate, data size, endianness, prescaler, clock polarity, etc., as they do not affect the pinout. Another example is enabling an ADC and ADC channel in the pinout view. This affects the pinout, but the selected sampling rate, data conversion mode, resolution, etc., does not. The pinout view lets the developer enable and configure peripherals that affect the pinout. The only exception to this is that some middleware libraries can be enabled from this view even though they do not change the pinout. An example of this is FreeRTOS, which is a real-time operating system. More settings related to the peripherals can be chosen in the configuration view.
The configuration view shows all the enabled peripherals and middlewarelibraries. In addition, it is possible to configure watchdog functionality, DMA transfers, enable the different interrupts and set additional clock and reset behavior. In this view, the configuration of the peripherals is done. It is possible to set the baud rate, data size, endianness, prescaler, clock polarity, etc. of the SPI peripherals or the sampling rate, data conversion mode, resolution, etc., of the ADCs. An example is shown in figure 2.
Clock configuration view
All clock configurations should be done in the clock configuration view. This view is shown in figure 3 and provides a good overview of the clock tree. This view enables the developer to choose between external and internal clock sources. Clock frequencies in the clock hierarchy are automatically calculated by setting the oscillator frequency used and adjusting the many prescalers and PLLs. Invalid clock configurations are clearly shown in red, which makes it easy to discover and fix any incompatibilities.
Power consumption calculator view
This view is used to calculate approximately how much current the microcontroller, with the selected peripherals and settings, will draw in different modes and in average. Control of the current consumption is important for low-power applications and this tool greatly helps the developer. The view lets the developer set parameters as supply voltage, clock frequency, run/sleep/standby mode, RAM voltage, enabled peripherals, etc. In lower power applications, the microcontroller will often sleep most of the time, only waking up periodically to check for events or when an interrupt occurs. This means that the microcontroller most likely has at least two modes with very different parameter values. The power consumption view makes it possible to add a sequence of steps, where one step equals a mode for a set time interval. When all the steps corresponding to the different modes have been added, a graphical representation is created. An example of this is shown in figure 4. In addition, the most regular batteries can be selected from a list and the approximate battery life can be estimated for a full charge.
Generate project reports
- Reports can be created by a single click and contains much useful information. The generated report contains information like:
- The selected microcontroller.
- What version of Cube and firmware package version that was used to generate the code.
- Which compiler is used and what version.
- An overview of microcontroller pins as shown in the pinout view.
- Pin list with mappings to package pin number, internal pin number/port, peripheral active on pin and a user-selected label.
- All power consumption calculations done in the power consumption calculator view.
After selecting the required peripherals in the pinout view and configuring the clocks and peripherals, it is possible to generate the initialization code. Not only is the code generated, but all necessary project files for a chosen IDE as well. In addition to this, template files are provided in the project, which gives some guidance on how one might structure the code. In the generated giles, there are commented sections where the custom code should be inserted. It is very important that the code the developer Writes in generated files is written within these sections. Otherwise it will be gone when the project is regenerated. Even a minor change like changing the baud rate for a peripheral is recommended doing in Cube and not in the generated source file itself. Not because this is any faster (or especially slower), but it will prevent unnecessary possible errors and keep the generated documentation updated. In addition, if at a later time more major changes are to be done and the Cube project is not up to date with the generated code, one must remember to add the changes done to the source file. Always keep Cube updated!
Cube uses the STM32 hardware abstraction layer (HAL) library to create the initialization code, which makes it a lot easier to migrate between STM32 microcontrollers if needed.
By default, all generated code is put in a header and source file. The generated files can be separated into headers and source files for each type of peripheral to get a better overview by adjusting the settings in the top toolbar. This does not include middleware libraries.
How to use the generated code and HAL
As mentioned, the STM32 HAL library is used. After the code is generated, everything should be ready to use the HAL library to control the peripherals. The syntax of the HAL library is shown in the table below: It is the function calls as shown first inthe table that should be used to controlthe behavior of the peripherals. To start a basic timer the HAL_TIM_Base_Start() can be called or to send data over UART with DMA one could call HAL_UART_Transmit_DMA(). The second line in the table are macros that helps the developer change register values. The reason to use macros is that they are more portable and reduces the chance of setting the wrong bit. Depending on the application, these macros must sometimes be used. For example when changing the ADC sampling rate between two frequencies while the application is running. The last line in the table can typically only enable and disable clocks to peripherals and functionality related to reset.
Pros, cons and experiences
The Cube is a new software with its initial release in February 2014 and it still has some bugs. Bugs can be something as trivial as a missing or repeated line in the generated code or an error in the user interface preventing the use of an actual valid setting. Despite this, it saves a lot of time. Additionally, six minor version updates have been released in one year, so it is rapidly becoming better.
It is easy to get a good overview of the peripherals used and which ones are still available. Making changes in the configuration is fast and easy, but keeping the default structure generated by Cube will probably save a lot of time when the code has to be regenerated.
When new code is generated, one should check the initialization code that has been changed or added. A quick look can reveal missing lines or wrong settings that might otherwise be hard to detect. It is possible to add custom code to the initialization code that will not be removed when the code is regenerated. This makes it possible to correct many code generation errors.
All necessary callback functions are already prototyped and only the definitions have to be wrtitten. If more than one peripheral or similar can trigger one interrupt, there is already a handler determining where it was called from and the appropriate callback function is called.
If the macros provided by the HAL libraryis used, then the data sheet and reference manual are your friends to avoid errors. It should be easy to set up the project in IDEs other than the Three officially supported ones. Atollic TrueStudio uses the GCC compiler, which is also supported by several other IDEs.
Cube currently does not support generation of flash initialization code for enabling reading and writing to flash, but it might be supported in the future.
The pinout view automatically checks for conflicts and resolves them if possible. A pin list can be generated that is useful for hardware developers designing the custom hardware. Even if this tool is not used for code generation, it is useful for setting the pinout and determine if the selected combination of peripherals is valid.
The normal workflow is setting the required peripherals, configure the clock and then configure the peripherals. The power consumption calculator is optional, but if used it should be used when everything else is set and configured. The Cube uses the HAL library and therefore ensures that the code can easily be ported to any other STM32 With minor effort, as long as the required hardware functionality is present.