Published: Sep 15, 2022

STM32F030F4 - Bare Metal - Getting Started

Introduction

This post describes how to write a simple bare metal hello world application from scratch. No additional libraries, just a few lines of assembly.

Requirements

  • ST-Link V2 + STLink Tools
  • STM32F030F4

Processor Details

PeripheralSTM32F030F4
Flash16KB
SRAM4KB
Advanced Control Timer1 (16-bit)
General Purpose Timer4 (16-bit)
SPI1
I²C1
USART1
12-bit ADC1
GPIOs15
Max. CPU frequency48 MHz
Operating Voltage2.4V - 3.6V
Temperature (Ambient)-40°C to 85°C
Temperature (Junction)-40°C to 105°C
PackageTSSOP20

Toolchain

Install the compiler and debugger:

sudo apt install gcc-arm-none-eabi gdb-multiarch

You can also use gdb-arm-none-eabi instead of the newer gdb-multiarch.

Linker Script

The linker script tells the compiler how much memory there is and where that memory is located.

The start address and size can be found in the datasheet under Memory Mapping. The STM32F030F4 has 4KB SRAM (starting at 0x20000000) and 16KB Flash (starting at 0x08000000).

/* define the end of the stack memory (start of SRAM + size of SRAM) */
_estack = 0x20001000;

/* define memory regions */
MEMORY
{
    FLASH ( rx )      : ORIGIN = 0x08000000, LENGTH = 16K
    RAM ( rxw )       : ORIGIN = 0x20000000, LENGTH = 4K
}

Hello World in Assembly

Following code is written using the ARM “Thumb” instruction set.

The vector table defines the locations in memory that the CPU should jump to when the specified hardware interrupt is triggered. For this simple application we just need to deal with the “reset” handler. Most other interrupts are disabled by default.

// Generate code optimized for cortex-m0 and handle floating point 
// calculations in software (fplib) since there is no FPU available.
.syntax unified
.cpu cortex-m0
.fpu softvfp
.thumb

// Global memory locations (usable in other files)
.global vtable
.global reset_handler

// Vector Table
.type vtable, %object
vtable:
    .word _estack
    .word reset_handler
.size vtable, .-vtable

// Application code
.type reset_handler, %function
reset_handler:
  // Set the stack pointer to the end of the stack
  LDR  r0, =_estack
  MOV  sp, r0

  // Load something recognizable into register r7
  LDR  r7, =0xBAAAAAAD
  MOVS r0, #0
  main_loop:
    // Keep incrementing register 'r0'
    ADDS r0, r0, #1
    B    main_loop
.size reset_handler, .-reset_handler

Build

Build the ELF (Executable and Linkable Format) file. This is the binary that will be uploaded to the STM32F030F4.

arm-none-eabi-gcc -x assembler-with-cpp -c -O0 -mcpu=cortex-m0 -mthumb -Wall main.S -o build/main.o
arm-none-eabi-gcc build/main.o -mcpu=cortex-m0 -mthumb -Wall --specs=nosys.specs -nostdlib -lgcc -T STM32F030F4.ld -o build/main.elf

Let’s get a rough idea of what will be written to the chip:

arm-none-eabi-nm build/main.elf

Output:

20001000 A _estack
08000010 t main_loop
08000008 T reset_handler
08000000 T vtable

Run/Debug

First start a GDB server by running st-util which should output:

INFO common.c: STM32F03x: 4 KiB SRAM, 16 KiB flash in at least 1 KiB pages.
INFO gdb-server.c: Listening at *:4242...

Now start GDB:

gdb-multiarch build/main.elf 

Then connect to the target and load the binary:

target extended-remote :4242
load

Run continue, wait a few seconds and press Ctrl+C. Execute info registers and you should see that r0 was incremented and r7 contains “0xBAAAAAAD”. The full output of those commands looks like:

(gdb) load
Loading section .text, size 0x1c lma 0x8000000
Start address 0x08000000, load size 28
Transfer rate: 36 bytes/sec, 28 bytes/write.
(gdb) continue
Continuing.
^C
Program received signal SIGTRAP, Trace/breakpoint trap.
0x08000010 in reset_handler ()
(gdb) info registers
r0             0x3f1e68            4136552
r1             0xffffffff          -1
r2             0xffffffff          -1
r3             0xffffffff          -1
r4             0xffffffff          -1
r5             0xffffffff          -1
r6             0xffffffff          -1
r7             0xbaaaaaad          -1163220307
r8             0xffffffff          -1
r9             0xffffffff          -1
r10            0xffffffff          -1
r11            0xffffffff          -1
r12            0xffffffff          -1
sp             0x20001000          0x20001000
lr             0xffffffff          -1
pc             0x8000010           0x8000010 <reset_handler+8>
xpsr           0x1000000           16777216
msp            0x20001000          0x20001000
psp            0xfffffffc          0xfffffffc
control        0x0                 0 '\000'
faultmask      0x0                 0 '\000'
basepri        0x0                 0 '\000'
primask        0x0                 0 '\000'
fpscr          0xffffffff          -1

Conclusion

While it might be tempting to skip over this and jump directly into STM32Cube, Platform.io, etc. writing a simple “bare metal” application gives lots of insight into how things work under the hood. This knowledge can help debugging more complex applications and appreciate all the work that has gone into existing libraries.

Next Steps

Next I would recommend trying to make the onboard LED blink. For this you can either read the blog post Bare Metal - Blink - or if you feel up for a challenge try to accomplish the task with your existing knowledge and the STM32F030F4 Datasheet.