ENGINEERING DOCUMENTATION / PROJECT 01

STM32F103 Bare-Metal Hello World

ARM Cortex-M3 · libopencm3 · Renode 1.17.0

A reproducible C firmware baseline for the STM32F103C8T6: compile, link, load, and run a program that transmits Hello, World! over simulated USART1. No target operating system or physical board was required.

01 / Objective and verified outcome

Develop a bare-metal C application for the STM32F103C8T6, cross-compile it for ARM Cortex-M3, and execute the ARM ELF firmware in Renode. The application sends a single message through USART1 and then remains in an infinite loop.

Observed in the Renode USART1 analyzer
Hello, World!

Result: Successful firmware execution in simulation. Physical STM32 Blue Pill validation remains a separate future step.

02 / Development environment

ComponentConfiguration
Host OSUbuntu Linux
MicrocontrollerSTM32F103C8T6
CPU / architectureARM Cortex-M3 / ARMv7-M
Cross-compilerarm-none-eabi-gcc 14.2.1
Peripheral librarylibopencm3 (previously built)
SimulatorRenode 1.17.0 portable
SerialUSART1, 115200 baud, 8N1
FirmwareARM ELF

Prerequisites: the ARM cross-toolchain, compiled libopencm3 library, and portable Renode installation.

03 / Project files and paths

The paths below record the development machine used for the successful run. Adjust them when reproducing the project elsewhere.

Application:
/home/mannysoto/Documents/2026/Oct/week5/Sat/embedded/stm32_hello_ms

libopencm3:
/home/mannysoto/GitHub/embedded/libopencm3

Renode:
~/GitHub/embedded/renode/renode_1.17.0-portable
stm32_hello_ms/
├── main.c
├── main.o
├── stm32f103.ld
└── hello.elf

main.o and hello.elf are generated build artifacts; main.c and stm32f103.ld are the source files to preserve.

04 / Configure Renode

Start Renode in one Ubuntu terminal:

cd ~/GitHub/embedded/renode/renode_1.17.0-portable
./renode

In the Renode monitor, create the machine, load the STM32F103 platform, and inspect the CPU and serial peripheral:

mach create
machine LoadPlatformDescription @platforms/cpus/stm32f103.repl
cpu Model
sysbus.usart1
showAnalyzer sysbus.usart1

cpu Model reported cortex-m3. The USART1 analyzer opened as a separate, initially blank window. Keep Renode open while building the firmware.

05 / C firmware source

Create the application directory and edit main.c in another terminal:

mkdir -p /home/mannysoto/Documents/2026/Oct/week5/Sat/embedded/stm32_hello_ms
cd /home/mannysoto/Documents/2026/Oct/week5/Sat/embedded/stm32_hello_ms
nano main.c
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/usart.h>

int main(void)
{
    rcc_periph_clock_enable(RCC_GPIOA);
    rcc_periph_clock_enable(RCC_USART1);

    gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ,
                  GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9);

    usart_set_baudrate(USART1, 115200);
    usart_set_databits(USART1, 8);
    usart_set_stopbits(USART1, USART_STOPBITS_1);
    usart_set_parity(USART1, USART_PARITY_NONE);
    usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);
    usart_set_mode(USART1, USART_MODE_TX);
    usart_enable(USART1);

    const char *message = "Hello, World!\r\n";

    while (*message) {
        usart_send_blocking(USART1, *message++);
    }

    while (1) {
        // Keep firmware running.
    }
}

The program enables GPIOA and USART1 clocks, configures PA9 as the USART1 transmit pin, sets 115200 baud with 8 data bits, no parity and one stop bit, sends each character, then loops indefinitely. \r\n advances the serial terminal to the next line.

06 / Linker script and memory map

Create stm32f103.ld:

MEMORY
{
    rom (rx)  : ORIGIN = 0x08000000, LENGTH = 64K
    ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}

INCLUDE cortex-m-generic.ld

This specifies the target Flash and SRAM layout and includes the generic Cortex-M linker script from libopencm3.

RegionStart addressCapacity
Flash0x0800000064 KB
SRAM0x2000000020 KB

07 / Compile, inspect, and link

Compile main.c to an ARM object file:

arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -O0 -g \
  -DSTM32F1 \
  -I ~/GitHub/embedded/libopencm3/include \
  -c main.c -o main.o

-DSTM32F1 selects the STM32 family for libopencm3. Inspect the object:

arm-none-eabi-objdump -f main.o

Expected identifying fields:

file format elf32-littlearm
architecture: armv7
HAS_RELOC, HAS_SYMS

Link the object, library, and target memory layout:

arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -g \
  -nostartfiles \
  -T stm32f103.ld \
  -L ~/GitHub/embedded/libopencm3/lib \
  -Wl,-L,$HOME/GitHub/embedded/libopencm3/lib \
  main.o -lopencm3_stm32f1 \
  -o hello.elf

The output is hello.elf, an ARM executable containing the firmware and required library code.

08 / Firmware size

arm-none-eabi-size hello.elf

Recorded successful build output:

   text    data     bss     dec     hex filename
   4264      12       0    4276    10b4 hello.elf
4,264 BText
12 BInitialized data
4,276 BTotal reported (dec)

The recorded image size fits within the nominal 64 KB Flash and 20 KB SRAM target capacities.

09 / Load and execute in Renode

Return to the configured Renode monitor and load the ELF:

sysbus LoadELF @/home/mannysoto/Documents/2026/Oct/week5/Sat/embedded/stm32_hello_ms/hello.elf
start

Renode reported Starting emulation..., and the USART1 analyzer displayed:

Hello, World!

The output was observed in simulation, confirming the end-to-end firmware build, load, execution, and serial transmit path.

10 / Quick reproduction reference

After creating main.c and stm32f103.ld, the essential Ubuntu build commands are:

cd /home/mannysoto/Documents/2026/Oct/week5/Sat/embedded/stm32_hello_ms

# Compile
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -O0 -g \
  -DSTM32F1 \
  -I ~/GitHub/embedded/libopencm3/include \
  -c main.c -o main.o

# Link
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -g \
  -nostartfiles \
  -T stm32f103.ld \
  -L ~/GitHub/embedded/libopencm3/lib \
  -Wl,-L,$HOME/GitHub/embedded/libopencm3/lib \
  main.o -lopencm3_stm32f1 \
  -o hello.elf

# Verify
arm-none-eabi-size hello.elf

In an existing configured Renode session:

showAnalyzer sysbus.usart1
sysbus LoadELF @/home/mannysoto/Documents/2026/Oct/week5/Sat/embedded/stm32_hello_ms/hello.elf
start

For a fresh Renode session, first repeat the machine creation and platform-loading steps in Section 04.

11 / Engineering capabilities demonstrated

This completed simulation exercise establishes a reference for ARM cross-compilation, bare-metal C firmware, STM32 peripheral clocks, GPIO alternate functions, USART transmission, linker scripts, embedded memory layout, ELF generation, and Renode execution.

Project status: Completed in Renode simulation. Physical STM32 Blue Pill hardware testing is a separate future milestone.