PSoC5LP RTOS
Lab 04 — Preemptive Scheduling, Starvation & Time Slicing

FreeRTOS on PSoC 5LP · Lab Series. Platform: CY8CKIT-059 (PSoC 5LP, Cortex-M3) · PSoC Creator 4.4 · FreeRTOS Kernel V11. Pairs with Chapter 2 — Timing & Priority.


1. Introduction & Objectives

The scheduler decides who runs. Two mechanisms control that decision: priority + pre-emption (across different priorities) and time slicing (among equal priorities). This lab makes both visible. Part A: three tasks at different priorities show pre-emption and starvation. Part B: three tasks at the same priority show what configUSE_TIME_SLICING does — and, just as important, what it does not do.

Course default: this course builds with configUSE_TIME_SLICING = 1 (ON). Part B temporarily sets it to 0 to compare, then you set it back to 1.

After this lab you can:

  • State the pre-emption rule and show it on LEDs / a logic analyzer.
  • Explain starvation across priorities: a high-priority task that never blocks stops everything below it.
  • Explain time slicing among equal priorities: ON = tick round-robin; OFF = run-until-block.
  • Compare configUSE_TIME_SLICING = 0 vs = 1 — behaviour, pros, cons — and choose by scenario.
  • Change a task's priority at run time (vTaskPrioritySet / uxTaskPriorityGet) and yield with taskYIELD().

2. Prerequisites

Hardware: CY8CKIT-059 PSoC 5LP Prototyping Kit; RobotSoC Kit 3× LED + 3× 220 Ω; optional logic analyzer on 3 timing pins.

Software: PSoC Creator 4.4 with the FreeRTOS V11 BaseProject.

Chapters (sync progress): Required — Chapter 2 (Timing & Priority: pre-emption, priority by deadline, time slicing, run-time priority); Peripheral prerequisite — basic GPIO is covered in the separate (non-FreeRTOS) Lab 02.

3. Reading Materials / References

  • Chapter 2 slides — Timing & Priority (pre-emption, choosing priorities, time slicing, run-time priority & taskYIELD).
  • FreeRTOS API: vTaskPrioritySet, uxTaskPriorityGet, taskYIELD, vTaskDelay.

4. Background & Theory of Operation

4.1 Pre-emption and cross-priority starvation (Part A). With configUSE_PREEMPTION = 1, the scheduler always runs the highest-priority Ready task. The instant a higher-priority task becomes Ready it pre-empts whatever is running. If every task blocks (e.g. vTaskDelay), they take turns by priority and all make progress. Starvation is the failure mode: a high-priority task that never blocks stays Ready forever, so the scheduler never runs anything below it. The lesson: a task that never blocks breaks the system, no matter how it is prioritised.

4.2 Time slicing and equal-priority sharing (Part B). Time slicing decides what happens among tasks of equal priority:

  • configUSE_TIME_SLICING = 1 (ON — course default): among equal-priority Ready tasks, the scheduler round-robins on every tick (1 ms here). N equal tasks each get ~1/N of the CPU; even a long computation is chopped each tick so peers keep running.
  • configUSE_TIME_SLICING = 0 (OFF): no automatic rotation among equals. The running task keeps the CPU until it blocks or calls taskYIELD(). Equal-priority tasks that all block still take turns, but only at block boundaries (coarse); and a single equal task that never blocks starves all its equal peers.

4.3 The one rule that ties it together. Time slicing only affects EQUAL priority. It changes nothing across priorities: a higher-priority Ready task always pre-empts, and a higher-priority task that never blocks starves lower ones whether slicing is on or off. So cross-priority sharing is decided by priority + pre-emption, and equal-priority sharing by time slicing.

4.4 Run-time priority & yielding. uxTaskPriorityGet(NULL) reads the caller's priority; vTaskPrioritySet(h, p) changes a task's priority while it runs (may pre-empt at once). taskYIELD() voluntarily hands the CPU to an equal-priority peer — the only way to share between equals when slicing is off. Priority should be set by deadline, not importance (rate-monotonic).

5. RTOS Task Architecture

THREE TASKS, THREE PRIORITIES — ONE CPU the scheduler always runs the highest-priority Ready task (configUSE_PREEMPTION = 1) High Task priority 3 · drives LED_H + timing pin must block (vTaskDelay) or it starves the rest LED_H Medium Task priority 2 · runs only when High is blocked LED_M Low Task priority 1 · runs only when both above are blocked LED_L higher priority → wins the CPU Idle Task (priority 0) — runs only when every task above is blocked Preemption: the instant a higher task becomes Ready it takes the CPU. Starvation: a high task that never blocks pins the CPU, so Medium and Low never run. Every task — even an urgent one — must block or delay. ​

Part A uses three different priorities over one CPU: High (p3) pre-empts Medium (p2), which pre-empts Low (p1); Idle (p0) runs only when all are blocked. Part B instead gives three tasks the same priority, so pre-emption is out of the picture and time slicing alone decides how they share the CPU.

6. Part A — Cross-Priority: Preemption vs. Starvation

Every task blocks — all three make progress:

Preemptive scheduling - every task blocks High runs first, then blocks; Medium runs, then Low; Idle fills the gaps - all three make progress High (p3) Med (p2) Low (p1) Idle (p0) 0 100 200 300 400 500 600 time (ms) Conceptual / idealized: burst lengths exaggerated. Each ms the scheduler runs the highest-priority Ready task. Measure on hardware with timing pins. ​

High runs first, then blocks; Medium runs, then Low; Idle fills the gaps.

The High task busy-loops — Medium and Low are starved:

Starvation - the High task busy-loops (never blocks) after t = 50 ms the High task never yields the CPU, so Medium and Low are starved High (p3) Med (p2) Low (p1) Idle (p0) 0 100 200 300 400 500 600 time (ms) STARVED — Med & Low never run Conceptual / idealized: burst lengths exaggerated. Each ms the scheduler runs the highest-priority Ready task. Measure on hardware with timing pins. ​

After the High task stops yielding, nothing below it ever runs again. Slicing cannot fix this — it is a priority problem.

7. Part B — Equal Priority: Time Slicing ON vs. OFF

Now all three tasks share one priority. Priority no longer separates them, so the only thing deciding the CPU split is configUSE_TIME_SLICING.

= 1 (ON, course default): tick round-robin — fair, fine-grained sharing:

All tasks SAME priority - Time Slicing = 1 (ON) every tick the scheduler rotates A -> B -> C, so all three share the CPU evenly (fine interleave) Task A Task B Task C 0 100 200 300 400 500 600 time (ms) A, B, C, A, B, C ... rotating each tick Conceptual / idealized: slices drawn coarser than the real 1 ms tick for visibility. All three advance together. ​

Each tick the scheduler rotates A → B → C; all three advance together.

= 0 (OFF): run-until-block — coarse, one task at a time:

All tasks SAME priority - Time Slicing = 0 (OFF) no tick rotation: each task runs its WHOLE burst, then blocks and the next runs - coarse, one-at-a-time Task A Task B Task C 0 100 200 300 400 500 600 time (ms) A blocks -> B B blocks -> C If a task never blocks, it keeps the CPU forever and the other equal-priority tasks are starved (never run). Conceptual / idealized. With blocking, tasks round-robin only at block points (coarse); without blocking, the first task hogs the CPU. ​

Each task runs its whole burst, then blocks and the next runs. If a task never blocks, it hogs the CPU and the equal peers are starved.

8. Time Slicing: Behaviour, Trade-offs & How to Choose

 = 1 (ON) — course default= 0 (OFF)
Equal-priority behaviour Round-robin every tick; each equal task gets ~1/N CPU No rotation; run until block / taskYIELD; equal peers take turns only at block points
Pros Fair among equals; forgiving (a non-blocking equal task won't fully lock peers out); least surprising (FreeRTOS default) Deterministic worst-case timing; fewer context switches (lower overhead, better bursts, lower power); encourages distinct-priority design
Cons More context switches each tick; less deterministic; chops coherent bursts; more wake-ups (worse for tickless idle) No auto-fairness — a non-blocking equal task starves peers; needs disciplined design (always block/yield); reverse-intuitive for beginners

Choose by scenario:

ScenarioPick
General-purpose app; several comparable background tasks; want fairness & easy coding ON (= 1)
Hard real-time; rate-monotonic; need deterministic worst-case, low jitter OFF (= 0)
Throughput / long compute bursts you don't want chopped every tick OFF (= 0) (let a burst finish; give distinct priorities or yield)
Strictly event-driven (tasks mostly block on queues/semaphores/notifications) Either; OFF avoids needless switches
Low power with tickless idle OFF (= 0) (fewer forced wake-ups)

Remember: none of this helps across priorities — slicing only governs equal-priority tasks.

9. Schematic Circuit & Pin Configuration

  • LED_H / LED_M / LED_L (Part A) or LED_A / LED_B / LED_C (Part B) → three GPIOs + 220 Ω → GND.
  • (Optional) TP_x → three timing pins to a logic analyzer; pulse one high while its task runs.
  • Assign all pins in <project>.cydwr → Pins.

10. PSoC Creator Configuration

  • Three Digital Output Pin components for the LEDs; optional three more for timing pins.
  • FreeRTOSConfig.h: configUSE_PREEMPTION = 1, configUSE_TIME_SLICING = 1 (course default), configMAX_PRIORITIES ≥ 4, heap_4.
  • Part B toggles configUSE_TIME_SLICING to 0 to compare, then back to 1. Rebuild after each change.

11. Provided Template

Two templates are provided:

  • Lab04_Template_A_crosspriority_main.c — Part A. Three tasks at priorities 3 / 2 / 1; a HIGH_BUSY_LOOP switch demonstrates starvation.
  • Lab04_Template_B_equalpriority_main.c — Part B. Three tasks at the same priority driving LED_A/B/C (+ timing pins). A TASKS_BLOCK switch chooses "burst then block" vs "never block"; you flip configUSE_TIME_SLICING in FreeRTOSConfig.h to compare ON vs OFF.

Both have a complete main() and task placeholders so the project builds and runs; note #include "FreeRTOS_PSoC.h". Do not change the boot sequence in main().

/* Part B - three tasks, SAME priority */
#define TASKS_BLOCK 1   /* 1 = burst then vTaskDelay; 0 = never block */
/* ...create A,B,C all at tskIDLE_PRIORITY + 1u... */
static void vEqualTask(void *pvName)   /* one body, three instances */
{
    for (;;) {
        /* TODO: TP_x high; busy-work ~ burst; TP_x low; */
#if TASKS_BLOCK
        vTaskDelay(pdMS_TO_TICKS(5));   /* yields -> peers run */
#endif
        /* with TASKS_BLOCK=0 and slicing OFF, this task starves its peers */
    }
}

12. Lab Experiments

Exp01 — Basic: cross-priority preemption & starvation (Part A). Run Template A. First with blocking (HIGH_BUSY_LOOP = 0): all three LEDs blink, High leads but Med/Low still run. Then HIGH_BUSY_LOOP = 1: LED_M and LED_L freeze (starvation). Confirm slicing does not help — try toggling it and show Med/Low are still starved. Expected: you can explain cross-priority starvation and why slicing is irrelevant to it.

Exp02 — Advanced: equal-priority time slicing ON vs OFF (Part B). Run Template B with all three tasks at the same priority and TASKS_BLOCK = 1. (1) With configUSE_TIME_SLICING = 1 (default): the three timing pins interleave every ~1 ms; the LEDs look evenly active. (2) Rebuild with configUSE_TIME_SLICING = 0: the pins now run one full burst at a time. (3) Set TASKS_BLOCK = 0 (never block) with slicing = 0: only Task A runs — equal-priority starvation; switch slicing back to = 1: all three share. Then set configUSE_TIME_SLICING back to 1. Expected: captures showing fine interleave (ON) vs one-at-a-time (OFF), and the starvation case.

Exp03 — Challenge: run-time priority, yielding & the choice. From one equal task, vTaskPrioritySet(NULL, 2) to lift it above its peers and show it now pre-empts them (restore with the value from uxTaskPriorityGet). With slicing off, add taskYIELD() to two equal tasks and show they now share. Finally, write one paragraph: for a given requirement (e.g. hard-real-time control vs. general background logging), which configUSE_TIME_SLICING setting you would choose and why. Expected: a working boost + yield, and a justified slicing choice.

13. Testing & Verification

#Pass condition
Exp01 All three blink with blocking; busy-loop starves Med/Low; slicing does not change that.
Exp02a Slicing ON: equal-priority pins interleave each tick; all three advance.
Exp02b Slicing OFF: equal-priority tasks run one burst at a time; non-blocking task starves peers.
Exp03 Run-time boost pre-empts peers; taskYIELD shares under slicing off; slicing choice justified.
Cleanup configUSE_TIME_SLICING set back to 1 after the lab.

Submit: Part A and Part B captures (or LED descriptions), your task bodies, and your Exp03 slicing-choice paragraph.

14. Q&A & Troubleshooting

  • Only the High LED is active (Part A) → the High task never blocks; that is starvation, expected in Exp01's busy-loop case.
  • Equal tasks don't interleave even with slicing ON → they may not all be Ready (one is blocking long); or priorities aren't actually equal.
  • Equal tasks run one-at-a-time and never alternate (slicing OFF, no blocking) → expected: that is equal-priority starvation; add taskYIELD() or blocking.
  • Slicing didn't fix cross-priority starvation → correct — it never can; slicing is equal-priority only.
  • vTaskPrioritySet seems ignored → target above configMAX_PRIORITIES-1 is clamped.
  • Forgot to revert → set configUSE_TIME_SLICING = 1 before moving on.

15. Analysis Questions & Lab Report

  1. State the pre-emption rule and give the single change that turns Exp01 from "all run" into "starvation".
  2. With all tasks at the same priority, contrast configUSE_TIME_SLICING = 1 and = 0: what does the CPU timeline look like in each?
  3. Why can time slicing not fix cross-priority starvation?
  4. For a hard-real-time design and for a general-purpose background design, which slicing setting fits each, and why?

Your report should include your captures/observations, your task bodies, and answers to the four questions.

© 2026 Air Supply Information Center (Air Supply BBS)