Introduction to Programming STM32 ARM Cortex-M 32-bit Microcontrollers

Article Technical Rating: 8 out of 10

The STM32 family of microcontrollers from STMicroelectronics is based on the ARM Cortex-M 32-bit processor core.

The STM32 series are some of the most popular microcontrollers used in a wide variety of products. They also have an excellent support base from multiple microcontroller development forums.

STM32 microcontrollers offer a large number of serial and parallel communication peripherals which can be interfaced with all kinds of electronic components including sensors, displays, cameras, motors, etc. All STM32 variants come with internal Flash memory and RAM.

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 few 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.

NOTE: This is a long, very detailed article so here's a free PDF version of it for easy reading and future reference.

 

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.

Finally, the STM32L sub-series is designed specifically for low-power portable applications running from a small battery.

Looking for a more basic introduction to microcontrollers? If so, check out my article Introduction to Microcontrollers.

 

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:

Developing the first application

It’s always easiest to start with a readily available basic code framework. Then, add the code that is required for the specific application and model of microcontroller.

Fortunately, STMicroelectronics provides a very useful graphical tool called STM32CubeMx that helps in creating a basic application project for any STM32 microcontroller of your choice. It also can be used to configure the peripherals on the multiplexed pins of the microcontroller.

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.

  • Install the STM32CubeMX software.
  • 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.

Building and flashing the code

Now open the generated MDK-ARM project file UARTEcho\MDK-ARM\UartEcho.uprojx.

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:


/* USER CODE BEGIN PV */
/* Private variables ———————————————————*/
static uint8_t recv_data;
/* USER CODE END PV */

 

After all of the initialization code, enable the driver to receive 1 byte. The following function enables the RXNE interrupt bit.


/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, &recv_data, 1);
/* USER CODE END 2 */

 

Now, add a callback function to handle the receive interrupt and transmit the received byte.


/* USER CODE BEGIN 0 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Transmit(huart, huart->pRxBuffPtr, 1, 1000);
}
/* USER CODE END 0 */

 

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.

Flash the code by selecting Flash->Download.

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.

 

Interrupt Description Vector Address
Reserved 0x00000000
Reset Reset 0x00000004
NMI Non maskable interrupt. The RCC clock security system (CSS) is linked to the NMI vector 0x00000008
HardFault All class of faults 0x0000000C
SVCall System service call via SWI Instruction 0x0000002C
PendSV Pendable request for system service 0x00000038
SysTick System tick timer 0x0000003C
WWDG Window watchdog interrupt 0x00000040
PVD_VDDIO2 PVD and VDDIO2 supply comparator interrupt (combined with EXTI lines 16 and 31) 0x00000044
RTC RTC interrupts (combined EXTI lines 17, 19 and 20) 0x00000048
Flash Flash global interrupt 0x0000004C
RCC_CRS RCC and CRS global interrupts 0x00000050
EXTI0_1 EXTI line[1:0] interrupts 0x00000054
EXTI2_3 EXTI line[3:2] interrupts 0x00000058
EXTI4_15 EXTI line[15:4] interrupts 0x0000005C
TSC Touch sensing interrupt 0x00000060
DMA_CH1 DMA channel 1 interrupt 0x00000064
DMA_CH2_3
DMA2_CH1_2
DMA channels 2 and 3 interrupts
DMA2 channel1 and 2 interrupts
0x00000068
DMA_CH4_5_6_7
DMA2_CH3_4_5
DMA channel 4,5,6 and 7 interrupts
DMA2 channel 3, 4, and 5 interrupts
0x0000006C
ADC_COMP ADC and COMP interrupts (Combined EXTI lines 21 and 22) 0x00000070
TIM1_BRK_UP_TRG_COM TIM1 break, update, trigger and commutation interrupts 0x00000074
TIM1_CC TIM1 capture compare interrupt 0x00000078
TIM2 TIM2 global interrupt 0x0000007C
TIM3 TIM3 global interrupt 0x00000080
TIM6_DAC TIM6 global interrupt and DAC underrun interrupt 0x00000084
TIM7 TIM7 global interrupt 0x00000088
TIM14 TIM14 global interrupt 0x0000008C
TIM15 TIM15 global interrupt 0x00000090
TIM16 TIM16 global interrupt 0x00000094
TIM17 TIM17 global interrupt 0x00000098
I2C1 I2C1 global interrupt (combined with EXTI line 23) 0x0000009C
I2C2 I2C2 global interrupt 0x000000A0
SPI1 SPI1 global interrupt 0x000000A4
SPI2 SPI2 global interrupt 0x000000A8
USART1 USART1 global interrupt (combined with EXTI line 25) 0x000000AC
UART2 USART2 global interrupt (combined with EXTI line 26) 0x000000B0
USART3_4_5_6_7_ 8 USART3, USART4, USART5, USART6, USART7, USART8 global interrupts (combined with EXTI line 28) 0x000000B4
CEC_CAN CEC and CAN global interrupts (combined with EXTI line 27 0x000000B8
USB USB global interrupt (combined with EXTI line 18) 0x000000BC

 

Extended Interrupts and Events Controller (EXTI)

The STM32 MCUs have an Extended interrupts and Events controller which manages the external and internal asynchronous events/interrupts and generates the event request to the CPU/Interrupt Controller and a wake-up request to the Power Manager.

Each of the one or more EXTI lines are mapped to one of the NVIC interrupt vectors.

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.

External Interrupt and GPIO mapping

Each of the GPIO available on the system can be configured to generate an interrupt. But each of the EXTI interrupt lines is mapped to multiple GPIO pins. For example, PIO0 on all the available GPIO ports (A,B,C, etc.) will be mapped to the EXTI0 line. PIO1 for all ports will be mapped to the EXTI1 line and so on.

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.


/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);

 

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 your project requires a sophisticated 32-bit microcontroller then I highly recommend the STM32 series. Not only are they powerful and popular, but STM32 microcontrollers are also quite affordable.

NOTE: If you're serious about developing a new electronic hardware product then download our free cheat sheets - 15 Steps to Develop Your Electronic Product and Summary of the Costs to Develop Your Electronic Product.

—–

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 of 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.

Leave a Reply 0 comments

Leave a Reply: