Real-time firmware is not defined by raw processor speed. It is defined by whether the system performs the required work within known timing constraints. Scheduling is therefore an engineering discipline: firmware must decide what runs, when it runs, what can preempt it, how long it may block, and whether every critical deadline remains achievable under worst-case load.
OSFEC.002 continues the Open Source Firmware Engineer Certification track from OSFEC.001: Microcontroller Architecture — Memory Maps, Registers, and Interrupts. The previous lesson established interrupts, latency, shared state, priorities, and the architectural bridge into context switching. This lesson develops those mechanisms into complete scheduling models.
The technician-level companion lesson is OSFTC.002: Serial Console and Boot Logs — UART, Baud Rate, Pinouts, Capture, and Recovery. Technician work observes system behavior. Firmware engineering determines how execution is structured so the system behaves predictably in the first place.
Real-time means deadline-aware
A system is real-time when correctness depends on both the logical result and the time at which that result is produced.
- Hard real-time: missing a deadline is considered a system failure.
- Firm real-time: late results have little or no value, though an isolated miss may not be catastrophic.
- Soft real-time: deadline misses reduce quality or responsiveness but do not necessarily invalidate the system.
Control loops, protection systems, motor commutation, power conversion, radio timing, industrial motion, and safety monitoring commonly contain real-time constraints. The correct scheduling architecture depends on the severity and frequency of the deadlines.
MIT OpenCourseWare: real-time behavior
The superloop model
The simplest firmware scheduler is often a superloop:
initialize();
while (1) {
read_inputs();
update_control();
service_comms();
update_outputs();
}
This architecture can be appropriate when every operation is short, execution time is bounded, timing requirements are loose, and task interactions remain simple.
The superloop becomes difficult when one function can block, when events arrive asynchronously, when some work is much more urgent than other work, or when a long operation delays everything behind it.
Cooperative scheduling
In cooperative scheduling, each unit of work runs until it voluntarily yields, blocks, or returns control to the scheduler.
The model is attractive because context changes occur at explicit locations. Shared-state reasoning can be simpler than in a fully preemptive system. The main risk is that one poorly behaved task can delay every other task.
A cooperative scheduler therefore requires a strong rule: no cooperative task may execute longer than the maximum latency tolerated by more urgent work.
Preemptive scheduling
In a preemptive scheduler, a higher-priority ready task can interrupt execution of a lower-priority task. The processor state of the interrupted task is preserved so it can resume later.
FreeRTOS describes its default single-core policy as fixed-priority preemptive scheduling with optional round-robin time slicing among equal-priority ready tasks. See FreeRTOS — Task Scheduling.
Preemption reduces response time for urgent work, but it introduces new engineering problems: shared resources, race conditions, non-reentrant code, priority inversion, stack sizing, context-switch overhead, and more complex worst-case timing analysis.
MIT OpenCourseWare: strong priorities and preemption
Task states
RTOS scheduling becomes easier to reason about when tasks are treated as state machines.
- Running: currently executing on a processor core.
- Ready: able to execute but waiting because another task has the processor.
- Blocked: waiting for time or an event such as a queue item, semaphore, notification, or I/O completion.
- Suspended: explicitly removed from scheduling until resumed.
FreeRTOS documents these task states and emphasizes that blocked tasks do not consume processor time while waiting. See FreeRTOS — Task States.
Ready is different from running
A task can be fully prepared to execute and still receive no CPU time because a higher-priority task remains ready. This distinction is central to real-time analysis.
A task that continuously performs work without blocking can starve lower-priority tasks. High-priority tasks therefore usually need a clear event-driven reason to run, then should block again when the urgent work is complete.
Priorities should represent urgency, not importance
A common design mistake is assigning high priority to code because the feature is “important.” In a real-time scheduler, priority should primarily reflect timing urgency and deadline structure.
A logging task may be operationally important but able to tolerate milliseconds of delay. A current-control loop may perform very little computation yet require service every 50 microseconds. The control loop should usually have higher scheduling urgency.
Fixed-priority scheduling
Many embedded RTOSes use fixed priorities. Each task is assigned a scheduling priority, and the highest-priority ready task runs.
This model is conceptually simple and maps well to periodic and event-driven embedded workloads. It also allows useful worst-case analysis when execution times, blocking, and activation rates are bounded.
The current University of Texas at Austin ECE445M real-time operating-systems course explicitly covers context switching, cooperative and preemptive multitasking, round-robin scheduling, thread states, synchronization, blocking semaphores, priority scheduling, and performance measurement. See UT Austin ECE445M — Embedded and Real-Time Operating Systems.
Time slicing
Time slicing allows equal-priority ready tasks to share processor time. It improves fairness but does not automatically improve real-time determinism.
For deadline-sensitive tasks, equal priority can make worst-case response dependent on how many peers are ready and where execution falls within the time slice. Systems with strict timing constraints should assign priorities intentionally and avoid relying on time slicing as a substitute for timing analysis.
Digi-Key: RTOS task scheduling
Context switching
A context switch changes execution from one task to another. The kernel must preserve enough processor state to resume the outgoing task later and restore the incoming task’s state.
Typical saved context includes general-purpose registers, stack pointer state, processor status, and architecture-specific state. RTOS ports may also manage floating-point context, privilege state, memory-protection configuration, and other features.
Arm’s 2026 Cortex-M learning path demonstrates kernel-style switching using SysTick and thread state, including a simple two-thread example. See Arm Learning Paths — Context Switching on Cortex-M.
The scheduler tick
Many RTOSes use a periodic hardware timer interrupt as a scheduler tick. The tick advances software time, releases delayed tasks whose wait period has expired, and may trigger a scheduling decision.
A 1 kHz tick provides 1 ms nominal tick resolution. That does not mean every task is limited to 1 ms timing precision; hardware timers, direct interrupts, tickless operation, and event-driven wakeups can provide finer timing where required.
Period and deadline are not the same concept
A periodic task may activate every 10 ms but still have a 2 ms deadline. The system must therefore distinguish:
- period: interval between task activations;
- release time: instant when a job becomes ready;
- deadline: latest acceptable completion time;
- execution time: processor time required to complete the job;
- response time: elapsed time from release to completion.
Worst-case execution time
Real-time analysis depends on a defensible bound for worst-case execution time (WCET). Average execution time is not enough.
WCET can be influenced by:
- input-dependent branches;
- cache behavior;
- flash wait states;
- DMA or bus contention;
- interrupt interference;
- critical sections;
- driver latency;
- memory allocation;
- logging;
- compiler optimization;
- fault handling or retries.
CPU utilization is necessary but not sufficient
A processor running at 40% average utilization can still miss a critical deadline if several tasks demand CPU time at the same instant.
Real-time design therefore evaluates the arrival pattern and interference among tasks, not only average load.
A scheduling example
Assume a single-core system contains these periodic tasks:
- control loop: period 1 ms, WCET 200 µs;
- sensor processing: period 5 ms, WCET 600 µs;
- communications: period 10 ms, WCET 800 µs;
- logging: period 100 ms, WCET 2 ms.
A simple utilization estimate is:
U = Σ(Ci / Ti)
For the example:
U = 0.2/1 + 0.6/5 + 0.8/10 + 2/100 = 0.42
The average modeled CPU demand is approximately 42%. This is useful capacity information, but it is not a proof that every deadline is met. Blocking, release phasing, interrupt load, preemption cost, and priority assignment still matter.
Rate-monotonic intuition
For independent periodic tasks with deadlines equal to their periods, a common fixed-priority rule is to assign higher priority to shorter periods. This is the intuition behind rate-monotonic scheduling.
Production systems rarely satisfy every textbook assumption. Shared resources, sporadic events, different deadlines, DMA, interrupts, and non-preemptible sections must be included in the real analysis.
Blocking versus busy-waiting
A busy-wait loop consumes CPU while waiting. A blocked task yields the processor until the required event occurs.
For example, waiting 20 ms for a queue item by repeatedly polling a flag wastes processor time and can starve lower-priority work. Blocking on the queue allows the scheduler to run useful work until the event arrives.
Interrupt service routines and tasks have different jobs
An ISR should usually handle the time-critical edge of an event, capture the necessary state, clear or acknowledge the source, and signal a task for larger processing.
The task can then perform parsing, filtering, protocol work, storage, or other operations without extending interrupt latency unnecessarily.
Priority inversion
Priority inversion occurs when a high-priority task is blocked by a resource held by a lower-priority task, while medium-priority tasks continue preempting the lower-priority owner and delay release of the resource.
Mutex implementations often use priority inheritance to reduce this problem by temporarily raising the priority of the resource owner. Priority inheritance limits one class of inversion but does not replace careful resource architecture.
Critical sections
A critical section protects a shared operation that must not be interrupted by conflicting access. Critical sections should be as short and bounded as possible because they increase blocking and can increase interrupt or scheduler latency.
Disabling interrupts around large computations is not a general synchronization strategy. It can silently destroy the timing guarantees that the RTOS was intended to provide.
Queues, semaphores, mutexes, and task notifications
- Queue: transfers data and synchronization between producers and consumers.
- Binary semaphore: commonly signals an event or resource availability.
- Counting semaphore: tracks multiple available events or resources.
- Mutex: protects ownership of a shared resource and may support priority inheritance.
- Task notification: lightweight direct signaling to a specific task in RTOSes that support it.
The synchronization primitive should match the communication model. Using one mechanism for every problem usually creates unnecessary complexity.
Stack sizing
Every task needs stack space for local variables, function calls, saved context, and architecture-specific state. Stack demand can increase through deep call chains, large local arrays, printf-family routines, floating-point context, recursion, and library behavior.
Engineering practice should include stack high-water measurements, fault detection, and margin rather than guessing stack size and assuming success because the system boots.
Jitter
Jitter is variation in the timing of repeated events. A control task intended to run every 1 ms may actually begin at 0.99 ms, 1.02 ms, 0.98 ms, and so forth.
Jitter can result from higher-priority interrupts, critical sections, scheduler behavior, DMA contention, cache/memory effects, or variable execution paths.
Measure scheduling behavior
Real-time firmware should expose evidence of its own timing behavior. Useful measurements include:
- task execution time;
- response time;
- deadline misses;
- interrupt latency;
- context-switch count;
- CPU utilization;
- stack high-water mark;
- queue depth;
- mutex hold time;
- scheduler lock duration;
- maximum critical-section duration;
- jitter.
GPIO timing pins, cycle counters, RTOS trace tools, logic analyzers, SWO/ITM, ETM, SEGGER SystemView, Percepio Tracealyzer, and vendor profilers can all contribute depending on the platform.
Tickless systems
Tickless idle reduces periodic scheduler interrupts when the system has no near-term work. This can reduce power consumption and unnecessary wakeups.
Tickless operation changes the timing implementation but not the requirement: the next deadline must still be serviced within its allowed bound.
Watchdogs belong in the scheduling model
A watchdog should prove that required progress is occurring, not merely that one high-priority task is alive.
A robust design may require several subsystems to report healthy progress before a watchdog service is permitted. Otherwise a scheduler fault could leave one task running continuously while the watchdog continues to be refreshed and the rest of the system remains failed.
Scheduling design checklist
- List every periodic, sporadic, and event-driven activity.
- Define period, deadline, and maximum acceptable response time.
- Estimate or measure worst-case execution time.
- Identify interrupt-driven work.
- Assign priorities from timing urgency.
- Identify every shared resource.
- Bound critical sections and non-preemptible regions.
- Identify all blocking operations.
- Check for priority inversion.
- Size task stacks with measurement and margin.
- Measure CPU load and worst-case latency.
- Track jitter and deadline misses.
- Validate behavior under worst-case event phasing.
- Repeat analysis after major firmware changes.
Exercises
- A control task runs every 2 ms and requires 300 µs of CPU time. Calculate its processor utilization.
- Explain why 40% average CPU utilization does not guarantee that all deadlines are met.
- Compare a superloop, cooperative scheduler, and preemptive RTOS for a system containing a 100 µs protection deadline and a 500 ms logging task.
- Explain why a high-priority task that never blocks can starve lower-priority tasks.
- Describe a priority-inversion scenario using high-, medium-, and low-priority tasks sharing a mutex-protected resource.
- Identify the difference between task period, deadline, execution time, and response time.
- Design an ISR-to-task handoff for a UART receive interrupt using a queue or task notification.
- Create a timing-measurement plan that records WCET, jitter, interrupt latency, stack high-water mark, and deadline misses.
Knowledge check
What makes a system real-time?
Correctness depends on completing required work within defined timing constraints, not only on producing the correct logical result.
What is preemption?
The scheduler suspends a currently running task so a higher-priority ready task can execute.
What is the difference between Ready and Blocked?
A Ready task can execute but is waiting for CPU time; a Blocked task is waiting for an event or time condition and is not eligible to run.
Why should priority reflect urgency rather than business importance?
Scheduling priority exists to satisfy timing constraints and deadlines.
What is WCET?
A defensible bound on the maximum processor execution time required by a task or code path under the analyzed conditions.
What is priority inversion?
A high-priority task is indirectly delayed by a lower-priority task that owns a required resource.
Why should an ISR usually defer large work to a task?
Long ISRs increase interrupt latency, block lower-priority interrupts, and make timing harder to bound.
What is jitter?
Variation in the timing of repeated events or task activations around their intended schedule.
Key takeaway
Real-time firmware scheduling is the controlled allocation of processor time under deadlines. A correct design does not merely create tasks and assign priorities; it defines task states, event flow, blocking behavior, execution-time bounds, preemption rules, shared-resource protection, context-switch cost, watchdog logic, and measurable timing evidence. The scheduler is only a mechanism. The timing architecture is the engineering work.
Engineering note: Timing examples in this lesson are educational. Production real-time analysis must use measured or justified WCET, actual interrupt rates, scheduler configuration, hardware timing, synchronization behavior, compiler settings, cache/memory characteristics, RTOS version, and the target processor’s architecture and errata.
Display note: this lesson uses standard Gutenberg paragraphs, headings, lists, preformatted code, and media embeds only. No decorative text-box or callout-box layout is used.
BitcoinVersus.Tech
Advertisement
Editor’s Note:
We volunteer daily to ensure the credibility of the information on this platform is Verifiably True. If you would like to support our research initiatives, please donate here: 3C9o19EH5HSiwEPyCTmEKzxhNCbo2X6TTb
BitcoinVersus.tech is not a financial advisor. This media platform reports on financial subjects purely for informational purposes.

Leave a comment