PSoC5LP RTOS
Lab 05 — Periodic Tasks & Timing Accuracy

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


1. Introduction & Objectives

A metronome, a sampling loop, a control loop — all need a steady period. This lab builds one on a passive buzzer and an LED, and shows why the obvious choice (vTaskDelay) slowly drifts, while xTaskDelayUntil holds a fixed grid. You measure the difference with a logic analyzer.

After this lab you can:

  • Explain timing jitter and why a relative delay drifts.
  • Use xTaskDelayUntil for a precise, drift-free period, seeding xLastWakeTime correctly.
  • Tell vTaskDelay (relative) from xTaskDelayUntil (absolute) and pick the right one.
  • Understand tick resolution (1 ms here) and what "deadline miss" means.
  • Measure period and jitter, and see what happens when the work exceeds the period.

2. Prerequisites

Hardware: CY8CKIT-059 PSoC 5LP Prototyping Kit; RobotSoC Kit Passive Buzzer, 1× LED + 220 Ω; optional logic analyzer on a timing pin.

Software: PSoC Creator 4.4 with the FreeRTOS V11 BaseProject.

Chapters (sync progress): Required — Chapter 2 (Timing & Priority: jitter, xTaskDelayUntil, seeding the wake time); Peripheral prerequisite — the passive buzzer is covered in the separate (non-FreeRTOS) Lab 06 (Buzzers).

3. Reading Materials / References

  • Chapter 2 slides — Timing & Priority (the jitter problem, the xTaskDelayUntil pattern, the metronome example).
  • RobotSoC Lab 06 — Passive & Active Buzzers (tone generation).
  • FreeRTOS API: vTaskDelay, xTaskDelayUntil, xTaskGetTickCount, pdMS_TO_TICKS.

4. Background & Theory of Operation

Tick resolution. Time in FreeRTOS is counted in ticks; here the tick is 1 ms (configTICK_RATE_HZ = 1000). All delays are rounded to whole ticks, so 1 ms is the finest period you can ask for — never expect microsecond scheduling from these calls.

Why vTaskDelay drifts. vTaskDelay(T) means "sleep for T from now" — it starts counting when the call runs, i.e. after your work. So the real period is work + T. Because the work time varies from cycle to cycle, each beat starts a little later than the last, and the error accumulates: after many cycles the metronome is noticeably off. That accumulating error is the drift.

Why xTaskDelayUntil does not. xTaskDelayUntil(&xLastWake, T) wakes the task at an absolute time: xLastWake + T. The call then advances xLastWake by exactly T, so the next wake is on the same fixed grid — independent of how long the work took. The beats land on seed, seed+T, seed+2T, … with no accumulation.

Seed once. xLastWake must be seeded once, before the loop, with xTaskGetTickCount(). Re-seeding it inside the loop re-introduces the drift, because you are again measuring from "now". After seeding, the delay call owns the variable — you do not set it yourself.

Jitter vs drift. Drift is the slow, cumulative error a relative delay builds up. Jitter is the small cycle-to-cycle variation around the target — caused by higher-priority tasks or ISRs running first. xTaskDelayUntil removes drift; jitter still exists but stays bounded and does not accumulate.

Deadline and overload. If the work ever takes longer than the period, xTaskDelayUntil cannot keep up: the target wake time is already in the past, so it does not block and the task runs late — a deadline miss. Keep the work well under the period, or raise the period.

5. RTOS Task Architecture

METRONOME TASK — A DRIFT-FREE PERIOD Metronome Task (priority 2) xLastWake = xTaskGetTickCount() seed ONCE, before the loop Buzzer_Tick(); LED toggle (the work) xTaskDelayUntil(&xLastWake, T) the call advances xLastWake by T each time → wakes on an absolute grid, no drift loop Passive Buzzer one short beep per beat LED toggles on the beat (scope it for drift) Key: seed xLastWake once, then let xTaskDelayUntil own it. vTaskDelay (relative) would drift by the work time each cycle.

One Metronome task: seed xLastWake once, then each cycle do the work (a short beep + LED toggle) and call xTaskDelayUntil. The buzzer gives the beat an audible mark and the LED gives it a scope-able edge. The dashed loop returns to the work step — the seed is outside the loop on purpose.

6. Relative Delay vs. Absolute Delay — Over Time

vTaskDelay (relative): the period is work + delay, so it drifts.

vTaskDelay - relative delay DRIFTS period = work + delay, and because the work time varies, each beat starts later than the ideal grid 0 200 400 600 800 1000 dashed blue = ideal 200 ms grid time (ms) WORK BEAT +30 +100 +140 drift grows every cycle Conceptual / idealized. WORK time varies each cycle; that is what makes the relative delay drift. Measure jitter on hardware.

Each beat starts later than the ideal 200 ms grid; the error (+30, +100, +140…) accumulates.

xTaskDelayUntil (absolute): every beat snaps back to the grid.

xTaskDelayUntil - absolute wake, NO drift wake = seed + k x T, so every beat snaps back to the fixed grid no matter how long the work took 0 200 400 600 800 1000 dashed blue = ideal 200 ms grid time (ms) WORK BEAT every beat lands on the grid Conceptual / idealized. WORK time varies each cycle; that is what makes the relative delay drift. Measure jitter on hardware.

The work time still varies, but each wake is seed + k × T, so the beats stay on the grid.

Build both in the lab and watch the LED edges on a scope: the relative version slides, the absolute version holds.

7. Schematic Circuit & Pin Configuration

  • Buzzer → GPIO (via a transistor if your passive buzzer needs current) → the beep pin.
  • LED → GPIO + 220 Ω → GND (toggles on the beat).
  • (Optional) TP → timing pin to a logic analyzer; pulse it high during the work.
  • Assign the pins in <project>.cydwr → Pins.

8. PSoC Creator Configuration

  • A PWM (or digital output + software tone) for the passive buzzer; a Digital Output Pin for the LED; optional timing pin.
  • FreeRTOSConfig.h (already set): configTICK_RATE_HZ = 1000, configUSE_PREEMPTION = 1, heap_4.

9. Provided Template

You are given Lab05_Template_main.c:

  • Given: all includes (note #include "FreeRTOS_PSoC.h"), the task handle, a beep() helper, and a complete main(). A USE_DELAY_UNTIL switch selects the absolute (Exp01) or relative (Exp02) version; the task has a placeholder so the project builds and runs.
  • You write: the vMetronomeTask() body — seed xLastWake once, then the work + xTaskDelayUntil loop (and the vTaskDelay version for Exp02).
  • Do not change the boot sequence in main().
static void vMetronomeTask(void *arg)
{
    TickType_t xLastWake;
    const TickType_t period = pdMS_TO_TICKS(BEAT_MS);
    xLastWake = xTaskGetTickCount();           /* seed ONCE, before loop */
    for (;;) {
        /* TODO(Exp01): TP_Write(1); beep(); LED_Write(!LED_Read()); TP_Write(0);
         *              xTaskDelayUntil(&xLastWake, period); */
        vTaskDelay(pdMS_TO_TICKS(BEAT_MS));    /* placeholder */
    }
}

10. Lab Experiments

Exp01 — Basic: a drift-free metronome. With USE_DELAY_UNTIL = 1, seed xLastWake once and beat every BEAT_MS using xTaskDelayUntil. Expected: a steady beep + LED; on a scope the period is BEAT_MS with only small jitter, no drift.

Exp02 — Advanced: watch the relative version drift. Set USE_DELAY_UNTIL = 0 to use vTaskDelay, and make the work time vary (e.g. a changing CyDelayUs). Capture the LED edges over many cycles. Expected: the period is work + BEAT_MS, so the beats slide later and later — visible drift against the xTaskDelayUntil version.

Exp03 — Challenge: measure jitter and a deadline miss. Measure period and jitter (max − min interval) at BEAT_MS = 100, 50, 10. Then make the work longer than the period and show xTaskDelayUntil can no longer keep up (deadline miss). Expected: a short table of period vs jitter, and a captured deadline miss with a one-line explanation.

11. Testing & Verification

#Pass condition
Exp01 xTaskDelayUntil gives a steady period = BEAT_MS, seeded once; no cumulative drift.
Exp02 vTaskDelay version drifts; the LED edges slide relative to the absolute version.
Exp03 Jitter table submitted; a deadline miss is shown when work > period.

Submit: your task body (both versions), scope/logic captures for Exp01 and Exp02, and the Exp03 jitter table.

12. Q&A & Troubleshooting

  • The metronome drifts even with xTaskDelayUntil → you re-seeded xLastWake inside the loop; seed it once, before the loop.
  • No beep → buzzer not started, wrong pin, or a passive buzzer driven without a tone/PWM.
  • Period is wrong by a fixed factor → configCPU_CLOCK_HZ or configTICK_RATE_HZ mismatch.
  • Beats bunch up / run late → the work exceeds the period; shorten the work or raise BEAT_MS.
  • Asked for < 1 ms period → below tick resolution; use a hardware timer for sub-millisecond timing.

13. Analysis Questions & Lab Report

  1. Why does vTaskDelay drift for a fixed period, and xTaskDelayUntil not?
  2. Why must xLastWakeTime be seeded once, before the loop?
  3. Distinguish drift from jitter. Which does xTaskDelayUntil remove, and which remains?
  4. What happens if the work takes longer than the period? How would you detect it?

Your report should include both task bodies, your captures, the jitter table, and answers to the four questions.

© 2026 Air Supply Information Center (Air Supply BBS)