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
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
| API | Purpose |
|---|---|
| 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 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.
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
- In the superloop, why does the button response time grow with BLINK_MS?
- What is the difference between CyDelay() and vTaskDelay()? Why does it matter here?
- In the RTOS version, why does the button stay responsive even with a 1000 ms blink?
- A blocked task is said to "cost no CPU." What does that mean, and how does the scheduler use it?
- (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.