RTOS · 21 pages
- RTOS tutorial — 10 lessonstutorial
- Lesson 1 — Why an RTOS
- Lesson 2 — Hello FreeRTOS
- Lesson 3 — Tasks
- Lesson 4 — Semaphores
- Lesson 5 — Mutex & priority inh.
- Lesson 6 — Queues
- Lesson 7 — Events & notifications
- Lesson 8 — Streams & timers
- Lesson 9 — Memory management
- Lesson 10 — Tickless idle + debug
- RTOS landscape
- Scheduling theory
- Priority inversion & inheritance
- IPC & synchronization primitives
- Tickless idle
- FreeRTOS
- Zephyr
- ThreadX (Azure RTOS)
- RT-Thread
- Safety-certified RTOS
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.
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.
- 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
xTaskCreateis ZephyrK_THREAD_DEFINE, ThreadXtx_thread_create, RT-Threadrt_thread_create. - FreeRTOS
xQueueSendis Zephyrk_msgq_put, ThreadXtx_queue_send, RT-Threadrt_mq_send. - FreeRTOS
xSemaphoreTakeis Zephyrk_sem_take, ThreadXtx_semaphore_get, RT-Threadrt_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
| 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
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:
- FreeRTOS reference — API summary you can return to between lessons.
- The RTOS landscape — when you graduate to Zephyr, ThreadX, or RT-Thread.
- Priority inversion — long-form on the Mars Pathfinder story; lesson 5 references this.
- IPC primitives — when to pick a queue versus a stream buffer versus a notification.
- Scheduling — fixed-priority pre-emptive context, including rate-monotonic analysis.
- Tickless idle — lesson 10 builds on this.
- NVIC priority — required reading before lesson 5.
- SysTick — the heartbeat under FreeRTOS.
- Exception model — PendSV, SVC, and how they cooperate with the scheduler.
Tools that pair well with the lessons:
- NVIC priority simulator — visualise PRIGROUP splits.
- Interrupt latency calculator — model context-switch overhead.
- Stack frame visualiser — see exactly what gets pushed on a context switch.
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.