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
Lesson 1 — Toolchain setup
Install arm-none-eabi-gcc, OpenOCD, and GDB. Wire up the ST-Link. Verify the toolchain with a one-line compile and decode every GCC flag.
- What you'll learn
- Installing the GNU Arm Embedded toolchain, OpenOCD, and GDB; verifying with a one-line compile; understanding every flag
- Code provided
- About 10 lines of C plus shell commands
- Time
- 45-60 minutes (mostly download/install)
- Hardware
- STM32F411 Nucleo or any Cortex-M4 board with ST-Link or compatible SWD probe
This first lesson is all setup. We will install three tools — the compiler, the flasher, and the debugger — and confirm each one works before we write a single byte of real firmware. Skipping this step is the single most common reason people abandon embedded tutorials. The cost of doing it right now is one evening; the cost of not doing it is every subsequent lesson failing in confusing ways.
The three tools:
- arm-none-eabi-gcc — the cross-compiler. It runs on your laptop and emits Cortex-M machine code.
- OpenOCD (or
st-flash) — talks to the SWD probe and writes your binary into the chip's flash. - arm-none-eabi-gdb — connects to the debugger half of OpenOCD and lets you step, breakpoint, and inspect.
All three are free. All three are available on Linux, macOS, and Windows.
Pick the board
You want a Cortex-M4-based STM32 with an on-board SWD probe. In order of preference for this tutorial:
- NUCLEO-F411RE (STM32F411, M4F, 100 MHz, 512 KB flash, 128 KB SRAM) — the cheapest sane choice. Around 15 USD. Ships with on-board ST-Link/V2-1 that also acts as a USB-to-UART bridge on USART2.
- STM32F4-DISCO (STM32F407, M4F, 168 MHz) — slightly older, has MEMS sensors and audio codec on board, ST-Link/V2 built in.
- NUCLEO-G474RE (STM32G474, M4F, 170 MHz) — modern peripherals, ST-Link/V3.
- NUCLEO-F446RE (STM32F446, M4F, 180 MHz) — drop-in for F411 with more flash.
If you already have any of these, use it. If not, get the F411 Nucleo. The lessons reference STM32F4xx register addresses; on a different family you will need to look up your chip's peripheral addresses in the reference manual, but the structure is identical.
Install the compiler
Linux (Debian/Ubuntu)
sudo apt update
sudo apt install gcc-arm-none-eabi binutils-arm-none-eabi libnewlib-arm-none-eabiNote: Ubuntu's packaged version sometimes lags. If you want the latest (12.x or 13.x), download from Arm's official site at developer.arm.com/downloads/-/arm-gnu-toolchain-downloads and add bin/ to your PATH.
macOS
brew install --cask gcc-arm-embeddedIf brew is missing, install it from brew.sh first. The cask drops the binaries in /Applications/ArmGNUToolchain/<version>/arm-none-eabi/bin and creates symlinks on PATH.
Windows
Download the installer from developer.arm.com/downloads/-/arm-gnu-toolchain-downloads. Run it. Check the box that adds the toolchain to PATH. Reboot if PowerShell still cannot find the binaries.
Verify
In any shell:
arm-none-eabi-gcc --versionExpected output (version may differ):
arm-none-eabi-gcc (Arm GNU Toolchain 13.2.Rel1) 13.2.1 20231009
Copyright (C) 2023 Free Software Foundation, Inc.If the shell says "command not found", your PATH is wrong. On Linux/macOS, which arm-none-eabi-gcc should print a path; if not, find where apt/brew put it and add that directory to your shell's PATH. Do not move on until --version prints.
Install OpenOCD
OpenOCD is the open-source debug bridge. It talks to ST-Link, J-Link, CMSIS-DAP, and most other SWD probes.
Linux
sudo apt install openocd
openocd --versionmacOS
brew install open-ocd
openocd --versionWindows
Grab a build from gnutoolchains.com/arm-eabi/openocd/ or use the OpenOCD that ships with VS Code Cortex-Debug. The xPack distribution at xpack.github.io/openocd/ is also good.
OpenOCD should print something like:
Open On-Chip Debugger 0.12.0st-flash alternative
If you only want to write firmware (not debug), stlink-tools is a smaller install:
# Linux
sudo apt install stlink-tools
# macOS
brew install stlinkIt gives you st-flash, st-info, and st-util. We will use st-flash for quick programming in lesson 2.
Install GDB
# Linux
sudo apt install gdb-multiarch
# macOS
brew install arm-none-eabi-gdb # may also be bundled with the gcc caskVerify:
arm-none-eabi-gdb --versionOn some Linux distros the binary is gdb-multiarch rather than arm-none-eabi-gdb. Either works — for the multiarch version you pass set architecture arm once after launch.
Optional: VS Code Cortex-Debug
Install VS Code. Install the Cortex-Debug extension by marus25. It wraps OpenOCD and GDB into a graphical debugger with peripheral register views. We will write everything from the command line in this tutorial, but for day-to-day work Cortex-Debug is excellent.
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [{
"name": "Cortex Debug",
"type": "cortex-debug",
"request": "launch",
"servertype": "openocd",
"cwd": "${workspaceFolder}",
"executable": "build/blink.elf",
"device": "STM32F411RE",
"configFiles": ["interface/stlink.cfg", "target/stm32f4x.cfg"],
"svdFile": "${workspaceFolder}/STM32F411.svd"
}]
}The SVD file (System View Description) tells the IDE the layout of every peripheral register on the chip — grab it from github.com/posborne/cmsis-svd-data/tree/main/data/STMicro.
Wire up the board
Take the Nucleo board out of its box. There are no jumpers to set — the on-board ST-Link is hardwired to the target STM32. Plug the USB cable into the CN1 / STLK port (the one nearest the small ST-Link MCU, not the user USB if your board has one). Three LEDs should light up:
- LD1 (red/green tricolour) — ST-Link status. Solid green means USB enumerated.
- LD3 (red) — 3.3 V power good.
- LD2 (green) — user LED, controlled by PA5 on the F411 Nucleo. This is the one we will blink.
The OS should enumerate two interfaces: a mass-storage device (drag-and-drop programmer) and a virtual COM port (USART2 bridge on F411 Nucleo). On Linux, check dmesg | tail after plugging in — you should see STM32 STLink and cdc_acm lines.
A first compile
We will write the simplest possible C program that the cross-compiler can chew on. Put this in hello.c:
int square(int x) {
return x * x;
}That's it — no main(), no headers, nothing fancy. We just want to confirm the compiler can produce Cortex-M4 object code.
Compile:
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -nostdlib -c hello.c -o hello.oIf the command returns with no output, it worked. Inspect what we made:
arm-none-eabi-objdump -d hello.oExpected output (Thumb assembly for the square function):
hello.o: file format elf32-littlearm
Disassembly of section .text:
00000000 <square>:
0: fb00 f000 mul.w r0, r0, r0
4: 4770 bx lrTwo instructions: multiply r0 by itself, return. That's Cortex-M4 Thumb-2.
Every flag decoded
The command line had four flags. Each one matters.
-mcpu=cortex-m4
Tells GCC which ARM core to target. This affects which instructions are available (Cortex-M4 has the Thumb-2 instruction set plus DSP extensions; Cortex-M0 has a much smaller subset). It also sets architecture-specific defaults for code generation.
You can also use -march=armv7e-m to target the architecture rather than a specific core. -mcpu is more common and lets GCC tune for the specific microarchitecture.
-mthumb
Cortex-M only executes Thumb (16-bit and Thumb-2 32-bit) instructions, never the older 32-bit ARM instruction set. This flag is mandatory for every Cortex-M target. Without it, GCC may emit ARM instructions that the M4 cannot execute.
-nostdlib
Do not link against the C standard library. We want this because:
- We have no operating system to provide syscalls.
- We have not provided startup code yet (next lesson).
- We want to confirm the compiler can produce code, independent of newlib.
In a real project you usually want newlib-nano (-specs=nano.specs) for memcpy, memset, and basic printf. We will add that in lesson 4.
-c
Compile only — produce an object file (.o), do not link. Useful for testing the compiler without needing a complete program with main().
A more realistic command
For comparison, here is what a typical full-project link command will look like by lesson 5:
arm-none-eabi-gcc \
-mcpu=cortex-m4 -mthumb \
-mfpu=fpv4-sp-d16 -mfloat-abi=hard \
-Os -g3 -ffunction-sections -fdata-sections \
-Wall -Wextra -Wpedantic -Wshadow -Wundef \
-T linker.ld -nostartfiles \
-Wl,--gc-sections -Wl,-Map=build/blink.map \
src/startup.s src/main.c \
-o build/blink.elfWe will introduce these flags one by one as the lessons need them. For now, the four-flag command above is the minimum to confirm the toolchain is alive.
The GCC flags reference explains every flag in this expanded line.
Exercises
Exercise 1.1. Run arm-none-eabi-gcc --print-multi-lib. What do the entries tell you about the toolchain's pre-built C library variants?
Exercise 1.2. Add -S instead of -c to the compile command. What does GCC produce, and where do the multiply and bx lr come from?
Exercise 1.3. On your board, what is the user LED pin? Find it in the board's user manual (search for "UM1724" for F411 Nucleo, "UM1472" for F407 Disco). Write down the GPIO port and pin number — you will need it in lesson 2.
What you learned
- Installed
arm-none-eabi-gcc, OpenOCD, and GDB on your host OS. - Wired the board over USB and confirmed the ST-Link enumerates.
- Compiled a one-line C file to Cortex-M4 Thumb-2 object code.
- Decoded the four mandatory flags:
-mcpu,-mthumb,-nostdlib,-c.
In lesson 2 we will write a complete bare-metal program — main.c, startup assembly, linker script — and flash it to the chip. The LED will blink. Every line will be yours.
Next
| Previous | Next |
|---|---|
| Tutorial overview | Lesson 2 — Your first blink, no HAL |