You don't need to understand your MCU.
Until suddenly you do.
HAL functions can hide hardware for a while. But the first time an interrupt fires unexpectedly, a peripheral stops responding for no apparent reason, or a delay takes ten times longer than expected — you need the foundation. This module builds it before you need it.
A vocabulary reference
Every technical term used across Modules 1–9, defined precisely with context. Stop Googling mid-lesson.
A concept ladder
The ten ideas that everything else builds on — in the correct order, with no assumed knowledge.
A practical primer
Hex numbers, binary, bit manipulation, and register patterns — the four mechanical skills every embedded engineer uses daily.
A guided debugging case
A real UART failure traced from the wire waveform to the RCC clock tree, register value, root cause, correction, and measured result.
A course map
Where each module fits, what it teaches, and which terms from this glossary it will use.
This course assumes you can write C code — loops, functions, pointers, structs. It does not assume you know anything about hardware. If you've ever wondered:
— "Why do I write GPIOA->ODR |= (1 << 5) instead of just setting a variable?"
— "What is a clock and why does everything depend on it?"
— "Why does volatile exist and when do I need it?"
— this module answers those questions first, so the rest of the course makes immediate sense.
"An MCU is a CPU, some memory, and a set of peripherals — all connected by a single shared address bus. Every component you interact with — RAM, Flash, GPIO, timers, UART — is just a range of addresses on that bus."
This is the model. Everything else is detail.
Once you hold this model, register writes make sense. Peripheral clock enables make sense. Memory-mapped I/O makes sense. Linker scripts make sense. DMA makes sense. The whole course is elaborating on this one idea from different angles — closer and closer to the metal.
The Big Picture
What an MCU is, what it contains, and how software talks to hardware
An MCU (Microcontroller Unit) is a complete computer on a single chip. It contains:
The arithmetic brain. Fetches instructions from memory, executes them, reads/writes data. On our boards: ARM Cortex-M4 — a 32-bit processor running up to 168 MHz.
Two types. Flash (non-volatile, stores your firmware — survives power loss). SRAM (volatile, stores running data — cleared on power loss). Both live on the same address bus.
Hardware blocks that do specific jobs: GPIO (pins), UART (serial comms), timers, ADC, SPI, I2C, DMA. Each one is a cluster of registers at a fixed address.
There is no system call, no driver API, no OS in between. Your C code writes directly to registers in hardware. The path is:
| Layer | What it is | Who uses it | What you sacrifice |
|---|---|---|---|
| Bare metal | Direct register writes. No abstractions. Your code IS the driver. | This course, low-level drivers, safety-critical code | Portability, development speed |
| HAL / LL | ST's Hardware Abstraction Layer. C functions that wrap register writes. | Most production STM32 code | Some overhead, less control |
| RTOS | Operating system layer — tasks, semaphores, queues. FreeRTOS is the most common. | Complex multi-task systems | Complexity, timing overhead |
This course teaches at the bare-metal level, using HAL only where it helps clarity. The goal is that you understand what HAL does — so you can debug it when it fails.
The Concept Ladder
Ten ideas in the right order — each one makes the next possible
These ten ideas are the prerequisite for everything else. They appear in order — each concept uses the previous one. Read them slowly. If one doesn't click, everything after it will feel fuzzy.
Course Map
What each module covers and how they connect — click any module to expand
The modules form a deliberate sequence: hardware foundation → toolchain → CPU → memory → peripherals → timing → power → design thinking. Each module builds on all previous ones. The glossary in this module covers every technical term you'll encounter.
Keyword Glossary
Every technical term used across Modules 1–9 — defined before you need them
Number Systems
Decimal, binary, hexadecimal — and why embedded engineers live in hex
What humans use. 0–9. Useless for hardware because it doesn't map to bits. You'll never write a register address in decimal.
What hardware actually uses. 0 and 1. Essential for understanding bit fields in registers. Every GPIO pin, every interrupt flag — one bit.
What embedded engineers use. 0–F. One hex digit = exactly 4 bits. 8-digit hex number = 32-bit address. All register addresses are in hex.
Every one of those addresses lives somewhere on a single 4GB address line. This is schematic, not to scale — Peripherals and reserved space are actually far larger than Flash or SRAM — but the order and boundaries are real.
Type any 8-digit hex address. Watch which region it falls in and what that byte pattern tells you.
Bit Manipulation
The four operations that appear in every embedded program — and why
Hardware registers pack many settings into a single 32-bit word. The MODER register for GPIOA holds configuration for 16 pins — 2 bits per pin. You must be able to change one pin's bits without touching the other 15 pins. That requires precise bit manipulation.
REG |= (1 << n) — sets bit n to 1. Never clears any other bit. "Open the gate at position n."
REG &= ~(1 << n) — clears bit n to 0. Never sets any other bit. "Close the gate at position n."
REG ^= (1 << n) — flips bit n. Useful for LED blink: toggles the current state without reading it first.
(REG >> n) & 1 — extracts bit n. Result is 0 or 1. Use to check if a flag is set.
Starting register value: 0b10100101 (0xA5). Pick an operation on bit 3 and watch exactly which bit moves.
When a register field is wider than 1 bit (e.g., 2-bit pin mode), you must clear the entire field first, then set the new value. The sequence is always: clear → set.
GPIOA->MODER = 0x400; resets ALL 16 pins to input mode and sets PA5 to output. Every other pin loses its configuration. Always use read-modify-write.
Some registers like BSRR (GPIO bit set/reset) are designed for direct assignment — writing the upper 16 bits clears pins, lower 16 bits sets them. One write, no race condition. These are the exception; always check the datasheet.
(1 << n) creates a bitmask with only bit n set. This is the single most common embedded C idiom. Memorize it.
In C, 1 << 31 may be undefined behavior (shifting into sign bit of int). Use 1UL << 31 or UINT32_C(1) << 31 to ensure unsigned 32-bit arithmetic. STM32 headers already do this — RCC_AHB1ENR_GPIOAEN expands to a safe value.
Worked Debugging Case
The firmware runs and the UART transmits — but the terminal shows garbage
Terminal baud or frame format mismatch; incorrect GPIO alternate-function setup; wrong voltage level or poor wiring; incorrect USART oversampling; wrong BRR divider; or an incorrect assumption about the clock feeding USART2.
Do not begin by rewriting the UART driver. First classify the failure using evidence from the wire, the peripheral registers and the RCC clock tree.
| Observation | Evidence | What it tells us |
|---|---|---|
| TX pin is active | A logic analyser shows clean 0–3.3 V transitions with recognisable start and stop bits. | The GPIO pin, alternate-function path and basic electrical connection are probably working. |
| Bit time is wrong | Measured bit period is approximately 17.36 µs. | 1 / 17.36 µs ≈ 57,600 baud — exactly half the intended 115,200 baud. This strongly indicates a clock or divider error. |
| BRR is internally consistent | USART2->BRR reads 0x02D9. | 0x02D9 is the divider expected when the code assumes an 84 MHz USART clock. |
| The actual peripheral clock is lower | RCC configuration shows SYSCLK = 168 MHz and APB1 prescaler = 4, so PCLK1 = 42 MHz. | USART2 is clocked from PCLK1. The APB timer x2 rule does not double the USART clock. |
Observe the physical symptom
Confirm whether the pin is inactive, electrically noisy, or active with the wrong timing.
Measure before changing code
Use a scope or logic analyser to measure voltage levels, bit time and frame structure.
Read the hardware state
Inspect GPIO alternate-function registers, USART CR1/CR2/BRR and RCC prescalers in the debugger.
Trace the clock from source to peripheral
Do not assume every peripheral receives SYSCLK, or that timer-specific clock rules apply to UART, SPI or I2C.
Correct one cause and re-measure
A successful fix must be visible both in the register state and on the physical waveform.
Tools and Setup
What you'll need and what each tool does in the embedded workflow
| Tool | What it does | When you use it |
|---|---|---|
| arm-none-eabi-gcc | The C compiler for ARM. Translates your .c files into ARM machine code. The "none-eabi" means it targets bare metal (no OS, embedded ABI). | Every build. Called by your Makefile or IDE automatically. |
| arm-none-eabi-ld | The linker. Combines compiled object files, assigns memory addresses, produces the final .elf using your linker script. | Every build. Run automatically after compilation. |
| arm-none-eabi-objcopy | Converts .elf (debug-rich) to .hex or .bin (programmer-friendly). The flash programmer needs .hex; keep .elf for debugging. | Every build. Produces the file you flash to the chip. |
| OpenOCD / ST-Link | On-chip debugger. Connects to the chip's JTAG/SWD debug port. Flash programming, breakpoints, register inspection — all through this. | Flashing firmware and debugging via GDB. |
| GDB / STM32CubeIDE | Debugger interface. Step through code, inspect registers, read memory live. STM32CubeIDE wraps GDB with a GUI. | Debugging — and reading peripheral registers live. |
Your source code. Headers define types and function prototypes. C files contain implementations. The compiler processes one .c file at a time.
Tells the linker where to place code and data in memory. Defines the Flash start address, SRAM boundaries, section layout. Module 2 covers this in depth.
Runs before main(). Sets up the stack, copies initialized data from Flash to SRAM, zeroes uninitialized data, then calls main(). Every STM32 project has one.
The output of the linker. Contains the machine code plus debug symbols. Keep this — it's what GDB needs to show you meaningful backtraces and variable names.
Generated by the linker. Shows the exact Flash and SRAM address of every function and variable. The .map file tells you exactly why your binary is the size it is.
ST's device header files. Define the peripheral register structures, bit field constants, and base addresses. These translate 0x40020014 into GPIOA→ODR for you.
The single most powerful debugging skill: stopping execution and reading a peripheral's actual register values. This tells you the hardware state directly — not what your code intended, what actually happened.
Six real symptoms. Pick the root cause for each — this is exactly the debugging instinct Modules 1–9 will build on.