Programming · 55 pages
- C language — 15 lessonstutorial
- Lesson 1 — Hello, C
- Lesson 2 — Types & literals
- Lesson 3 — Expressions & operators
- Lesson 4 — Control flow
- Lesson 5 — Functions
- Lesson 6 — Arrays
- Lesson 7 — Pointers
- Lesson 8 — Strings
- Lesson 9 — Structs & unions
- Lesson 10 — Dynamic memory
- Lesson 11 — Preprocessor
- Lesson 12 — Multi-file projects
- Lesson 13 — Standard library
- Lesson 14 — Undefined behavior
- Lesson 15 — Modern C (C11/17/23)
- C++ programming — 15 lessonstutorial
- Lesson 1 — Hello, C++
- Lesson 2 — References & namespaces
- Lesson 3 — Classes & RAII
- Lesson 4 — Inheritance & polymorphism
- Lesson 5 — Templates
- Lesson 6 — STL containers
- Lesson 7 — Iterators & algorithms
- Lesson 8 — Smart pointers
- Lesson 9 — Move semantics
- Lesson 10 — Lambdas
- Lesson 11 — Error handling
- Lesson 12 — Concurrency
- Lesson 13 — Modules (C++20)
- Lesson 14 — Modern C++23
- Lesson 15 — C++ on Cortex-M
- Bare-metal C tutorial — 10 lessonsembedded
- Lesson 1 — Toolchain setup
- Lesson 2 — Bare-metal blink
- Lesson 3 — Registers in C
- Lesson 4 — Memory map
- Lesson 5 — Linker script
- Lesson 6 — Interrupts & ISRs
- Lesson 7 — Bit patterns
- Lesson 8 — Fixed-point math
- Lesson 9 — State machines
- Lesson 10 — Ring buffers + DMA
- Embedded C patterns
- volatile & memory-mapped IO
- CMSIS-Core
- CMSIS-DSP
- Linker scripts
- Startup & vector table
- C++ on Cortex-M
- Rust on Cortex-Mmodern
- Inline assembly
- Bare-metal libc & newlib-nano
- Bootloaders & DFU
- OTA update flow
C tutorial — bare-metal Cortex-M from zero
A hands-on 10-lesson tutorial. Set up GCC, blink an LED without HAL, read registers, write a linker script, handle interrupts, decode CFSR/HFSR, build a DMA-driven UART driver — all from scratch.
Most embedded C tutorials online start with HAL_GPIO_TogglePin(...). You copy the call, the LED blinks, and you have learned nothing about the silicon underneath. Six months later when the HAL leaks abstractions — a peripheral half-initialised, a clock dependency you can't see, an interrupt firing at the wrong priority — you have no model to debug it with.
This tutorial does the opposite. We start at the silicon and earn our way up. By lesson 2 you will have toggled an LED with a literal pointer to a memory-mapped register, a 16-line startup assembly file, and a linker script you wrote yourself. By lesson 10 you will have implemented a DMA-driven UART driver with a lock-free ring buffer and a circular DMA controller — the same kind of code that ships in production firmware.
The philosophy is simple: learn the silicon, then earn the right to use HAL. After you have written a volatile register-banged driver, the HAL's wrapper around it is just a convenience. Before that, it is a black box that hides the bugs you cannot debug.
At a glance
- Target
- Cortex-M4 (STM32F411 Nucleo, STM32F4-DISCO, or NUCLEO-G474)
- Hardware required
- Cortex-M4 board with on-board ST-Link (or external SWD probe), USB cable, host PC
- Software
- arm-none-eabi-gcc, OpenOCD or st-flash, arm-none-eabi-gdb, a text editor
- Time investment
- Approximately 15-20 hours total — 60-90 minutes per lesson
- Prerequisites
- Basic C: pointers, structs, bit operations. No prior embedded experience required
- You will write
- About 1500 lines of C and 50 lines of linker / assembly
- Output
- A working bare-metal codebase: blink, GPIO driver, SysTick, UART RX/TX with DMA, fault decoder
The board
The lessons target the STM32F411 Nucleo-64 (a Cortex-M4F at 100 MHz with 512 KB flash, 128 KB SRAM, on-board ST-Link/V2-1). It is the cheapest sane choice: a single USB cable carries power, programming, and a virtual COM port. Total cost around 15 USD.
The code will run unchanged on:
- STM32F4-DISCO (STM32F407, same Cortex-M4F core, MEMS sensors on board)
- NUCLEO-F446RE (STM32F446, M4F at 180 MHz)
- NUCLEO-G474RE (STM32G474, M4F at 170 MHz, modern peripherals)
- Any other Cortex-M4 with an SWD probe — peripheral register addresses change, the rest does not.
If you have an STM32F1 (M3, no FPU) or STM32L0 (M0+), most lessons still work — only lesson 8 (fixed-point) has a section that depends on M4F intrinsics, and lesson 6 references SysTick which is identical on every Cortex-M.
The 10 lessons
| Core | Arch | Pipe | DMIPS | FPU | DSP | MPU | TZ | MVE | Notes |
|---|---|---|---|---|---|---|---|---|---|
| Cortex-M0 | ARMv6-M | 3-stage | 0.84/MHz | — | Subset of Thumb-2. | ||||
| Cortex-M0+ | ARMv6-M | 2-stage | 0.95/MHz | — | Single-cycle I/O port. | ||||
| Cortex-M23 | ARMv8-M Baseline | 2-stage | 0.99/MHz | — | Secure & non-secure worlds. | ||||
| Cortex-M3 | ARMv7-M | 3-stage | 1.25/MHz | — | The classic mid-range core. | ||||
| Cortex-M33 | ARMv8-M Mainline | 3-stage | 1.50/MHz | SP | Used in nRF53, STM32L5/U5. | ||||
| Cortex-M4 | ARMv7E-M | 3-stage | 1.25/MHz | SP | Most popular general-purpose MCU core. | ||||
| Cortex-M55 | ARMv8.1-M | 4-stage | 1.6/MHz | SP/DP | 4–5× DSP/ML uplift vs. M4. | ||||
| Cortex-M7 | ARMv7E-M | 6-stage | 2.14/MHz | SP/DP | Used in i.MX RT, STM32H7. | ||||
| Cortex-M85 | ARMv8.1-M | 7-stage | 3.0/MHz | SP/DP | i.MX RT700, Renesas RA8. |
How to use this tutorial
Three rules. They sound boring; they are the difference between absorbing the material and forgetting it in a week.
-
Read in order. Each lesson assumes the last one. Lesson 4 (memory layout) will not make sense without lesson 2 (the blink program whose memory layout we dissect). Lesson 10's ring buffer assumes the interrupt vocabulary from lesson 6.
-
Type the code. Do not copy-paste. Typing is slow on purpose. The friction is where learning lives. When you mis-type a register name and the compiler complains, you have built a memory of that register name that you will never have from a clipboard. Every working firmware engineer I know has typed
GPIOA->ODR ^= (1<<5);enough times that their fingers know it. -
Do every exercise before reading the answer. The answers are in
<Callout type="spec">blocks at the end of each lesson. Read them only after you have written your own code and either made it work or got stuck. Getting stuck for 20 minutes and then reading the answer teaches more than reading the answer in the first place.
A note on HAL and CubeMX
I am not anti-HAL. I have shipped firmware that used STM32Cube HAL extensively. The HAL is a perfectly reasonable abstraction once you understand what it abstracts. The problem is using it without that understanding, because the moment a real bug shows up — a stuck DMA stream, a missing baud rate, an EXTI line that won't fire — you cannot debug at the abstraction level. You have to drop down to registers anyway.
After this tutorial you will be able to read the HAL source code and understand exactly what it is doing. You will also know when to bypass it (hot ISRs, custom protocols, code-size-constrained projects) and when to use it (one-off bring-up code, prototypes, anything where engineering time matters more than the last 5% of performance).
What you will have built
By the end:
- A bare-metal blink (lesson 2) that flashes the user LED, written entirely without vendor libraries.
- A GPIO register-level driver (lessons 3, 5) with proper volatile semantics and atomic BSRR usage.
- A SysTick-driven 1 ms tick (lesson 6) feeding both
main()polling code and interrupt handlers. - A UART RX/TX driver (lessons 6, 9, 10) that handles back-pressure correctly and runs DMA in the background.
- A Q15 PID controller (lesson 8) that runs in roughly 30 cycles per update on M4F.
- A command parser (lesson 9) that dispatches text commands from the serial port to runtime actions.
The total code is around 1500 lines. Every line is yours.
Cross-references
- Embedded C patterns — the patterns this tutorial uses, in reference form.
- Startup & vector table — deeper dive on what we build in lesson 2.
- Linker scripts — reference companion to lesson 5.
- CMSIS — what to use after you have done it the hard way.
- NVIC overview — interrupt model behind lesson 6.
- Vector table — the architectural reference.
- DMA peripheral — used in lesson 10.
- UART peripheral — used in lessons 6, 9, 10.
- Linker script builder tool — generate a starting point for your chip.
- Fault decoder — paired with lesson 6 hardware fault sidebars.
- GCC flags reference — what every flag in our build command means.
Start with Lesson 1 — Toolchain setup.