PSoC5LP RTOS
Lab 13 — Memory: Heap, Stack & Faults
FreeRTOS on PSoC 5LP · Lab Series. Platform: CY8CKIT-059 (PSoC 5LP, Cortex-M3) · PSoC Creator 4.4 · FreeRTOS Kernel V11 · heap_4. Theme: watch the heap and each task's stack; catch faults with the two hooks. Pairs with Chapter 14 — Memory, Heap & Stack.
1. Introduction & Objectives
Every task, queue, and semaphore you have created so far came out of one pool — the FreeRTOS heap — and every task runs on its own stack. When either runs out, the symptom is a mysterious HardFault far from the real cause. This lab makes memory visible: a Monitor task reports the free heap, the lowest it has ever reached, and how close each task came to overflowing its stack. Then you deliberately break both and catch the faults with the stack-overflow and malloc-failed hooks.
After this lab you can:
- Read the live free heap (xPortGetFreeHeapSize) and its low-tide watermark (xPortGetMinimumEverFreeHeapSize).
- Read a task's stack high-water mark (uxTaskGetStackHighWaterMark) and right-size its stack.
- Enable and use vApplicationStackOverflowHook and vApplicationMallocFailedHook.
- Explain what heap_4 coalescing does, and the difference between dynamic and static allocation.
- (Challenge) Read per-task CPU share with run-time stats.
2. Prerequisites
Hardware: CY8CKIT-059 PSoC 5LP Prototyping Kit; RobotSoC Kit 1× LED (fault indicator); a UART-to-USB path (or the KitProg UART) for the monitor text.
Software: PSoC Creator 4.4 with the FreeRTOS V11 BaseProject (configured for heap_4).
Chapters (sync progress): Required — Chapter 14 (Memory, Heap & Stack: heap schemes, the TCB + stack, overflow); Peripheral prerequisite — basic UART output is covered in the separate (non-FreeRTOS) Lab 03; GPIO (the fault LED) in Lab 02.
3. Reading Materials / References
- Chapter 14 slides — Memory, Heap & Stack (heap_1..5, static vs dynamic, the stack high-water mark).
- FreeRTOS API: xPortGetFreeHeapSize, xPortGetMinimumEverFreeHeapSize, uxTaskGetStackHighWaterMark, vTaskGetRunTimeStats, xTaskCreateStatic.
4. Background & Theory of Operation
One heap, many objects. With dynamic creation (xTaskCreate, xQueueCreate, …) the kernel carves each object's TCB and stack out of a single array of size configTOTAL_HEAP_SIZE. This course uses heap_4, which coalesces adjacent freed blocks back together — so repeatedly creating and deleting objects does not permanently fragment the pool the way heap_2 can.
Two numbers to watch. xPortGetFreeHeapSize() tells you the free bytes right now; xPortGetMinimumEverFreeHeapSize() remembers the lowest the free heap has ever reached — the low-tide watermark. The second is the one that matters: a design can look fine at idle yet have come within a few bytes of exhaustion during a burst. Note a subtlety that coalescing cannot hide: free bytes can exist while no single contiguous block is large enough for the next request. Watch both the total and the worst moment.
Each task owns a stack. You pass a stack depth (in words, not bytes) to xTaskCreate. Local variables, the call chain, and anything the compiler spills live there. Too small and the task overflows into whatever sits next door — the classic cause of a corruption that shows up much later.
The stack high-water mark. uxTaskGetStackHighWaterMark(handle) returns the smallest amount of free stack that task has ever had (in words). A large number means plenty of headroom; a number near 0 means it nearly overflowed. You measure this, then set the stack to the peak plus a safe margin — never guess.
Two safety nets.
- vApplicationStackOverflowHook(task, name) fires when a task runs off its stack. Enable it with configCHECK_FOR_STACK_OVERFLOW = 2 (the more thorough check).
- vApplicationMallocFailedHook() fires when pvPortMalloc returns NULL. Enable it with configUSE_MALLOC_FAILED_HOOK = 1.
Both turn a silent, delayed corruption into an immediate, named failure you can see on an LED or UART.
Static vs dynamic. xTaskCreateStatic lets you supply the stack buffer and TCB yourself, so the task uses no heap at all — deterministic memory, known at link time, at the cost of more boilerplate. Dynamic is simpler; static is for when you must prove nothing is allocated at run time.
5. RTOS Task Architecture
A Monitor task periodically reads the heap and per-task stack marks and reports them. The worker tasks consume stack (and, in Exp03, heap). Everything — TCBs, stacks, queues — comes from the single heap_4 region, whose freed blocks coalesce. The two hooks are the safety nets; right-sizing turns the measured marks into correct stack sizes.
6. Memory Over Time
Stack: the high-water mark is the peak usage, measured against the allocated ceiling:
The mark is the headroom left between the deepest-ever stack use and the allocated size.
Heap: creations consume it; the watermark records the worst moment:
A free() bumps the free heap back up, but the min-ever-free watermark never rises — it remembers the low tide.
The rule both diagrams teach: a number that looks safe on average can still be dangerous at its worst moment. Size stacks from the high-water mark and size the heap from the low-tide watermark, each with a margin.
7. Schematic Circuit & Pin Configuration
- LED (fault) → GPIO + 220 Ω → GND; driven inside the two hooks.
- UART → the monitor channel (KitProg UART or a USB-UART bridge).
- Assign the pins in <project>.cydwr → Pins.
8. PSoC Creator Configuration
- A UART component for the monitor text; a Digital Output Pin for the fault LED.
- FreeRTOSConfig.h (already set): configCHECK_FOR_STACK_OVERFLOW = 2, configUSE_MALLOC_FAILED_HOOK = 1, configUSE_TRACE_FACILITY = 1, and for Exp03 run-time stats configGENERATE_RUN_TIME_STATS = 1 with a high-frequency counter wired to the two portCONFIGURE_TIMER_* / portGET_RUN_TIME_COUNTER_VALUE macros. Heap scheme: heap_4.
9. Provided Template
You are given Lab13_Template_main.c:
- Given: all includes (note #include "FreeRTOS_PSoC.h"), the task handles, a complete main() that creates two workers and a monitor, and both hooks as stubs. A report() helper sketches the UART output.
- You write: vMonitorTask() (read and report the three memory figures), the Exp02 stack-shrinking, and the Exp03 malloc loop.
- Do not change the boot sequence in main().
static void vMonitorTask(void *arg)
{
(void)arg;
for (;;)
{
report("freeHeap=", (unsigned)xPortGetFreeHeapSize());
report("minEverFree=", (unsigned)xPortGetMinimumEverFreeHeapSize());
report("markA=", (unsigned)uxTaskGetStackHighWaterMark(hA));
report("markB=", (unsigned)uxTaskGetStackHighWaterMark(hB));
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
10. Lab Experiments
Exp01 — Basic: make memory visible. Write vMonitorTask to report the free heap, the min-ever-free watermark, and each worker's stack high-water mark once a second. Expected: a steady readout; the marks settle once each task has hit its deepest path, and the free heap stops dropping once startup allocation is done.
Exp02 — Advanced: right-size a stack from its mark. Add a deep path to a worker (a recursive call or a large local array). Watch its high-water mark fall. Shrink WORKER_STACK until the mark approaches 0 — or the stack-overflow hook fires and the fault LED lights. Then set the stack to the measured peak plus a margin. Expected: you can show the mark shrinking, trigger the overflow hook on purpose, and then pick a correct stack size instead of guessing.
Exp03 — Challenge: exhaust the heap, and read CPU share. In a worker, loop pvPortMalloc() without freeing until it returns NULL and the malloc-failed hook fires — watch the min-ever-free watermark walk down to it first. Restore the code. (Optional: enable run-time stats and call vTaskGetRunTimeStats() to print each task's CPU percentage.) Expected: the malloc-failed hook catches exhaustion immediately, and you can explain why the watermark is the early warning.
11. Testing & Verification
| # | Pass condition |
|---|---|
| Exp01 | Monitor reports free heap, min-ever-free, and both stack marks each second. |
| Exp02 | A worker's mark shrinks with a deeper path; the overflow hook fires when undersized; a right-sized stack is chosen from the mark. |
| Exp03 | A malloc loop drives the malloc-failed hook; the watermark is shown walking down first. |
Submit: your monitor task, the Exp02 mark readings + the stack size you chose, and your Exp03 observation of the watermark and hook.
12. Q&A & Troubleshooting
- HardFault with no obvious cause → a stack overflow; enable configCHECK_FOR_STACK_OVERFLOW = 2 and read the hook's name argument.
- Overflow hook never fires → the check is 0, or the overflow is so large it corrupts past the guard; use method 2 and start with a known-small stack.
- xTaskCreate returns pdFAIL → the heap could not fit the TCB + stack; raise configTOTAL_HEAP_SIZE or shrink stacks.
- Marks look huge / in bytes → the mark and the depth are in words (4 bytes on Cortex-M3); multiply to compare with bytes.
- Run-time stats all zero → no high-frequency counter wired; the stats timer must tick far faster than the RTOS tick.
- Free heap never recovers after delete → that is expected if you never vPortFree/delete; heap_4 coalesces only what you actually free.
13. Analysis Questions & Lab Report
- What is the difference between xPortGetFreeHeapSize and xPortGetMinimumEverFreeHeapSize, and why is the second the one to design against?
- What does a stack high-water mark of (near) 0 mean, and how do you use the mark to size a stack?
- What does heap_4 coalescing do, and why can free heap exist yet an allocation still fail?
- When would you choose xTaskCreateStatic over dynamic creation?
Your report should include your monitor task, your Exp02 measurements and chosen stack size, and answers to the four questions.