PSoC5LP RTOS
Lab 23 — Observability & Debugging
FreeRTOS on PSoC 5LP · Lab Series Platform: CY8CKIT-059 (PSoC 5LP, Cortex-M3) · PSoC Creator 4.4 · FreeRTOS Kernel V11 Theme: a live dashboard — states, CPU %, stack, heap — plus configASSERT discipline Pairs with: Chapter 13 — Architecture & Reliability & Chapter 18 — Reliability & Watchdog
1. Introduction & Objectives
"It froze" is the hardest bug report to act on. The cure is observability: build the system so that, at any moment, you can see what every task is doing, who is using the CPU, how close each stack is to overflow, and whether the heap is leaking. FreeRTOS has the instrumentation built in — a trace facility and run-time stats — and this lab turns it into a one-screen dashboard printed over UART, backed by configASSERT and the fault hooks so problems fail loud and early instead of silently.
After this lab you can:
- Print a task table with vTaskList (state, priority, stack high-water) and CPU share with vTaskGetRunTimeStats.
- Configure the run-time stats timer and explain how CPU % is measured.
- Build your own dashboard programmatically with uxTaskGetSystemState.
- Use configASSERT, the stack-overflow and malloc-failed hooks as tripwires.
- Know when a once-a-second dashboard is enough and when to reach for a trace tool.
- (Extension) Register queues and semaphores by name with vQueueAddToRegistry so a kernel-aware debugger shows them by name, not by address.
2. Prerequisites
Hardware - CY8CKIT-059 PSoC 5LP Prototyping Kit - RobotSoC Kit: UART to a serial terminal; a free-running Timer/Counter for the stats clock; an LED
Software - PSoC Creator 4.4 with the FreeRTOS V11 BaseProject
Chapters (sync progress) - Required: Chapter 13 (architecture & reliability) and Chapter 18 (reliability & watchdog). - Related: Lab 13 (stack high-water, heap, hooks), Lab 18 (reset reason, watchdog), Lab 20 (a gatekeeper for the UART output).
3. Reading Materials / References
- Chapter 13 slides — Architecture & Reliability (visibility, asserts, measuring vs guessing).
- FreeRTOS docs — Run Time Stats, uxTaskGetSystemState, vTaskList, configASSERT.
- (Optional) Percepio SystemView / Tracealyzer for FreeRTOS trace.
- FreeRTOS API: vTaskList, vTaskGetRunTimeStats, uxTaskGetSystemState, uxTaskGetStackHighWaterMark, xPortGetFreeHeapSize, configASSERT.
- (Extension) vQueueAddToRegistry, vQueueUnregisterQueue, and configQUEUE_REGISTRY_SIZE — naming kernel objects for the debugger.
4. Background & Theory of Operation
The trace facility names the parts. Set configUSE_TRACE_FACILITY = 1 and the kernel keeps enough bookkeeping to report, for every task, its name, state, priority, stack high-water mark, and task number. uxTaskGetSystemState(array, len, &total) fills an array of TaskStatus_t with exactly this — the programmatic way to read the whole system. The convenience function vTaskList(buf) formats the same thing as a text table (it also needs configUSE_STATS_FORMATTING_FUNCTIONS = 1).
Run-time stats measure CPU share. To answer "who is hogging the CPU?" the kernel needs a clock far faster than the 1 ms tick, so it can time even short slices. You provide it with two macros — portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() (start a free-running counter, say 100 kHz+) and portGET_RUN_TIME_COUNTER_VALUE() (read it) — and set configGENERATE_RUN_TIME_STATS = 1. On every context switch the kernel adds the elapsed counter to the outgoing task's total. A task's CPU % is then simply its accumulated counter ÷ the grand total. vTaskGetRunTimeStats(buf) prints this table; or you read TaskStatus_t.ulRunTimeCounter and compute the percentage yourself.
Stack and heap, surfaced live. The two numbers from Lab 13 belong on the dashboard: uxTaskGetStackHighWaterMark(task) (smallest free stack ever — how close to overflow), and xPortGetFreeHeapSize() with xPortGetMinimumEverFreeHeapSize() (free now vs the low-tide watermark — leaking or near exhaustion). Together with state and CPU %, these answer "is the system healthy?" at a glance.
configASSERT — fail loud, fail early. configASSERT(x) is FreeRTOS's internal sanity check: the kernel calls it with conditions like "this priority is valid" or "this isn't called from a forbidden ISR". If you leave it undefined it does nothing and bugs stay silent; define it to stop and report __FILE__/__LINE__ (a breakpoint while debugging, a logged message in the field). It turns a mysterious later crash into a precise "assert at line 42". The stack-overflow and malloc-failed hooks (Lab 13/18) are the same philosophy applied to those two faults.
Extension — name your queues for the debugger (the queue registry). The run-time dashboard names tasks, but a stalled system usually hangs on a queue, semaphore, or mutex — and in a kernel-aware debugger (or vTaskList's "blocked on" column, with the right tooling) those objects show up only as bare hex addresses, which tell you nothing. The queue registry fixes this: call vQueueAddToRegistry(handle, "bus_mutex") right after you create each queue/semaphore/mutex, and the kernel keeps a small name↔handle table (sized by configQUEUE_REGISTRY_SIZE, which must be ≥ the number you register). A debugger's RTOS view, or a trace tool like SystemView/Tracealyzer, then lists them by name — so "Comms is Blocked on cmd_queue" instead of "Blocked on 0x20000A14". The registry is debug metadata only: it does not change behaviour, costs a few bytes per entry, and vQueueUnregisterQueue removes an entry if you delete the object. Register every long-lived synchronisation object you might need to recognise when the system is stuck — it is the object-level complement to pcTaskGetName for tasks.
Dashboard vs trace. The dashboard is cheap and always-on, but it is a once-a-second snapshot — it cannot show a 50 µs priority inversion or the exact order of ISRs and switches. For that, a trace tool (SystemView / Tracealyzer) instruments the kernel to record every task switch, ISR, and block on a timeline you scrub through. Use the dashboard for continuous health and the trace for deep, occasional investigation. Both cost a little CPU and RAM, so enable them during development and gate them behind a build flag for release.
5. RTOS Task Architecture
The kernel's trace facility and run-time stats counter feed a periodic Diagnostics task that calls uxTaskGetSystemState / vTaskList + vTaskGetRunTimeStats, adds stack and heap figures, and prints a table to the UART dashboard (optionally streaming to a trace tool). The tripwires — configASSERT and the two hooks — catch faults the instant they happen. The panels list what to watch and the fail-loud discipline.
6. Inside the Running System
Run-time stats — a fast counter times each task's slices, summed into CPU %:
A timer far faster than the tick timestamps every context switch; a task's CPU % is its accrued counter ÷ the total — exactly what vTaskGetRunTimeStats reports.
The live dashboard — one screen that answers "is the system healthy?":
Printed once a second over UART; each column — state, priority, stack free, CPU % — is a question you would otherwise have to guess at, with a heap line below.
The lesson both diagrams carry: measure, don't guess. A few numbers, refreshed every second, replace hours of speculation about what the system "might" be doing.
7. Schematic Circuit & Pin Configuration
- UART → the dashboard output to a serial terminal (route through a gatekeeper, Lab 20, if other tasks also print).
- Timer/Counter → a free-running counter for the stats clock (≫ tick rate).
- LED → optional, lit by a tripwire (assert/hook).
- Assign the pins in <project>.cydwr → Pins.
8. PSoC Creator Configuration
- A UART, a free-running Timer/Counter (for stats), an optional LED.
- FreeRTOSConfig.h (already set): configUSE_TRACE_FACILITY = 1, configUSE_STATS_FORMATTING_FUNCTIONS = 1, configGENERATE_RUN_TIME_STATS = 1 with portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() / portGET_RUN_TIME_COUNTER_VALUE() wired to the timer, configCHECK_FOR_STACK_OVERFLOW = 2, configUSE_MALLOC_FAILED_HOOK = 1, and configASSERT defined to a trap.
9. Provided Template
You are given Lab23_Template_main.c:
- Given: all includes (note #include "FreeRTOS_PSoC.h"), two demo workers with different CPU appetites, the stats-timer macro sketch, the two fault hooks, a vAssertCalled trap, and a complete main() that creates the workers and the Diagnostics task.
- You write: vDiagTask() — the formatted path (vTaskList + vTaskGetRunTimeStats + heap lines) and the programmatic path (uxTaskGetSystemState → compute CPU %) — plus the Exp03 tripwire exercises.
- Do not change the boot sequence in main().
/* the easy, formatted dashboard */
vTaskList(buf); out(buf); /* state, prio, stack free, num */
vTaskGetRunTimeStats(buf); out(buf); /* abs time + CPU % per task */
snprintf(buf, sizeof buf, "heap free: %u min-ever: %u\r\n",
(unsigned)xPortGetFreeHeapSize(),
(unsigned)xPortGetMinimumEverFreeHeapSize()); out(buf);
10. Lab Experiments
Exp01 — Basic: the live dashboard
Fill in vDiagTask to print vTaskList and vTaskGetRunTimeStats, plus the free-heap and min-ever lines, once a second. Expected: a readable table on the terminal — the busy worker shows a high CPU %, the light worker a low one, IDLE takes the rest; stack-free and heap figures update live.
Exp02 — Advanced: build the table yourself
Replace the formatted helpers with uxTaskGetSystemState: walk the TaskStatus_t array and compute each task's CPU % from ulRunTimeCounter / total, printing your own columns. Expected: the same information under your own control — the basis for a compact fixed-width dashboard like the mockup, and the understanding of where each number comes from.
Exp03 — Challenge: make the tripwires fire
Deliberately trigger each tripwire and confirm it reports precisely: pass a bad argument or priority to hit configASSERT (see the file/line); under-size a stack to fire the overflow hook; exhaust the heap to fire the malloc-failed hook. (Optional: connect SystemView/Tracealyzer and capture a task-switch timeline to see a fast event the dashboard misses.) Expected: each fault stops the system at a known point with a clear cause — the difference between a silent freeze and a located bug.
Exp04 — Extension: name your kernel objects (queue registry)
Set configQUEUE_REGISTRY_SIZE to at least the number of objects you create, and call vQueueAddToRegistry(h, "name") right after creating each queue, semaphore, and mutex. Deliberately deadlock a task on one of them, then inspect the system in a kernel-aware debugger (or your trace tool) and confirm the object appears by name rather than by address. Expected: blocked-on objects are identified by name, turning "Blocked on 0x2000…" into "Blocked on cmd_queue" — and a note on how much faster that makes locating the stall.
11. Testing & Verification
| # | Pass condition |
|---|---|
| Exp01 | A live dashboard prints state, priority, stack free, CPU %, and heap once a second. |
| Exp02 | uxTaskGetSystemState drives a self-built table with correct CPU % per task. |
| Exp03 | configASSERT and both hooks fire on cue and report a precise cause. |
| Exp04 | Queues/semaphores are registered with vQueueAddToRegistry and show by name in the debugger / trace view. |
Submit: your dashboard code (both paths), a capture of the live output, and your Exp03 tripwire demonstrations.
12. Q&A & Troubleshooting
- CPU % all zero → no run-time-stats clock; wire portCONFIGURE_TIMER_FOR_RUN_TIME_STATS / portGET_RUN_TIME_COUNTER_VALUE to a timer faster than the tick.
- vTaskList prints nothing / won't link → configUSE_TRACE_FACILITY or configUSE_STATS_FORMATTING_FUNCTIONS is 0.
- Dashboard output interleaves with other prints → route all UART output through a gatekeeper task (Lab 20).
- configASSERT never fires → it is undefined (a no-op); define it to call your trap with __FILE__/__LINE__.
- Stats percentages drift / wrap → the stats counter overflowed over a long run; it is a share, not an absolute — reset or accept the rollover.
- The dashboard itself distorts the numbers → formatting is heavy; print less often, keep the Diag task low priority, and remember stats cost a little CPU.
13. Analysis Questions & Lab Report
- What does the trace facility let you read about each task, and which two API calls expose it?
- How is a task's CPU % measured, and why must the stats clock be faster than the tick?
- What does configASSERT do, and why is defining it worth the effort?
- When is a once-a-second dashboard enough, and when do you need a trace tool instead?
- (Extension) What does the queue registry add, and why is naming a blocked-on object as useful for debugging a hang as naming the task?
Your report should include your dashboard code, a capture of the output, your tripwire demonstrations, your Exp04 registry result, and answers to the questions.