A minimal example of the State pattern implemented in C, running as a FreeRTOS task on a Raspberry Pi Pico 2 (RP2350).
State(include/state.h, src/state.c) is a single function pointer,run_state. It is the common shape every concrete state shares.Context(include/context.h, src/context.c) owns oneStateinstance per concrete state (idle,loop,sub,error) by value, plus acurrent_statepointer selecting the active one. No dynamic allocation anywhere: building aContextreserves all the storage the state machine will ever need in one block.- Transitions go through
SetState(ctx, id), which repointscurrent_stateat the matching field inside the sameContext. SubState(incontext.h) shows how to extendStatewith extra per-state data: it embeds aStateas its first field (super) plus one more field (value). Becausesupersits at offset 0 in memory, aSubStatecan be handed around as a plainState*(upcast, just&sub.super) and safely cast back toSubState*when the concrete state needs its own fields (downcast, inSubState_Run) — the classic C idiom for single-field polymorphism, nomallocor vtable involved.
Current chain of states, looping forever: idle → loop → sub → error → idle → ...
stateDiagram-v2
[*] --> idle
idle --> loop
loop --> sub
sub --> error
error --> idle
src/state.c is placeholder logic, not a real state
machine: each *_Run() just prints and calls SetState() to move to
the next one, purely to show the pattern's own mechanism (self/
context, and how a transition works). It's the starting point for
building an actual state machine on top of it - context->peripheral
(see below) is already wired up and ready to use once you replace
these bodies with real logic.
RunCurrentState() runs as a FreeRTOS task (xTaskCreate in
src/main.c): it starts at idle and loops forever, calling
current_state->run_state() and then vTaskDelay() between iterations
so it yields to the scheduler instead of busy-waiting.
include/ Public headers (context.h, state.h)
src/ Sources (main.c, context.c, state.c) + CMakeLists.txt
port/FreeRTOS-Kernel/ FreeRTOSConfig.h and the CMake glue to build the kernel
FreeRTOS_Kernel_import.cmake Pico-side FreeRTOS kernel import (SMP port)
CMakeLists.txt Top-level build: picks the board, pulls in the Pico SDK
and FreeRTOS kernel, adds src/
arm-none-eabi-gcctoolchain and CMake ≥ 3.12- pico-sdk, with
PICO_SDK_PATHpointing at it - FreeRTOS-Kernel, with
PICO_FREERTOSpointing at it (used asFREERTOS_KERNEL_PATH)
Target board is set in CMakeLists.txt: PICO_BOARD=pico2,
PICO_PLATFORM=rp2350.
export PICO_SDK_PATH=/path/to/pico-sdk
export PICO_FREERTOS=/path/to/FreeRTOS-Kernel
cmake -S . -B build
cmake --build buildThe build produces build/src/FreeRTOSC.uf2: hold BOOTSEL on the Pico,
plug it in, and copy the .uf2 file to the mass-storage device that
appears.
Connect over USB serial (stdio_init_all() in main.c enables both USB
and UART stdio) to see each state's printf as the machine walks its
states, one every 100ms.