armhub.dev
RTOS · 21 pages
RTOStutorial

RTOS tutorial — FreeRTOS on Cortex-M from zero

A hands-on 10-lesson tutorial. Why an RTOS, your first task, blocking primitives, queues, event groups, mutexes done right, tickless idle, debugging — all on real silicon.

5 min read · 1,183 wordsupdated · May 13, 2026

Most engineers learn an RTOS the wrong way. They read three blog posts, port the demo project, get a blinking LED, then graft a queue and a semaphore onto whatever code they happen to be writing. Three months later, they have a system that mostly works, occasionally locks up, and that nobody on the team really understands. The mental model never gelled because the tutorial skipped the parts that matter: why an RTOS at all, what xTaskCreate actually allocates, why the SysTick handler has to be the lowest-priority interrupt in the system, and what happens to a hardware ISR that calls xQueueSend instead of xQueueSendFromISR.

This tutorial is the version I wish I had been handed in 2012. Ten lessons, built on real silicon, in linear order. By the end you will have written, debugged, traced, and reasoned about a small but complete FreeRTOS application running on a Cortex-M4 board you can buy for fifteen dollars. The mental model will transfer to Zephyr, ThreadX, RT-Thread, and any future RTOS you encounter, because the underlying ideas — pre-emption, priority, blocking, IPC, the Cortex-M port internals — are universal. Only the API names change.

Tutorial at a glance
Target RTOS
FreeRTOS Kernel v11.x (post-AWS-stewardship release)
Target silicon
STM32F411 Nucleo-64 — anything Cortex-M4F works (nRF52, RP2040 M0+ with kernel rebuilt, MAX32660)
Hardware required
Board, USB cable, ST-Link (built into Nucleo), one LED (built in), one button (built in), UART-to-USB bridge (built in)
Toolchain
arm-none-eabi-gcc 13.x or later, make or CMake, OpenOCD or STM32CubeProgrammer
Time investment
12–18 hours total, 60–120 minutes per lesson
Lessons
10 lessons + this landing page
Recommended pre-reads
C tutorial, NVIC priority, exception model, SysTick

Why this tutorial and not the FreeRTOS docs

The official FreeRTOS documentation is reference material. It tells you what xQueueCreate does, not when to reach for a queue versus a stream buffer versus a task notification. It tells you what configMAX_SYSCALL_INTERRUPT_PRIORITY is, not what catastrophe befalls you when you set it wrong. Reference docs are organised by API surface; tutorials are organised by the order in which questions arise in your head as you build something. This is a tutorial. We will keep returning to the same six or seven core ideas — priority, blocking, context switch, ISR safety, memory layout, predictability, observability — and look at them from progressively more interesting angles.

The other reason: the FreeRTOS demos compile, but they do not teach. Most of them assume you already know what a task is, what a tick is, and what an idle hook is for. We start much earlier.

Philosophy — one RTOS deeply

You only need to learn one RTOS deeply. The vocabulary maps cleanly across the field:

  • FreeRTOS xTaskCreate is Zephyr K_THREAD_DEFINE, ThreadX tx_thread_create, RT-Thread rt_thread_create.
  • FreeRTOS xQueueSend is Zephyr k_msgq_put, ThreadX tx_queue_send, RT-Thread rt_mq_send.
  • FreeRTOS xSemaphoreTake is Zephyr k_sem_take, ThreadX tx_semaphore_get, RT-Thread rt_sem_take.
  • Priority inheritance, tickless idle, software timers, ISR-safe API variants — every modern RTOS has them. Only the spellings differ.

We picked FreeRTOS because:

  • It is MIT-licensed, free to use commercially, and shipped on more Cortex-M designs than every other RTOS combined.
  • Its kernel is tiny (about 9000 lines of C) — you can read every line.
  • The port for ARM Cortex-M is a model of clarity. PendSV for context switch, SysTick for tick, SVC to bootstrap. We will dissect it in lesson 2.
  • Amazon's stewardship since 2017 has stabilised the release process without changing the API. Code from 2012 still compiles against the current kernel.

If you finish this tutorial and then move to Zephyr because your employer requires it, you will spend a weekend learning the Kconfig + device-tree workflow and then feel right at home. The kernel concepts are the same.

Prerequisites

You should know C — pointers, function pointers, structs, the difference between static and extern, basic preprocessor. If any of that feels shaky, work through the C tutorial first; it is calibrated to set you up for embedded work.

You should also be comfortable bringing up a bare-metal project on Cortex-M — that is, you can write a startup file, configure a clock tree, get a blinking LED working without an RTOS. If you cannot, our Cortex-M lineup page and the exception model page give the background; lesson 2 will not re-derive any of that.

Hardware: an STM32F411 Nucleo-64 is the reference. Any Cortex-M3, M4, M7, or M33 board works with minor adjustments. The Nucleo has its built-in ST-Link debugger, a UART-over-USB bridge mapped to USART2, an LED on PA5, and a button on PC13 — enough to do everything we need.

The ten lessons

Cortex-M lineup
sort:
CoreArchPipeDMIPSFPUDSPMPUTZMVENotes
Cortex-M0ARMv6-M3-stage0.84/MHz—Subset of Thumb-2.
Cortex-M0+ARMv6-M2-stage0.95/MHz—Single-cycle I/O port.
Cortex-M23ARMv8-M Baseline2-stage0.99/MHz—Secure & non-secure worlds.
Cortex-M3ARMv7-M3-stage1.25/MHz—The classic mid-range core.
Cortex-M33ARMv8-M Mainline3-stage1.50/MHzSPUsed in nRF53, STM32L5/U5.
Cortex-M4ARMv7E-M3-stage1.25/MHzSPMost popular general-purpose MCU core.
Cortex-M55ARMv8.1-M4-stage1.6/MHzSP/DP4–5× DSP/ML uplift vs. M4.
Cortex-M7ARMv7E-M6-stage2.14/MHzSP/DPUsed in i.MX RT, STM32H7.
Cortex-M85ARMv8.1-M7-stage3.0/MHzSP/DPi.MX RT700, Renesas RA8.

How to use this tutorial

Read each lesson cover-to-cover before touching code. The lessons are written to be understood in one sitting; the code is at the end. Then build the code yourself — copy it, do not paste it. Typing the call sequences is part of how the API gets into your fingers.

Every lesson ends with two or three Exercises. They are not optional. The answers are in collapsed callouts at the end of each lesson, but resist looking until you have either solved the exercise on hardware or convinced yourself you know exactly why it works. Real embedded debugging is "stare at the symptom and form a hypothesis"; the exercises are calibrated to that.

If something behaves unexpectedly on your board, do not move on. RTOS bugs are vastly easier to diagnose in a five-task system than in a twenty-task system. Fix it now.

Conventions used throughout

Code is in C unless otherwise stated. Shell commands assume bash on Linux or macOS; Windows users can use WSL or the equivalent native tooling. Snippets that need vendor HAL calls (clock init, GPIO setup) abstract them as SystemClock_Config() and gpio_init() — the focus is the FreeRTOS API, not the vendor's clock-tree wizard.

Stack sizes are given in words, not bytes, because that is FreeRTOS's convention. configMINIMAL_STACK_SIZE = 128 means 128 × 4 = 512 bytes on Cortex-M. We will repeat this every time it matters.

Priority values follow FreeRTOS's "higher number is higher priority" convention. This is the opposite of NVIC priorities, which are "lower number is higher urgency." Lesson 5 explains why both conventions exist and how to keep them straight.

Cross-references

This tutorial is self-contained but every lesson links out to deeper reference material on this site. The most useful background pages:

Tools that pair well with the lessons:

Where to next

If you are ready, open Lesson 1 — Why an RTOS. It is the most boring lesson in the set — almost no code, just judgment. Read it anyway. Half the production RTOS bugs in the wild trace back to engineers who used an RTOS for a workload that did not actually need one, or who skipped an RTOS for one that did.

If you already know whether you need an RTOS and just want the code, jump to Lesson 2 — Hello FreeRTOS. You can always come back to lesson 1 after your first hang.