Schedulers and State Machines
Bare-metal firmware becomes maintainable when time-based work is scheduled explicitly and behavior is represented as states. This avoids hidden blocking delays and makes timing review possible.
Learning Objectives
By the end of this lesson, you should be able to replace blocking delays with periodic tasks, design a cooperative scheduler, model behavior as a finite state machine, and verify timing and transitions.
Why Blocking Delays Fail
read_sensor();
delay_ms(1000);
update_display();
During the delay, the firmware cannot respond to buttons, communication, or faults unless interrupts do all the work.
Cooperative Scheduler
typedef void (*task_fn)(void);
typedef struct {
task_fn run;
uint32_t period;
uint32_t next;
} task_t;
Each task must return quickly. Long operations are split into states.
State Machines
State machines prevent scattered flag logic and make reviews easier.
Worked Example: Sensor Polling
A nonblocking sensor transaction can use IDLE, START_CONVERSION, WAIT_READY, READ, FILTER, and FAULT. No state blocks for conversion time, so other tasks can run while the sensor is busy.
Timing Calculation
If tasks have periods 10 ms, 20 ms, and 100 ms, and worst-case runtimes 0.4 ms, 0.6 ms, and 2 ms:
$$
U=\frac{0.4}{10}+\frac{0.6}{20}+\frac{2}{100}=0.09
$$
About 9% of CPU time is scheduled work, leaving margin for interrupts and bursts.
Design Rules
- Use monotonic tick comparisons that tolerate wraparound.
- Keep task functions short and nonblocking.
- Give each state a clear exit condition.
- Log unexpected events and invalid transitions.
- Separate transition logic from hardware register access when possible.
Common Mistakes
- Calling blocking drivers from scheduled tasks.
- Updating
next_ms = now + period, which can accumulate jitter after delays. - Encoding state with many unrelated booleans.
- Forgetting timeout transitions.
- Letting one task monopolize the loop.
Summary
Schedulers decide when work runs; state machines decide what behavior happens next. Together they make bare-metal firmware deterministic, reviewable, and testable without requiring an RTOS.
Further Reading
- Miro Samek, "Practical UML Statecharts in C/C++."
- Jack Ganssle, "State Machines for Embedded Systems."
- FreeRTOS documentation for comparison with preemptive scheduling.