5.5.10 · D2Embedded Systems & Real-Time Software

Visual walkthrough — FreeRTOS — task creation, priorities, xTaskCreate

1,902 words9 min readBack to topic

Everything here refines the parent note FreeRTOS — Task Creation, Priorities, xTaskCreate. We assume you know nothing but "a CPU runs one instruction at a time."


Step 1 — One CPU, one lane of time

WHAT: we draw time as one lane and mark that only one box can sit in it at a time.

WHY: the entire reason a scheduler exists is that demand (many tasks want to run) exceeds supply (one lane). If there were infinite lanes, no scheduler would be needed. Fixing this picture in your head prevents the #1 beginner error: imagining tasks running "in parallel."

PICTURE: the lane is the white bar. The coloured block sitting inside it is whichever task is Running. Everyone else is stacked above the lane, waiting.

Figure — FreeRTOS — task creation, priorities, xTaskCreate

Step 2 — Each task carries two numbers we care about

Read the symbols like this:

  • — the urgency dial. Bigger = more urgent. (This is the FreeRTOS convention; see the parent note's steel-manned mistake #1.)
  • — the total count of priority levels you compiled in. The highest usable number is , not , because we start counting at .
  • — reserved home of the idle task; nothing useful should share it.

WHAT: we tag every waiting task with its number and its state .

WHY: the scheduler is stateless about your code — it does not know what readSensor() does. It only sorts tasks by these two numbers. So these are the only levers you actually control.

PICTURE: each waiting box now wears a number badge and a colour telling its state.

Figure — FreeRTOS — task creation, priorities, xTaskCreate

Step 3 — Throw away everyone who cannot run

We define the ready set — the only tasks eligible to win the CPU:

  • — the ready set, our shortlist of candidates.
  • — one task.
  • — that task's state. Only Ready/Running tasks pass the filter.

WHAT: we grey out and remove the Blocked/Suspended boxes, leaving a shortlist .

WHY: picking among all tasks would be wrong — you'd hand the CPU to something that is asleep waiting for a sensor. Filtering first is what makes blocking free (the vTaskDelay mechanism relies on exactly this).

PICTURE: faded boxes = out of the running; solid boxes = the shortlist .

Figure — FreeRTOS — task creation, priorities, xTaskCreate

Step 4 — The one rule: pick the maximum priority in the ready set

  • — the winning priority level, the single largest badge among the shortlist.
  • — "scan the ready set, keep the biggest." Why max and not min? Because in FreeRTOS bigger = more urgent. If FreeRTOS had used the UNIX "nice" convention it would be a min here — the operator literally encodes the convention.
  • — priority of candidate .

WHAT: among the solid boxes we crown the one with the biggest number.

WHY: this single line is preemptive priority scheduling. Everything else (preemption, starvation, round-robin) is a consequence of applying this rule again and again as states change.

PICTURE: an arrow drops the highest-badge box into the time lane; the rest keep waiting.

Figure — FreeRTOS — task creation, priorities, xTaskCreate

Step 5 — Preemption: re-run the rule the instant changes

Consider the parent's Worked Example 2: vLow at (never blocks) and vHigh at .

  • The set grew the moment vHigh's 10 ms delay expired.
  • jumped , so the winner changed, so the lane's occupant is forcibly swapped. The mechanics of that swap (saving registers into the TCB) are covered in Context Switching.

WHAT: we show the lane's occupant flip from vLow to vHigh exactly at the wake-up instant, then flip back.

WHY: this is why a real-time sensor read gets deterministic timing even while a low task hogs math — the rule re-fires and the high task cuts the line every single time.

PICTURE: a timeline where the blue vLow block is sliced open by a yellow vHigh block every 10 ms.

Figure — FreeRTOS — task creation, priorities, xTaskCreate

Step 6 — The tie case: equal priority ⇒ round-robin

  • The braced set is "all candidates matching the top level."
  • The cycle is round-robin — fair sharing, active only if configUSE_TIME_SLICING = 1. See Round-robin vs Preemptive Scheduling.

WHAT: two same-colour boxes take turns filling the lane, one tick apiece.

WHY: without a tie-breaker the scheduler would be undefined when priorities collide. Round-robin makes equal priority mean equal share, which is exactly what "two LEDs blinking together" needs.

PICTURE: the lane alternates L1, L2, L1, L2 in tick-wide slices.

Figure — FreeRTOS — task creation, priorities, xTaskCreate

Step 7 — The degenerate case: nobody is Ready

  • — the ready set always contains at least the idle task.
  • Therefore is never taken over an empty set; there is always a floor at .

WHAT: when all coloured boxes fade to Blocked, the grey idle box drops into the lane.

WHY: this is why a high-priority task that never blocks is catastrophic (parent mistake #3): it holds forever, the idle task never runs, so power-saving and the watchdog feed that live in idle never happen. The degenerate case teaches you to always block.

PICTURE: all user boxes greyed and blocked; the lane holds the lone idle task.

Figure — FreeRTOS — task creation, priorities, xTaskCreate

The one-picture summary

Every scheduling decision FreeRTOS ever makes is this loop: filter to Ready → take the max priority → break ties round-robin → re-run on every event, with idle as the floor.

Figure — FreeRTOS — task creation, priorities, xTaskCreate

one

many

Event fires: delay wakeup or queue or ISR

Rebuild ready set R

Compute Pstar = max priority in R

One task at Pstar or many

Run that task

Round robin one tick each

Wait for next event

Recall Feynman retelling — the whole walkthrough in plain words

There is one cook and one cutting board (Step 1: one CPU, one time lane). Every recipe card carries an urgency number and a status flag (Step 2). Before choosing, the manager throws away every card that says "waiting for the oven" — those cooks are asleep and don't count (Step 3). From what's left, he picks the card with the biggest urgency number and puts that cook on the board (Step 4: ). The instant a sleeping cook's timer dings, the manager re-checks; if the newly-awake cook is more urgent, he shoves the current cook aside mid-chop (Step 5: preemption). If two cards tie for the top number, they take turns, a few seconds each, forever (Step 6: round-robin). And if every real cook is asleep, there's a lazy backup cook who just naps on the board so it's never empty (Step 7: idle at priority 0). That entire story runs again on every ding — that's the whole scheduler.

Recall Self-test

Why is the scheduling operator max and not min? ::: Because FreeRTOS defines bigger priority number = more urgent, so the largest ready priority wins. A task is Blocked on vTaskDelay — is it in the ready set R? ::: No; only Ready/Running tasks are in R, so it cannot be chosen until its delay expires. Two tasks share the top priority P-star — what happens? ::: Round-robin time-slicing, one tick each (if configUSE_TIME_SLICING=1). Why can R never be empty? ::: The idle task at priority 0 is always Ready, so max is always defined. What triggers a re-evaluation of P-star? ::: Any event that changes R — a delay waking, a queue/semaphore signal, or an interrupt.