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
Peripheral | STM32F030F4 |
---|---|
Flash | 16KB |
SRAM | 4KB |
Advanced Control Timer | 1 (16-bit) |
General Purpose Timer | 4 (16-bit) |
SPI | 1 |
I²C | 1 |
USART | 1 |
12-bit ADC | 1 |
GPIOs | 15 |
Max. CPU frequency | 48 MHz |
Operating Voltage | 2.4V - 3.6V |
Temperature (Ambient) | -40°C to 85°C |
Temperature (Junction) | -40°C to 105°C |
Package | TSSOP20 |
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.