Print "Hello World!"

In order to print “Hello World!”, we need to configure a USART module to transmit this message from the board to the PC. Before starting the project, we had better learn about how HAL&LL library works between the application code and the hardware.

As the image above shows, the HAL&LL layer is an abstract layer which provides many APIs for setting up nearly all the modules of an STM32 MCU. When the application layer calls an HAL&LL function, the called function checks all the posted parameters, makes sure that these parameters are correct, and then writes these parameters into the corresponding registers to set the modules up. In the most cases, the HAL&LL library will callback MSP(MCU Specific Package) functions to setup lower level module parameters and distribute GPIOs in need. These MSP functions can be overwritten by application codes to achieve more flexibilities and possibilities.

Taking configuring USART and transmitting message via USART as an example, the diagram above shows that the whole program can be divided into three different stages.

  1. After some necessary initialization, such as the system clock configuration, the static constructor, etc., the program flow enters the main function to complete the first stage. The first stage, which is labelled by light-blue arrows, initializes the HAL library. In between, HAL_MspInit is a function for initializing the lower layer of HAL.
  2. The second stage is the flow labelled by yellow arrows. In this stage, HAL_UART_Init, HAL_UART_MspInit and other functions are called in sequence to setup the USART module.
  3. The final stage is presented by the dark-blue arrows in the diagram. During this stage, HAL_UART_Transmit and so on are invoked to exchange data with another device through the enabled USART module.

The full name of USART is Universal Synchronous/Asynchronous Receiver/Transmitter. In this chapter, we will use the asynchronous mode. If you are not familiar with USART, especially UART, just google it before going ahead.

main.c describes and implements the application layer.

 * main.c
 *  Created on: Sep 29, 2019
 *      Author: daizhirui
#include "main.h"
char* msg = "Hello World!\n";
void Error_Handler(void);
UART_HandleTypeDef huart2;
void UART2_Init(void) {
    huart2.Instance = USART2;
    huart2.Init.BaudRate = 115200;
    huart2.Init.WordLength = UART_WORDLENGTH_8B;
    huart2.Init.StopBits = UART_STOPBITS_1;
    huart2.Init.Parity = UART_PARITY_NONE;
    huart2.Init.Mode = UART_MODE_TX_RX;
    huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    if (HAL_UART_Init(&huart2) != HAL_OK) {			// ==> invoke HAL_UART_MspInit
int main(void) {
    HAL_Init(); 	// ==> invoke HAL_MspInit in msp.c
    while (1) {
        HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
void Error_Handler(void) {
    while (1);

In msp.c, most functions are override functions that implements some abstract HAL APIs according to the application needs. Most of these functions initialize the lower layer of HAL, such as enabling the corresponding clock, assigning GPIO to modules, etc.

 * msp.c
 *  Created on: Sep 29, 2019
 *      Author: daizhirui
#include "main.h"
//void HAL_MspInit(void) {}
void HAL_UART_MspInit(UART_HandleTypeDef *huart) {
    // Enable Relative Module Clocks
    GPIO_InitTypeDef gpio_uart;
    gpio_uart.Pin = GPIO_PIN_2;
    gpio_uart.Mode = GPIO_MODE_AF_PP;
    gpio_uart.Pull = GPIO_PULLUP;
    gpio_uart.Speed = GPIO_SPEED_LOW;
    gpio_uart.Alternate = GPIO_AF7_USART2;
    HAL_GPIO_Init(GPIOA, &gpio_uart);       // UART TX
    gpio_uart.Pin = GPIO_PIN_3;
    HAL_GPIO_Init(GPIOA, &gpio_uart);       // UART RX

it.c provides functions that process interrupts.

 * it.c
 *  Created on: Sep 29, 2019
 *      Author: daizhirui
#include "main.h"
void SysTick_Handler(void) {

Project archive:

You can use STM32CubeProgrammer to upload the binary to the board. It is an all-in-one multi-OS software that can do many things about STM32 products, such as upgrading the ST-Link firmware, uploading the binary, adding writing protection, etc. The usage of STM32CubeProgrammer varies in different operating systems. In Windows, its GUI runs well so that it is easy to use. In macOS and Linux, if you don't follow the instruction about Java versions, you have to use the CLI version of STM32CubeProgrammer.

When you open this software, you will see the following window. If you might have connected your STM32 board to the computer, you would find that an ST-Link device is shown on the up right of the window.

Click “Connect” and open the “Erasing&Programming” section by clicking the download button on the left. Then, you just need to select your binary and click “Start Programming” to upload it. In general, you can find your binary in the “debug” folder under the root of your project folder. The binary has a name ending with “.elf”.

You can download the User Manual of STM32CubeProgrammer to learn about the details of using STM32CubeProgrammer_CLI.

Here I provide a Makefile which simplifies the procedures of uploading and running the program.

ELF_FILE=./Debug/$$(ls ./Debug | grep .elf)
CONNECTION_CMD=-c port=SWD freq=4000
	$(STM32CUBEPROG) -c port=SWD -s

To read the output from the board, you need to install a serial tool, such as SSCOM for Windows and SerialTools for macOS.

Then, you can select the board in the window of your serial tool software. The name of the board is generally expected to be “COMx STMicroelectronics ST…” in Windows and “usbmodemxxxxx” in macOS. Also, you should set the baud rate the same as the value you specify in your project. Then, click “OpenCom” or “connect” and press the reset button of your board to launch your program. Instantly, you will see the output in the serial tool.

  • stm32_tutorial/chapter2.txt
  • Last modified: 10 months ago
  • by daizhirui