Getting started with STM32 development using CubeMX and gcc

2020-12-14 gcc

Introduction

In this article I would like to write an introduction to writing code for the NUCLEO-F446RE development board using ST’s code generating software called CubeMX. In this article I will show you how to use the free and open source compiler gcc to build the code. I’ll also show you how to write a hello world program that blink’s the LED and prints a message to the console using the USART peripheral.

Getting Started

Installing Compiler

The first thing you’ll need to do is to install the gcc arm compiler that will be used to build the code. the arm-none-eabi is called the target triple because it specifies the architecture of the compiler, the target system of the compiler, and the ABI of the compiler. In this case we are using an arm processor. We have no system because we are compiling for a bare metal MCU with no operating system. We are using the eabi ABI which stands for embedded application binary interface. Here are the commands to install the compiler no several popular distributions of Linux.

# Install build dependencies on an Arch based distro of Linux.
sudo pacman -S arm-none-eabi-gcc arm-none-eabi-newlib base-devel
# Install build dependencies on a Debian/Ubuntu based distro of Linux.
sudo apt install gcc-arm-none-eabi build-essential
# Install the gcc compiler from ARM's website for Windows
https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads

Installing CubeMX

You can install CubeMX from this site: https://www.st.com/en/development-tools/stm32cubemx.html The tool is free, but you will have to create an account with ST’s website. It’s a java program so there will be an installer for Windows, Mac, and Linux. Just run the appropriate installer for your system.

Creating STM32CubeMX Project

The first thing to create a CubeMX project is to select the MCU that the Nucleus board is using. To do that click in File -> New Project. Select STM32F446RE for the board. This is the STM chip that is used in the Nucleo development board that I’m using. Go ahead and select Start Project.

stm32 cubemx

You’ll be greeted with the following screen. Here you can see all of the pins on te MCU. You have the option to click on each of the pins and select the desired functionality for each of the individual pins. This makes it much easier to select the peripherals by allowing selection using the GUI instead of looking at the datasheet.

stm32 cubemx

First things we are going to want to do is configure the USART peripheral and then configure a GPIO that we use for the EE’s version of hello world. If we look at the schematic for the Nucleus development board we can see that PA2 and PA3 are connected USART -> USB peripheral on the dev board. So click on these pins and select USART_TX and USART_RX. Also click on PA4 and select GPIO_Output. You can also right click on pins in CubeMX to give them User lables to make them easier to reference in the GUI and in the generated code. I’ve named PA4 LD_GRN because it’s connect to the green LED on the Nucleus dev board. Now that the pins have been configured, the peripherals can be configured in the menu on the left. In the picture below you can see that the USART has been configured by default to have a baud rate of 115200. We’re just going to leave that as is for the moment since it’s good enough for what we are going to be using it for.

stm32 cubemx

The next thing to do is to change the project type for the generated code. We are going to be using the generated Makefiles to compile the code. These makefile are going to use the gcc compiler that we installed at a earlier step of the process. Now that the generated project type has been changed to use Makefiles, all that needs to be done is to actually generated the code.

To do that go back to the main screen with a view of the MCU pin selections, and there should be a generate button at the top of the screen. Once you click that the code will be generated for the configuration that we have just selected using the CubeMX software. CubeMX may prompt you to download the source code for the generated project. Go ahead and select yes so that the HAL libraries will be accessible on your system.

ls
Core/  Drivers/  getting-started.ioc  Makefile  startup_stm32f446xx.s  STM32F446RETx_FLASH.ld

The project file is the getting-started.ioc. The Makefile contains all the commands required to build the project. the startup_stm32f446xx.s contains the startup code for the MCU. The STM32F446RETx_FLASH.ld is the linker script which is used to define all the memory locations for the STM MCU. Drivers contains all the library HAL code for the peripherals and the Core folder contains all the generated initialization code that has been configured in the CubeMX software. To build the project all you have to do is run the make command in the same directory that contains the Makefile. Here’s an example of the outpu.

make
mkdir build		
arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 \
-mfloat-abi=hard -DUSE_HAL_DRIVER -DSTM32F446xx -ICore/Inc \
-IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy \
-IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include \
-IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections \
-g -gdwarf-2 -MMD -MP -MF"build/main.d" -Wa,-a,-ad,-alms=build/main.lst 
Core/Src/main.c -o build/main.o
...
arm-none-eabi-gcc build/main.o build/stm32f4xx_it.o build/stm32f4xx_hal_msp.o 
build/stm32f4xx_hal_tim.o build/stm32f4xx_hal_tim_ex.o build/stm32f4xx_hal_uart.o \
build/stm32f4xx_hal_rcc.o build/stm32f4xx_hal_rcc_ex.o build/stm32f4xx_hal_flash.o \
build/stm32f4xx_hal_flash_ex.o build/stm32f4xx_hal_flash_ramfunc.o \
build/stm32f4xx_hal_gpio.o build/stm32f4xx_hal_dma_ex.o build/stm32f4xx_hal_dma.o \
build/stm32f4xx_hal_pwr.o build/stm32f4xx_hal_pwr_ex.o build/stm32f4xx_hal_cortex.o \
build/stm32f4xx_hal.o build/stm32f4xx_hal_exti.o build/system_stm32f4xx.o \
build/startup_stm32f446xx.o -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 \
-mfloat-abi=hard -specs=nano.specs -TSTM32F446RETx_FLASH.ld  -lc -lm -lnosys  \
-Wl,-Map=build/getting-started.map,--cref -Wl,--gc-sections -o build/getting-started.elf
arm-none-eabi-size build/getting-started.elf
   text	   data	    bss	    dec	    hex	filename
   5092	     20	   1636	   6748	   1a5c	build/getting-started.elf
arm-none-eabi-objcopy -O ihex build/getting-started.elf build/getting-started.hex
arm-none-eabi-objcopy -O binary -S build/getting-started.elf build/getting-started.bin	

The first command compiles each of the individual source files into binary object files containing the machine code. I’ve expcleded most of these commands since they are repatitive and long since each and every file needs to be compiled. The second command shown is the linker step. It takes a list of all the object files and then created an executable file that contains all of the executable binary machine code in one file. THen the arm-none-eabi-size command is used to show the size of the executable. It shows both the size of the application in the amount of flash memory it uses and it also prints the amount of RAM it uses as well. The next two arm-none-eabi-objcopy commands convert the getting-started.elf file into getting-started.hex and getting-started.bin files. These files are the filetypes that are required by the programmer to load the programs on the STM mcu.

Writing Hello World Code

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    HAL_GPIO_WritePin(LD_GRN_GPIO_Port, LD_GRN_Pin, GPIO_PIN_SET);
    HAL_UART_Transmit(&huart2, (uint8_t*)"On\r\n", 4, 100);
    HAL_Delay(500);

    HAL_GPIO_WritePin(LD_GRN_GPIO_Port, LD_GRN_Pin, GPIO_PIN_RESET);
    HAL_UART_Transmit(&huart2, (uint8_t*)"Off\r\n", 5, 100);
    HAL_Delay(500);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Here is the code listing for the main function that is located in Core/Src/main.c. Everything in this file has been generated except for the code that is located between the USER CODE BEGIN and USER CODE END comments. If you look at the main file you can see that the HAL is initialized, then the clocks, and then the peripherals that were configured using CubeMX, and then a forever while loop is run which contains the user code. In this code we are toggling a LED on and off once a second, and also printing the state of the LED to the console.

Programming MCU

The Nucleo development kits have a very simple way of programming the firmware onto the MCU. When plugged in via USB they are mounted as a USB drive which contains a README. But this mount point also serves as a programming interface. When the .bin file is copied to this mount point it is automatically flashed onto the target MCU on the Nucleo development board.

Results

When the code is flashed onto the MCU a green LED should start blinking at a rate of 1 second. The application will also print on and off to the console. To see these we are going to be using the miniterm.py program to read the data from the USART peripheral. This asciinema shows the expected output of the MCU.

Now that you have followed these instructions you know know how to generate code for a new project using the CubeMX software. You know how to access the GPIO and UART peripherals. This is enough to get started becoming familiar with the STM32 platform and to start writing simple beginner application for their MCUs.