PSoC5LP RTOS
Lab 01 — Bare-Metal Superloop vs. RTOS

FreeRTOS on PSoC 5LP · Lab Series Platform: CY8CKIT-059 (PSoC 5LP, Cortex-M3) · PSoC Creator 4.4 · FreeRTOS Kernel V11 Pairs with: Chapter 0 — Why an RTOS?


1. Introduction & Objectives

Before learning any RTOS API, you need to feel the problem an RTOS solves. In this lab, you run a traditional bare-metal superloop that blinks an LED and watches a button, measure how slowly the button responds, and then fix it with two FreeRTOS tasks. The same two jobs, two ways — and a stopwatch on the difference.

After this lab you can:

  • Explain why a superloop couples unrelated jobs, so one slow step delays the others.
  • Measure button response time and show it grows with the blink delay.
  • Describe how an RTOS runs independent tasks, and why a blocked task costs no CPU.
  • Build and run a first two-task FreeRTOS project, and confirm that the button remains responsive.
  • State the difference between CyDelay() (busy-wait) and vTaskDelay() (block).
  • (Extension) Pass per-instance data through pvParameters and run one task function as several independent tasks.

2. Prerequisites

Hardware - CY8CKIT-059 PSoC 5LP Prototyping Kit - RobotSoC Kit: 2× LED + 2× 220 Ω, 1× push button - USB-UART optional (for notes); a stopwatch or phone timer for response measurement

Software - PSoC Creator 4.4 with the FreeRTOS V11 BaseProject

Chapters (sync progress) - Required: Chapter 0 — Why an RTOS? - Peripheral prerequisite: basic GPIO (LED/switch) is covered in the separate (non-FreeRTOS) Lab 01–03.

3. Reading Materials / References

  • Chapter 0 slides — Why an RTOS? (superloop limits, task states, first FreeRTOS project).
  • FreeRTOS API: xTaskCreate, vTaskDelay, vTaskStartScheduler.
  • PSoC Creator component datasheets: Digital Output Pin, Digital Input Pin.
  • (Extension) Task parameters: the void *pvParameters argument of xTaskCreate — one function body, many instances with per-instance data.

4. Background & Theory of Operation

The superloop. The simplest firmware is one endless loop that does every job in turn. It works until two jobs have different timing needs. Here, one job blinks an LED on a period T, the other must react to a button quickly. In the loop the blink uses CyDelay(T) — a busy-wait that spins the CPU for T milliseconds doing nothing. The button is polled only once per loop, so a press that occurs during the delay is not detected until the delay ends.

Response time couples to the delay. The worst-case button response is therefore about T. Raise T from 100 ms to 1000 ms to make the blink slower, and the button becomes visibly sluggish — not because the button changed, but because the two jobs share one loop. That coupling is the superloop's fundamental limit; adding more jobs only makes it worse (poor scalability).

What an RTOS changes. An RTOS runs each job as an independent task, and a scheduler decides which Ready task runs. The key idea: a task that is waiting does so by blocking, not spinning. vTaskDelay(T) puts the blink task to sleep for T and frees the CPU the whole time. So the button task — a separate task — keeps running and reacts within about one scheduler tick, no matter how large T is. The jobs are decoupled.

CyDelay vs vTaskDelay. CyDelay(ms) busy-waits: the CPU is 100% busy and unavailable. vTaskDelay(pdMS_TO_TICKS(ms)) blocks: the task leaves the CPU, others run, and it returns to Ready when the time is up. Using CyDelay inside a task would re-create the superloop problem — so in RTOS code, delays are blocking delays.

Extension — one function, many tasks (pvParameters). A task does not need its own hand-written function. xTaskCreate(fn, name, stack, pvParameters, prio, &handle) passes the fifth argument pvParameters straight into fn(void *arg), so the same function can run as several independent tasks, each told apart by its parameter. Blinking three LEDs at three rates is then one vBlinkTask created three times — pass a small struct ({ pin, period }) as pvParameters and read it at the top of the task. Each instance gets its own stack and its own copy of local variables, so they never interfere; only truly shared data needs protection (the subject of the later synchronization labs). The rule: cast arg back to your type on entry, and make sure the pointed-to data outlives the task (a static or global struct, never a stack local of main that disappears). This is how real systems scale — N identical sensors or channels become N instances of one tested function, not N copy-pasted functions.

5. RTOS Task Architecture

BARE-METAL SUPERLOOP one loop does everything · jobs are coupled while (1) LED_Toggle() blink the status LED CyDelay(T) — BLOCKS CPU spins; nothing else runs if (Button) react polled once per loop Button lag ≈ up to T raise T → worse response FreeRTOS — TWO TASKS independent jobs · scheduler decides Blink Task priority 1 · vTaskDelay(T) blocks for T → CPU is free Button Task priority 2 · reacts to the press runs within ~1 tick, independent of the blink period T Scheduler (pre-emptive) The problem: in the superloop a long CyDelay blocks the button poll, so response time grows with the blink period. The fix: a blocked task costs no CPU, so the button task stays responsive no matter how long the blink task sleeps. ​

Left — the bare-metal superloop: one loop blinks, busy-waits, then polls the button, so response lag grows with the blink period. Right — the RTOS fix: a Blink Task (priority 1) that blocks in vTaskDelay, and a Button Task (priority 2) that reacts independently. This two-task shape is the seed of every later lab.

6. Schematic Circuit & Pin Configuration

  • LED_STATUS → GPIO + 220 Ω → GND (the blinking LED).
  • LED_ACT → GPIO + 220 Ω → GND (lights when the button is pressed).
  • BTN → push button to GND, internal pull-up (active-low: pressed reads 0).
  • Assign all three in <project>.cydwr → Pins.

7. PSoC Creator Configuration

  • Two Digital Output Pin components (LED_STATUS, LED_ACT), one Digital Input Pin (BTN, resistive pull-up, initial state high).
  • FreeRTOSConfig.h (already set in the BaseProject): configUSE_PREEMPTION = 1, heap_4, configMAX_PRIORITIES ≥ 3.

8. FreeRTOS API Reference

APIPurpose
xTaskCreate(fn, name, stack, arg, prio, &handle) Create a task from the heap. Returns pdPASS or a fail code.
vTaskDelay(pdMS_TO_TICKS(ms)) Block the calling task for a time; frees the CPU (unlike CyDelay).
vTaskStartScheduler() Start the scheduler; does not return.
CyDelay(ms) Bare-metal busy-wait (reference version only; never inside a task).

9. Provided Template

Two files are provided:

  • Lab01_Superloop_main.c — the bare-metal version, complete. Build and run it, then measure the button response as you change BLINK_MS. You do not edit it; you observe it (Exp01 / Exp02).
  • Lab01_Template_rtos_main.c — the RTOS version. All includes (note: #include "FreeRTOS_PSoC.h", which defines FreeRTOS_Init() — it builds the PSoC 5LP interrupt vector table), both task handles, and a complete main() that creates both tasks (blink priority 1, button priority 2) and starts the scheduler. Both task functions have a placeholder so the project builds and runs immediately. You write the bodies of vBlinkTask() and vButtonTask() (Exp03).
  • Do not change the boot sequence in main().
#include "FreeRTOS.h"
#include "FreeRTOS_PSoC.h"   /* FreeRTOS_Init(): builds the PSoC 5LP vector table */
#include "task.h"

/* blink task - you fill this body */
static void vBlinkTask(void *arg)
{
    for (;;) {
        /* TODO(Exp03): LED_STATUS_Write(!LED_STATUS_Read());
         *              vTaskDelay(pdMS_TO_TICKS(BLINK_MS));  // BLOCKS */
        vTaskDelay(pdMS_TO_TICKS(BLINK_MS));   /* placeholder */
    }
}

10. Lab Experiments

Exp01 — Basic: measure the superloop

Build and run Lab01_Superloop_main.c with BLINK_MS = 100. Press the button at random moments and estimate how long until LED_ACT lights. Expected: a noticeable, variable lag up to about the blink period.

The timing diagram below traces the idea: a press is only seen at the next loop poll, so the lag varies and can approach T.

Superloop response lag (blink T = 500 ms) a press is only seen at the next loop poll, so lag grows with T 0 200 400 600 800 1000 1200 1400 1600 time (ms) dashed verticals = loop poll points (every T) PRESS RESP 410 ms 480 ms 490 ms 170 ms Conceptual/idealized. Worst-case superloop lag ≈ the blink period T; RTOS response is ~1 tick regardless of T. Measure on hardware. ​

Superloop with T = 500 ms — response lag varies and approaches the blink period.

Exp02 — Advanced: make it worse on purpose

Raise BLINK_MS to 250, 500, then 1000. At each value, press the button and estimate the worst-case response. Record a small table of BLINK_MS vs observed worst-case lag. Expected: the lag grows roughly with BLINK_MS — proof that the two jobs are coupled.

Exp03 — Challenge: fix it with the RTOS

Open Lab01_Template_rtos_main.c (with BLINK_MS = 1000) and write the two task bodies. The blink task must block with vTaskDelay (not CyDelay). Build, run, and measure the button response again. Expected: the button responds within a few ms even though the blink period is 1000 ms — the jobs are now decoupled.

FreeRTOS response (independent button task) the button task runs within ~1 tick, whatever the blink period 0 200 400 600 800 1000 1200 1400 1600 time (ms) PRESS RESP 8 ms 8 ms 8 ms 8 ms Conceptual/idealized. Worst-case superloop lag ≈ the blink period T; RTOS response is ~1 tick regardless of T. Measure on hardware. ​

RTOS — the button task reacts within ~1 tick, independent of the blink period.

Exp04 — Extension: one function, three blink tasks via pvParameters

Write a single vBlinkTask(void *arg) that reads a BlinkCfg { pin, period_ms } from arg, and create it three times with three different static configs (e.g. 200 / 500 / 1000 ms on three LEDs). Confirm that all three blink independently at their own rates from a single function body. Expected: three LEDs on three periods, no duplicated task code; each instance runs the same code with its own parameter and its own stack.

11. Testing & Verification

#Pass condition
Exp01 Superloop runs; button response is visibly delayed and variable.
Exp02 Table of BLINK_MS vs worst-case lag shows lag growing with the period.
Exp03 RTOS version: button responds fast (a few ms) with BLINK_MS = 1000.
Exp04 One vBlinkTask created three times with different pvParameters blinks three LEDs at three independent rates.

Submit: your Exp02 table, your two RTOS task bodies, and a one-line explanation of why the RTOS button stays fast.

12. Q&A & Troubleshooting

  • Nothing blinks (RTOS) → FreeRTOS_Init() missing/after task creation, or RTOS_USED not defined; the scheduler faults.
  • Button never responds (RTOS) → the blink task uses CyDelay instead of vTaskDelay, re-creating the superloop; or the button task never delays and starves others.
  • Button double-fires → contact bounce; add a short debounce delay.
  • Both LEDs wrong polarity → check active-low wiring and the pin's initial drive state.

13. Analysis Questions & Lab Report

  1. In the superloop, why does the button response time grow with BLINK_MS?
  2. What is the difference between CyDelay() and vTaskDelay()? Why does it matter here?
  3. In the RTOS version, why does the button stay responsive even with a 1000 ms blink?
  4. A blocked task is said to "cost no CPU." What does that mean, and how does the scheduler use it?
  5. (Extension) How does pvParameters let one function run as several tasks, and why must the data it points to outlive the task?

Your report should include your measurements, your task bodies, and answers to the questions.

© 2026 Air Supply Information Center (Air Supply BBS)