showwaiyan_

← home · all projects

Bare-Metal STM32 Firmware

A button press raises a hardware interrupt and the LED blinks five times, with no HAL, no CMSIS and no standard library.

CARM Cortex-M4STM32F4

Overview

Bare-metal C firmware for the STM32F401RE (Nucleo board, ARM Cortex-M4). Everything between power-on and main() is written by hand: the startup code, the interrupt vector table and the linker script. The point of the project is to understand what the vendor libraries normally hide.

At a glance

Board
Nucleo STM32F401RE (Cortex-M4)
Language
C, GNU linker script
Button
B1 on PC13, input, rising-edge EXTI
LED
LD2 on PA5, output
Toolchain
arm-none-eabi-gcc, OpenOCD, ST-Link

The interrupt path

  1. Button on PC13
  2. EXTI line 13
  3. NVIC IRQ 40
  4. EXTI15_10_IRQHandler
  5. event_count += 5
  6. Main loop blinks LED

What it shows

  • Startup and vector table. startup.c has a hand-written interrupt vector table and Reset_Handler. It copies .data from flash to SRAM and zeroes .bss before calling main().
  • Custom linker script. linker.ld defines the FLASH (512K) and SRAM (96K) regions and places .text, .rodata, .data and .bss.
  • Direct register access. RCC, GPIO, SYSCFG, EXTI and NVIC are configured through their registers directly, using the datasheet and reference manual.
  • Safe sharing with the interrupt. The handler and the main loop share a volatile counter. The main loop reads and clears it inside a short interrupt-disabled section (cpsid i / cpsie i).
  • Atomic LED writes. The LED is set and cleared through the GPIO BSRR register. It is a single write, so there is no read-modify-write that an interrupt could split.

How it works

  1. main() enables the clocks for SYSCFG, GPIOA and GPIOC, then sets PA5 as output and PC13 as input.
  2. SYSCFG routes PC13 to EXTI line 13. EXTI is set to rising edge and the line is unmasked. NVIC IRQ 40 is enabled.
  3. On a button event, EXTI15_10_IRQHandler clears the pending bit and adds 5 to event_count.
  4. The main loop copies and clears event_count with interrupts disabled, then blinks the LED that many times.

Lesson learned

EXTI line 13 did not fire until the SYSCFG clock was enabled (RCC_APB2ENR bit 14). Without that clock, SYSCFG cannot route PC13 to the EXTI line.

Build and flash

Build
arm-none-eabi-gcc \
  -mcpu=cortex-m4 \
  -mthumb \
  -nostdlib \
  -T linker.ld \
  startup.c main.c \
  -o blink.elf
Flash
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
  -c "program blink.elf verify reset exit"