Introduction To Programming STM32 ARM Cortex-M 32-Bit Microcontrollers
Introduction To Programming STM32 ARM Cortex-M 32-Bit Microcontrollers
The range of performance available with the STM32 is quite expansive. Some of the
most basic variants include the STM32F0 and STM32F1 sub-series that start with a clock
frequency of only 24 MHz, and are available in packages with as low as 16 pins.
At the other performance extreme, the STM32H7 operates at up to 400 MHz, and is
available in packages with as many as 240 pins. The more advanced models are
available with Floating Point Units (FPU) for applications with serious numerical
processing requirements. These more advanced models blur the line between a
microcontroller and a microprocessor.
Development Tools
Development tools are required to develop the code, program the microcontroller and
test/debug the code. The development tools include:
• Compiler
• Debugger
• In-Circuit Serial Programmer (ICSP)
There are several software development tools available for code development on
STM32 microcontrollers. The software tools are available as Integrated Development
Environments (IDE) which combines all of the necessary tools into an integrated
environment.
Two common development packages include:
• Keil MDK ARM (uVison5 IDE) – The MDK ARM IDE is a very stable development
environment which can be downloaded for free. It allows development of code
up to a program size of 32 KB. For developing larger programs a licensed version
needs to be purchased here: http://www2.keil.com/mdk5/install.
• CoIDE – A free tool chain which is based on a trimmed down version of the
Eclipse IDE integrated along with an embedded ARM version of the free GCC
compiler. It can be downloaded here:
http://www.coocox.org/software/coide.php.
There are also several other IDEs that are available for use with STM32
microcontrollers. However, this article focuses on developing and flashing a program
using the very popular Keil MDK ARM uVision5 IDE.
Apart from the software tools, an In-Circuit Serial Programmer (ICSP) is required to
program and test the code on the actual microcontroller. The ICSP is required to
interface the microcontroller to the PC software tools via a USB port. The ARM Cortex-
M microcontrollers support two programming protocols: JTAG (named by the
electronics industry association the Joint Test Action Group) and Serial Wire Debug
(SWD).
There are several ICSP programmers available that support these protocols, including:
• Keil U-Link 2
• Segger J-Link
• ST-Link
The STM32CubeMX tool can be downloaded from here. The STM32Cube comes with an
extensive set of drivers for all types of peripherals and support for an optional
FreeRTOS (a free Real-Time Operating System) pre-integrated with the code.
The following section describes in detail how to create a simple UART application for
the STM32F030 microcontroller that echoes whatever is typed on a terminal window.
• Run the application and select New Project. It will then open the MCU Selector
window as shown below.
• Double click to select the microcontroller model being used. In this case we’re
using the STM32F030K6. It then takes you to the pinout page for the selected
microcontroller.
The STM32F030K6 is an ARM Cortex-M0 core with 32KB of Flash memory and 4KB of
RAM memory. The example code enables the UART that uses the PA9 and PA10 pins for
receiving and transmitting serial data as shown below with the green pins.
Configure the UART settings under the Configuration Tab and choose the UART settings
as shown below. Enable the NVIC global interrupt option under the NVIC Settings tab.
Next, navigate to Project-->Settings in order to add the new project name and select
the tool chain IDE to be used. For this example, set the project name to 'UARTEcho' and
select the Keil-MDK5 IDE for the project development.
Finally, generate the project code by clicking Project -> Generate Code.
This program so far just initializes the UART peripheral and stops in an infinite loop.
It’s important to note that the STM32Cube generates /* USER CODE BEGIN x */
and /* USER CODE END x */ comment blocks to implement the user specific
code. The user code must be written within these comment blocks. Whenever the code
is re-generated with modified configurations the STMCube tool retains the user code
within these user comment blocks.
Next, define a global variable to receive a byte from the UART in the main.c source file:
After all of the initialization code, enable the driver to receive 1 byte. The following
function enables the RXNE interrupt bit.
Now, add a callback function to handle the receive interrupt and transmit the received
byte.
Finally, we need to compile the code and flash (download) it to the microcontroller.
When the Keil MDK ARM IDE is installed, drivers for ST-LINK V2, J-Link and Ulink2 are
available. The ST-Link debugger will be selected by default. Go to Projects-->Options
for Target and in the Debug tab select the ICSP programmer used.
The microcontroller will now echo any data received over the UART. It can be
connected to a PC by using a USB-to-Serial Converter. On the PC open the COM port
with a terminal application using the settings of 115200-8-N-1. Now anything that is
sent from the terminal will echo back through the microcontroller.
Interrupt system
The STM32 interrupt system is based on the ARM Cortex M core NVIC peripheral. The
STM32 MCUs support multiple maskable interrupt channels apart from the 16 interrupt
channels of the ARM core.
For example the STM32F0 MCU series support 32 maskable interrupts. The exception
and the interrupt vector table for this family of MCUs is given in the table below.
For the external interrupt lines, to generate an interrupt, the interrupt line should be
configured and enabled. This is done by programming the two trigger registers with the
desired edge detection and by enabling the interrupt request by writing a ‘1’ to the
corresponding bit in the interrupt mask register.
Some of the EXTI lines are combined to a single NVIC vector. For example, EXTI4_15 is
mapped to a single vector address so there will be a single interrupt routine for all the
interrupts from PIO4 to PIO15. But the source of the interrupt can be identified by
reading the interrupt pending register.
One important thing to consider while designing a system using the STM32 MCUs is the
selection of the GPIO pins for the interrupts. The MCU may have more than 16 GPIOs
available on the device but there are only 16 external interrupt lines available.
For instance, the EXTI_0 can be mapped to either PA0 or PB0 but not both. So while
choosing the pins for external interrupts they should be chosen such that they can be
uniquely mapped to one of the EXTI lines.
The following section describes how to configure an interrupt using the STM32 Cube.
Select the Configuration Tab and choose the hardware module for which the interrupt
has to be configured. The module configuration window opens.
Then select the NVIC settings tab and enable the global interrupt.
The code to enable the interrupt for the module will be generated in the
stm32f0xx_hal_msp.c in the HAL_<module>_MSPInit(...) function.
The code generated by the STM32 Cube will have the IRQ_Handler implementation of
all the interrupts. When the interrupt is enabled the code will be included into the
application.
Usually the generated code already handles the IRQ and clears the flag which generated
the interrupt. It then calls an application callback that corresponds to the event that
generated the interrupt for the module.
The STM32 HAL (Hardware Abstraction Layer) implements a callback for each of the
event types within each module as part of the driver. In this example the Rx Transfer
Complete callback should be copied from the stm32f0xx_hal_UART.c file.
The callback functions within the driver will be implemented with a __weak linker
attribute. The user needs to implement a copy of the necessary callback function by
removing the __weak attribute in one of the application files and then writing the
specific handling required within that function.
/**
* @brief Rx Transfer completed callback.
* @param huart UART handle.
* @retval None
*/
__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE : This function should not be modified, when the callback is needed,
the HAL_UART_RxCpltCallback can be implemented in the user file.
*/
}
Conclusion
This tutorial is an introduction to writing an application that works with the STM32
family of microcontrollers. There are several other methods for writing an application
but the STM32Cube discussed is an easy and intuitive method to get started.
This tool simplifies the initialization of the microcontroller peripherals. It also improves
the maintainability of the code especially when there are hardware revisions which
require remapping of the signals to different pins.
Another advantage of using the STM32Cube tool is that it generates a report of the user
configuration for the microcontroller. In this report it details the clock tree, pin
mapping and hardware module configuration which are all very useful. There are also
several other code libraries and example programs available for all the STM32 variants.
Support for several IDEs is also included.
If you want a pre-design of your product, along with accurate estimates for all of the
costs to develop, prototype, scale, and ultimately manufacture it, as well as an accurate
timeline, then be sure to check out the Predictable Hardware Report.
-----
This article is a guest post by my friend Mohan Kashivasi, who I’ve had the pleasure of
working with on multiple projects. Mohan has over 15 years experience as an
Embedded Design Engineer. He is the founder of a startup called Vithamas
Technologies Ltd. that develops wireless mesh networking solutions for IoT applications.
Previously, Mohan was a senior staff engineer at CSR/Qualcomm and was one of the
principal developers of CSRmesh technology.