Exercises — FreeRTOS — task creation, priorities, xTaskCreate
Before we start, three words we will lean on constantly:
Now a picture of the model every timing question below relies on:

Level 1 — Recognition
Here we only identify rules. No computation of timelines yet.
Exercise 1.1 (L1)
Two tasks are created:
xTaskCreate(vA, "A", 128, NULL, 2, NULL);
xTaskCreate(vB, "B", 128, NULL, 5, NULL);Both are READY. Which one runs, and why?
Recall Solution 1.1
B runs. The scheduler is preemptive and priority-based: the numerically largest READY priority always gets the CPU. Since , task B wins. (Trap: "priority 2 sounds more important" — it isn't. Bigger number = more urgent in FreeRTOS.)
Exercise 1.2 (L1)
You pass usStackDepth = 200 on a Cortex-M. How many bytes of stack does the task reserve?
Recall Solution 1.2
Picture 200 register-sized boxes stacked up; each box is 4 bytes wide on Cortex-M, so multiply the count of boxes by the width of one box: The "×4" is nothing mysterious — it's just "how many bytes fit in one word," the same way "×12" converts feet to inches.
Exercise 1.3 (L1)
Name the four task states and say which one uses zero CPU while waiting.
Recall Solution 1.3
Running, Ready, Blocked, Suspended. The Blocked state uses no CPU — the task is parked waiting for an event (a delay expiring, a queue item, a semaphore).
Level 2 — Application
Now we plug numbers into the rules.
Exercise 2.1 (L2)
configTICK_RATE_HZ = 1000. What does vTaskDelay(pdMS_TO_TICKS(250)) translate to in ticks?
Recall Solution 2.1
Think of the ruler analogy: a tick is one notch on the time-ruler, and here each notch is worth 1 ms (because 1000 notches fit in 1000 ms). To measure 250 ms you just count how many notches fit inside it:
So the task blocks for 250 ticks. (Because 250 is a whole number, pdMS_TO_TICKS has no fraction to truncate here — contrast the rounding edge case in the Tick definition.)
Exercise 2.2 (L2)
A task needs 1.5 KB of stack. sizeof(StackType_t) = 4. What usStackDepth (in words) should you pass? (Use 1 KB = 1024 bytes.)
Recall Solution 2.2
This is the reverse of Exercise 1.2: there we turned boxes into bytes (×4); here we have a byte budget and must find how many 4-byte boxes fit, so we divide by 4.
Pass usStackDepth = 384.
Exercise 2.3 (L2)
Your heap has 5000 bytes free. Each task needs stack 256 words plus a TCB of 100 bytes. How many such tasks can xTaskCreate fit before it returns errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY?
Recall Solution 2.3
Cost of one task = stack bytes + TCB bytes (remember: xTaskCreate allocates both the stack and the TCB):
Now, why a floor? Imagine laying 1124-byte bricks into a 5000-byte shelf. Four bricks use bytes and leave 504 bytes free — but a task is all-or-nothing: you cannot allocate "half a stack + half a TCB" and get a working task, so the 504 leftover bytes are useless for a 5th task. That's exactly what the floor (round-down) captures — count only the whole bricks that fit:
4 tasks fit; the 5th xTaskCreate fails because — the heap can't fit the 5th stack + TCB, so it returns errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY. See Heap memory management (heap_1..heap_5) in FreeRTOS for how the heap actually carves this out.
Level 3 — Analysis
Now we reason about timelines and who preempts whom.
Exercise 3.1 (L3)
void vHigh(void *p){ for(;;){ work_2ms(); vTaskDelay(pdMS_TO_TICKS(10)); } }
void vLow (void *p){ for(;;){ crunch(); } } // never blocks
xTaskCreate(vLow, "low", 256, NULL, 1, NULL);
xTaskCreate(vHigh, "high", 256, NULL, 3, NULL);work_2ms() takes 2 ms. Over a 30 ms window (assume both start at , vHigh runs first), how many milliseconds does vLow actually get on the CPU?
Recall Solution 3.1
First, vHigh's true loop period. One trip round its for(;;) does two things back-to-back: 2 ms of work_2ms(), then a vTaskDelay(10 ms) that puts it to sleep. vTaskDelay(N) sleeps for N ticks measured from the moment the delay call is reached — i.e. after the work. So the loop period is:
vHigh therefore becomes READY again at ms — a 12 ms rhythm, not 10 ms. (vTaskDelayUntil would instead fix a 10 ms period regardless of work length — see FreeRTOS — vTaskDelay vs vTaskDelayUntil.)
Now count the bursts inside the 30 ms window. vHigh runs a 2 ms burst starting at each wake:
- burst 1: ms
- burst 2: ms
- burst 3: ms
- next wake would be ms — outside the 30 ms window.
So exactly 3 bursts land in , totalling ms of high-priority CPU. Because , each burst preempts vLow instantly, and vLow (which never blocks) simply soaks up every remaining instant:
vLow gets 24 ms. The key subtlety: the 12 ms period and the 30 ms window happen to still give 3 bursts, but you must derive the period as 2+10 first — assuming "every 10 ms" would wrongly place wakes at 0,10,20,30 and mis-place the blocks. This is preemptive scheduling in action.

Exercise 3.2 (L3)
Same code, but now vHigh is also priority 1 (equal to vLow), and configUSE_TIME_SLICING = 1. Does vHigh still read its sensor every 10 ms reliably? Why or why not?
Recall Solution 3.2
No — not reliably. With equal priority, when vHigh's delay expires it becomes READY but does not preempt vLow; they merely round-robin one tick at a time. But vLow never blocks, so it keeps taking its turns. vHigh still eventually runs (it's not starved forever, because equal-priority tasks share), yet its exact wake-to-run latency now depends on where in the round-robin vLow is — timing becomes jittery, not deterministic. To guarantee 10 ms sensing, vHigh must be strictly higher priority. See FreeRTOS — vTaskDelay vs vTaskDelayUntil for tightening the period itself.
Exercise 3.3 (L3)
A task at priority 3 holds a mutex. A task at priority 5 then tries to take that same mutex and blocks. A priority-4 task is READY. Without any special mechanism, what unwanted thing can happen, and what feature fixes it?
Recall Solution 3.3
The priority-4 task can run and delay the priority-3 mutex holder, which in turn delays the higher priority-5 waiter — a lower-priority task effectively outranks a higher one. This is priority inversion. The fix is priority inheritance: the mutex temporarily boosts the holder (3) up to the waiter's priority (5) so it finishes and releases quickly. Full treatment in Priority Inversion and Priority Inheritance and FreeRTOS — Semaphores and Mutexes.
Level 4 — Synthesis
Now we design and combine ideas across features.
Exercise 4.1 (L4)
You must run: (a) a safety-critical motor watchdog every 5 ms, (b) a UART logger, (c) a background statistics computation. Assign priorities and describe the blocking strategy for each. Justify.
Recall Solution 4.1
A sound design:
| Task | Priority | Blocking strategy |
|---|---|---|
| Motor watchdog | 3 (highest) | vTaskDelayUntil(5 ms) — fixed period, must preempt everything |
| UART logger | 2 | Blocks on a [[FreeRTOS — Queues and inter-task communication |
| Statistics | 1 | May busy-compute; lowest so it never delays the others |
Why: the watchdog is time-critical, so it sits on top and blocks precisely every 5 ms (use vTaskDelayUntil, not vTaskDelay, to avoid drift). The logger is event-driven — blocking on a queue means it uses zero CPU until a message appears. Statistics is "whatever time is left," so it's lowest and is allowed to never block because nothing below it can starve. The idle task at priority 0 still runs when even statistics has nothing to do, feeding the hardware watchdog / entering low power.
Exercise 4.2 (L4)
An interrupt fires when a button is pressed. You want a task to handle the (slow) debounce + action. Sketch the correct architecture. Why can't you just call xTaskCreate inside the ISR?
Recall Solution 4.2
Correct architecture (deferred handling):
- Pre-create the handler task in
main()before the scheduler starts. - The task blocks waiting on a semaphore (or task notification).
- The ISR does almost nothing: it calls
xSemaphoreGiveFromISR(...)to signal the task, then returns. - The scheduler unblocks the handler task, which does the slow work at task level.
Why not xTaskCreate in the ISR: most creation/allocation paths are not ISR-safe — they may touch the heap and kernel lists with interrupts assumed enabled, risking corruption. ISRs must be short and use only ...FromISR APIs. See ISR-safe APIs (FromISR) and deferred interrupt handling.
Level 5 — Mastery
Deep, multi-constraint reasoning.
Exercise 5.1 (L5)
Heap free = 8 KB (8192 bytes), sizeof(StackType_t) = 4, TCB = 100 bytes each. You need:
- 1 control task: stack 512 words
- 2 sensor tasks: stack 256 words each
- 1 logger: stack 384 words
Does everything fit? If yes, how much heap remains? If no, which task fails? (Assume tasks are created in the order listed.)
Recall Solution 5.1
Cost per task = stack_words × 4 + 100 (stack + its TCB).
- Control:
- Sensor (each):
- Logger:
Running total against 8192: Everything fits. Remaining heap: (Real heaps also add small per-allocation alignment/overhead — see Heap memory management (heap_1..heap_5) in FreeRTOS — but on the model here, 2160 bytes remain.)
Exercise 5.2 (L5)
Three tasks, all priority 2, configUSE_TIME_SLICING = 1, configTICK_RATE_HZ = 1000. Each is pure CPU work and never blocks. Task X needs a total of 15 ms of CPU to finish one job. Assuming perfectly fair 1-tick round-robin, what wall-clock time passes before X finishes its job?
Recall Solution 5.2
Three equal tasks take turns one tick each, so the CPU hands X 1 tick out of every 3. Picture the timeline coloured X, Y, Z, X, Y, Z, … : for every 3 ms of wall-clock, exactly 1 ms is X's. To bank 15 ms of its own work X needs 15 of those turns, and each turn is 3 ms apart: ≈ 45 ms wall-clock to bank 15 ms of X's own execution — the other 30 ms went to the two peers. This is the cost of equal-priority sharing; if X were higher priority it would finish in ~15 ms.

Exercise 5.3 (L5)
Explain, using the state machine, exactly what sequence of state transitions a task goes through in one iteration of:
for(;;){ readSensor(); vTaskDelay(pdMS_TO_TICKS(10)); }when a higher-priority task exists.
Recall Solution 5.3
One iteration, with a higher-priority task present:
- Task is Running — executes
readSensor(). - Hits
vTaskDelay(10 ms)→ moves Running → Blocked (parked on a delay timer, 0 CPU). - Scheduler picks the highest READY task (possibly the higher-prio one, or idle).
- After 10 ms the delay expires → Blocked → Ready.
- When it is the highest READY priority, scheduler dispatches it → Ready → Running, loop repeats.
Note it never touches Suspended — that only happens via an explicit vTaskSuspend. The pairing of the TCB stores the stack pointer at step 2 and restores it at step 5, so readSensor() resumes seamlessly. This constant Blocked→Ready→Running cycle is why a well-written real-time task spends most of its life Blocked.
Recall Quick self-test (cloze)
The scheduler always runs the numerically largest READY priority.
usStackDepth is measured in words, so bytes = words × 4 on Cortex-M.
A task waiting on a delay is in the Blocked state and uses zero CPU.
Equal-priority tasks share via round-robin time-slicing.