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

OBSERVABILITY — SEE INSIDE THE RUNNING RTOS Trace facility configUSE_TRACE_FACILITY names, states, priorities Run-time stats counter high-freq timer (>> tick) per-task run time Diagnostics Task (periodic) uxTaskGetSystemState() / vTaskList() vTaskGetRunTimeStats() → CPU % + stack high-water, free heap format a table, once a second UART dashboard live table to a terminal Trace tool (optional) SystemView / Tracealyzer task-switch timeline Tripwires configASSERT() StackOverflowHook MallocFailedHook catch faults the instant they happen What to watch (and what it tells you) State (Run/Ready/Blocked/Suspended) — is a task stuck? CPU % per task — who is hogging the processor? Stack high-water — how close to overflow? (Lab 13) Free heap / min-ever — leaking or near exhaustion? Priority & task number — is the design as intended? Needs configUSE_TRACE_FACILITY = 1 and configGENERATE_RUN_TIME_STATS = 1. configASSERT & trace: fail loud, fail early · Define configASSERT() to stop and report file+line — it catches bad priorities, bad args, ISR misuse. · A silent system is the hard one to debug; a dashboard and asserts turn “it froze” into “Task X starved at L42”. · A trace tool shows the exact switch/ISR timeline when a dashboard’s once-a-second snapshot is too coarse. The dashboard is cheap & always-on; the trace is deep & occasional. Measure, don’t guess: stats cost a little CPU/RAM, so enable them while developing and strip them (or keep a light build flag) for release.

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 %:

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; each task accrues its run time Task A Task B Idle stats clk fast (>> tick) CPU % Task A 35% Task B 30% Idle 35% CPU% of a task = its accrued counter / the total — this is exactly what vTaskGetRunTimeStats() prints. Conceptual / idealized

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?":

The live dashboard — one screen that answers “is the system healthy?” Printed once a second over UART; every column is a question you would otherwise guess at === RTOS HEALTH (t=0:42:17) ============================== Task State Prio Stack free CPU% Sensor Blocked 2 184 B 11% Control Running 3 96 B 24% Comms Blocked 1 220 B 3% Diag Running 2 150 B 2% IDLE Ready 0 72 B 60% heap free: 3,912 B min-ever: 3,480 B asserts: 0 Read it at a glance: Control is Running at 24% with only 96 B of stack left; IDLE at 60% means headroom to spare. Conceptual / idealized

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

  1. What does the trace facility let you read about each task, and which two API calls expose it?
  2. How is a task's CPU % measured, and why must the stats clock be faster than the tick?
  3. What does configASSERT do, and why is defining it worth the effort?
  4. When is a once-a-second dashboard enough, and when do you need a trace tool instead?
  5. (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.

© 2026 Air Supply Information Center (Air Supply BBS)