Firmware 101: STM32 Bare metal

Programming STM32 microcontroller at low-level with CMSIS core

Leonardo Cavagnis
7 min readJan 4, 2022

Hardware: the part of a computer that you can kick; Software: the one you can only curse at… Firmware: the part you can do both!”

If you are a noob in firmware development, read first my quickstart guide about development with STM32.

A Firmware is a specific class of software that provides the low-level control for a specific hardware.
Examples of devices containing firmware are the embedded systems: a computer system with a dedicated function (E.g., digital camera, vacuum cleaner robot, etc…). Embedded systems are based on electronic boards where the “brain” is the MicroController Unit (MCU).

In this project, you will control all the LEDs mounted on the STM32F072B-DISCO: an evaluation board produced by ST, one of the most important MCU suppliers. The program will continuously turn on the LEDs in sequence.

Compared to the Firmware 101: STM32 Quickstart guide project, by following this tutorial you will create the application from scratch without using auto-generated code. You will write on your own the low-level drivers to communicate with the hardware.

Below the expected result:

Led blinking demo

Starting-from-scratch = Bare metal programming

Bare metal programming is writing firmware which directly runs on the hardware without using any underlying software abstraction (E.g., auto-generated code, operating systems, etc…).

To create and build a bare-metal project, you will use Keil µVision IDE: a software development environment for Arm Cortex-M based microcontroller.

Development flow with Keil uVision IDE

Create bare metal project in Keil μvision IDE

  • Open Keil uVision software.
  • Go to: Project → New uVision project… and choose a project name.
  • In the Device for Target section, select the ST MCU used in the application (STM32F072RBTx).
Keil — Device For Target
  • In the Run-Time Environment section, select checkboxes: CMSIS CORE and Device Startup (make sure to select “Standalone” in the dropdown menu).
Keil — Manage Run-Time Environment
  • Project will be structured as follows:
Keil STM32 Bare metal project structure

* CMSIS CORE: The Common Microcontroller Software Interface Standard (CMSIS) is a software abstraction layer for ARM Cortex microcontrollers. It provides interfaces to processor and peripheral registers.
* Device Startup: A startup file is an assembly (.s) file executed before the main() function of application. It performs initialization steps by setting up the hardware of the microcontroller so that the user application can run.

  • Before starting to develop the application, you need to create the main file. Rename the “Source Group 1” folder in “Src” and add the main.c file.
main.c file

Reference Manual: the “instruction booklet” of ST MCUs

Without using the HAL libraries and auto-generated code, you have to manually set the MCU peripheral registers. To know which registers and how to control them, it is necessary to consult the “instruction booklet” of the microcontroller.
This “booklet” is called Reference manual. It provides complete information on how to use a specific ST MCU.

You can find the STM32F0x Reference Manual in the Books section of your Keil workspace.

Keil — Books section

The GPIO configuration registers

According to STM32F072B-DISCO schematic (Books section → Board Data Books → Schematics), LED pins are:

  • Red: PC6 (GPIO Port C, pin #6)
  • Orange: PC8 (GPIO Port C, pin #8)
  • Blue: PC7 (GPIO Port C, pin #7
  • Green: PC9 (GPIO Port C, pin #9)
LEDs section of 32F072B-DISCOVERY Schematic (MB1076)

To manage the LED status, the GPIO Pins connected to LEDs have to be configured as output pins. To do that, follows below steps:

  1. Enable GPIO peripheral
  2. Configure GPIO pin as output
  3. Control GPIO status

1 — Enable GPIO peripheral
STM32F072B microcontroller has 6 GPIO ports (A, B, … F). Each port contains up to 15 pins. Before using each port, you have to enable the related peripheral clock. This applies to all peripheral types.

Page 44/1004 — RM0091 Reference Manual for STM32F0x

In our project, only Port C is used. You enable the GPIO Port C peripheral clock by setting bit 19 (I/O Port C Clock Enable) to 1 in register RCC_AHBENR.

/* Port C Clock Enable */
RCC->AHBENR |= (1UL << 19);
Page 120/1004 — RM0091 Reference Manual for STM32F0x

2— Configure GPIO pin as output
Each GPIO port has four configuration registers (GPIOx_MODER, GPIOx_OTYPER, GPIOx_OSPEEDR and GPIOx_PUPDR). Each configuration register is dedicated to a specific function. MODER register allows user to configure the I/O mode (Input, Output, Analog) of a pin.

By setting bit 12 to 1 and bit 13 to 0 of GPIOC_MODER register, you configure PC6 (Red Led) pin as output:

GPIOC->MODER |= (1UL << (6*2));
GPIOC->MODER &= ~(1UL << (6*2+1));
Page 157/1004 — RM0091 Reference Manual for STM32F0x

3— Control GPIO status
Each GPIO port has two data registers:

  • Input data register (GPIOx_IDR): reads the data present on the I/O pin (If it is configured as Input).
  • Output data register (GPIOx_ODR): value written to the register is output on the I/O pin (If it is configured as Output).

To turn on the Red led (PC6 pin): set bit 6 to 1 of GPIOC_ODR register:

GPIOC->ODR |= (1UL << 6);

To turn off the Red led (PC6 pin): set bit 6 to 0 of GPIOC_ODR register:

GPIOC->ODR &= ~(1UL << 6);
Page 159/1004 — RM0091 Reference Manual for STM32F0x

The “hello world” complete application

#include "stm32f0xx.h" /* CMSIS Core of STM32F0xx uC */int main() {
/* Port C Clock Enable */
RCC->AHBENR |= (1UL << 19);

/* Configure Pin PC6 (Red) as OUTPUT */
GPIOC->MODER |= (1UL << (6*2));
GPIOC->MODER &= ~(1UL << (6*2+1));

/* Configure Pin PC7 (Blue) as OUTPUT */
GPIOC->MODER |= (1UL << (7*2));
GPIOC->MODER &= ~(1UL << (7*2+1));

/* Configure Pin PC8 (Orange) as OUTPUT */
GPIOC->MODER |= (1UL << (8*2));
GPIOC->MODER &= ~(1UL << (8*2+1));

/* Configure Pin PC9 (Green) as OUTPUT */
GPIOC->MODER |= (1UL << (9*2));
GPIOC->MODER &= ~(1UL << (9*2+1));

while(1){
/* Red ON, Green OFF, Blue OFF, Orange OFF */
GPIOC->ODR |= (1UL << 6);
GPIOC->ODR &= ~(1UL << 9);
GPIOC->ODR &= ~(1UL << 7);
GPIOC->ODR &= ~(1UL << 8);
delay();
/* Red OFF, Green ON, Blue OFF, Orange OFF */
GPIOC->ODR &= ~(1UL << 6);
GPIOC->ODR |= (1UL << 9);
GPIOC->ODR &= ~(1UL << 7);
GPIOC->ODR &= ~(1UL << 8);
delay();
/* Red OFF, Green OFF, Blue ON, Orange OFF */
GPIOC->ODR &= ~(1UL << 6);
GPIOC->ODR &= ~(1UL << 9);
GPIOC->ODR |= (1UL << 7);
GPIOC->ODR &= ~(1UL << 8);
delay();
/* Red OFF, Green OFF, Blue OFF, Orange ON */
GPIOC->ODR &= ~(1UL << 6);
GPIOC->ODR &= ~(1UL << 9);
GPIOC->ODR &= ~(1UL << 7);
GPIOC->ODR |= (1UL << 8);
delay();
}

return 0;
}

What about the delay() function?
Since you do not have any support library, you have to create a delay function yourself. The easiest way is to implement a while() loop with a big number.

void delay(){
volatile int i = 0;
while (i<=500000) {
i++;
}
}

Why using volatile keyword for i variable?
volatile tells the compiler not to optimize anything that has to do with the ivariable.
In general, the compiler optimizes what is considered unused and useless.
In the delay() function the while loop is useless because it does not execute any helpful operations, so it is important to declare the variable i as volatile otherwise the function does not perform as expected.

Let’s upload it on the board!

Before loading the program on the microcontroller: plug USB cable of eval-board to the computer and setup Keil project as follow:

  • Go to: Flash → Configure Flash Tools…
  • In the Utilities section, click on the Settings button.
  • In the Flash Download section, click on Add and select the two items related to STM32F0xx MCU.
Keil — Target Driver Setup

Now, click first on Build and then on Download button.
Look at the evaluation board …and have fun!

Keil — Build and Download icons

References

If you want to learn more about STM32 Bare metal programming, you can find an interesting course on Udemy:
The STM32 Crash course: Bare metal and CMSIS Core

--

--

Leonardo Cavagnis

Passionate Embedded Software Engineer, IOT Enthusiast and Open source addicted. Proudly FW Dev @ Arduino