Bare Metal STM32 · Part 1 of 7
STM32 HAL vs Bare Metal: Which Should You Use?
38:22
Part 1 / 7
⚙️ Bare Metal STM32 Series · 7 Parts
Part 1 of 7
Part 01
HAL vs Bare Metal: Which Should You Use?
38:22
Part 02
Clock Configuration with RCC: From HSI to PLL
22:10
Part 03
Interrupts & NVIC: Complete Configuration Guide
47:22
Part 04
SPI DMA Transfers: No CPU Overhead
33:05
Part 05
ADC DMA Circular Mode: Continuous Sampling
29:45
Part 06
Debugging with SWO Trace & ITM Printf
18:30
Part 07
Low-Power Stop Mode with Wake-Up on EXTI
34:12
01
Overview
Every STM32 developer eventually faces the same question: HAL or bare metal? HAL hides register complexity behind clean function calls — great for prototyping. Bare-metal register access gives you total control, deterministic timing, and smaller binaries — essential for production. This part teaches you both approaches side-by-side so you can make an informed choice for every project.
- Understand the three layers: HAL, Low-Level (LL) drivers, and direct register access
- Toggle a GPIO using all three approaches and compare the generated assembly
- Measure the overhead difference between HAL and register writes with a logic analyser
- Know when to use each layer — and why mixing them in the same project is fine
02
The Three Layers
HAL
High-level, portable API. Handles clock enables, state machines, and callbacks for you. Largest code size, non-deterministic timing. Best for getting started and complex peripherals like USB.
HAL_GPIO_WritePin()
LL Drivers
Thin inline wrappers around registers. Portable like HAL but with near-zero overhead. Generated by CubeMX. Best of both worlds for most peripheral access.
LL_GPIO_SetOutputPin()
Registers
Direct read/write to memory-mapped peripheral registers. Single instruction, zero overhead, fully deterministic. Requires reading the reference manual but produces the tightest code.
GPIOD->BSRR = (1<<12)
03
Code
01
Three ways to toggle a GPIO — HAL, LL, and registergpio_comparison.c
C
/* ── Method 1: HAL ─────────────────────────────────────── */ // ~20 instructions — checks state, handles locks, portable HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET); HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12); /* ── Method 2: LL Driver ───────────────────────────────── */ // ~2 instructions — inline, no overhead, still portable LL_GPIO_SetOutputPin(GPIOD, LL_GPIO_PIN_12); LL_GPIO_TogglePin(GPIOD, LL_GPIO_PIN_12); /* ── Method 3: Direct Register ─────────────────────────── */ // 1 instruction — BSRR is a write-only atomic set/reset reg GPIOD->BSRR = (1 << 12); // Set PD12 GPIOD->BSRR = (1 << (12 + 16)); // Reset PD12 GPIOD->ODR ^= (1 << 12); // Toggle (not atomic!) /* ── Clock Enable — always required before any GPIO use ── */ // HAL way: __HAL_RCC_GPIOD_CLK_ENABLE(); // Register way: RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
02
Bare-metal GPIO init from scratchgpio_bare.c
C
void GPIO_Init_PD12_Output(void) { // 1. Enable GPIOD clock on AHB1 bus RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; // 2. Set MODER[25:24] = 01 → General purpose output GPIOD->MODER &= ~(0x3 << (12 * 2)); GPIOD->MODER |= (0x1 << (12 * 2)); // 3. Set OTYPER bit 12 = 0 → Push-pull (default) GPIOD->OTYPER &= ~(1 << 12); // 4. Set OSPEEDR[25:24] = 10 → High speed GPIOD->OSPEEDR |= (0x2 << (12 * 2)); // 5. Set PUPDR[25:24] = 00 → No pull (output drives the line) GPIOD->PUPDR &= ~(0x3 << (12 * 2)); }
BSRR is your best friend for atomic GPIO
The Bit Set/Reset Register (BSRR) lets you set or clear any output pin in a single atomic write — no read-modify-write needed. Bits [15:0] set pins; bits [31:16] clear pins. This is safe in both task and ISR context without disabling interrupts.
Continue the Series
Work through all 7 parts of Bare Metal STM32 to master low-level embedded development from the ground up.