PSoC5LP RTOS
Lab 22 — Fully Static FreeRTOS (No Heap)
FreeRTOS on PSoC 5LP · Lab Series. Platform: CY8CKIT-059 (PSoC 5LP, Cortex-M3) · PSoC Creator 4.4 · FreeRTOS Kernel V11. Theme: every RTOS object placed in .bss; no pvPortMalloc, no heap at run time. Pairs with Chapter 13 — Architecture & Reliability & Chapter 14 — Memory, Heap & Stack.
1. Introduction & Objectives
Lab 13 watched the heap shrink as tasks and queues were created and saw that an allocation can fail at run time. For many products that risk is unacceptable: medical, industrial, and automotive firmware is often required to allocate nothing after start-up, so that the memory a system needs is known and guaranteed at build time. FreeRTOS supports this directly: every object has a ...Static() creation function that uses memory you declare, and with dynamic allocation switched off the heap — and pvPortMalloc itself — is not even linked. This lab converts a multi-task app to fully static allocation.
After this lab you can:
- Create tasks, queues, and semaphores with their ...Static() forms, backed by your own buffers.
- Provide the two required kernel-memory callbacks (vApplicationGetIdleTaskMemory, vApplicationGetTimerTaskMemory).
- Build with configSUPPORT_DYNAMIC_ALLOCATION = 0 and confirm no heap is linked.
- Read the RAM cost of the system from the .map file at build time.
- Explain the static-vs-dynamic trade-off and when each is appropriate.
2. Prerequisites
Hardware: CY8CKIT-059 PSoC 5LP Prototyping Kit (the mechanism is RAM-only; any simple peripheral set works).
Software: PSoC Creator 4.4 with the FreeRTOS V11 BaseProject.
Chapters (sync progress): Required — Chapter 14 (Memory, Heap & Stack — static vs dynamic) and Chapter 13 (architecture & reliability); Related — Lab 13 (the heap, stack high-water mark, allocation failure), Lab 18 (reliability).
3. Reading Materials / References
- Chapter 14 slides — Memory, Heap & Stack (the heap schemes, static allocation, the .map file).
- FreeRTOS docs — Static vs Dynamic Allocation and the required callbacks.
- FreeRTOS API: xTaskCreateStatic, xQueueCreateStatic, xSemaphoreCreateBinaryStatic, xSemaphoreCreateMutexStatic, xEventGroupCreateStatic, xTimerCreateStatic, vApplicationGetIdleTaskMemory, vApplicationGetTimerTaskMemory.
4. Background & Theory of Operation
Dynamic vs static — where the memory comes from. With dynamic creation (xTaskCreate, xQueueCreate, …) the kernel calls pvPortMalloc to carve each object's TCB, stack, and storage out of the single heap_4 pool at run time. With static creation (xTaskCreateStatic, …) you supply that memory as ordinary variables — a StaticTask_t for the TCB, a StackType_t[] for the stack, a storage array for a queue — and the object is placed in .bss by the linker. Nothing is allocated when the object is created; ...Static only initialises the memory you already reserved.
The anatomy of a static create. Each ...Static() function takes the same arguments as its dynamic cousin plus the buffers it would otherwise have malloc'd. For a task:
StaticTask_t tcbA;
StackType_t stackA[DEPTH];
TaskHandle_t h = xTaskCreateStatic(fn, "A", DEPTH, arg, prio, stackA, &tcbA);
The handle comes back exactly as before (it is NULL only if an argument is wrong, never because memory ran out). The same pattern applies to xQueueCreateStatic (a StaticQueue_t + a storage array), xSemaphoreCreateMutexStatic (a StaticSemaphore_t), and so on.
The part everyone forgets: the kernel's own tasks. The scheduler creates an Idle task, and if timers are enabled a Timer/daemon task — each needs a TCB and stack. In a dynamic build the kernel mallocs these for you. In a static build it cannot, so it asks you through two callbacks you must define:
- vApplicationGetIdleTaskMemory(&tcb, &stack, &depth)
- vApplicationGetTimerTaskMemory(&tcb, &stack, &depth) (only if configUSE_TIMERS = 1)
With configSUPPORT_STATIC_ALLOCATION = 1 these are mandatory — omit them and the project fails to link or the scheduler cannot start.
Switching the heap off entirely. Setting configSUPPORT_STATIC_ALLOCATION = 1 enables the static API alongside the dynamic one. To make the build fully static, also set configSUPPORT_DYNAMIC_ALLOCATION = 0: now any accidental call to a dynamic xTaskCreate/pvPortMalloc is a compile/link error, and the heap implementation (heap_4.c) and pvPortMalloc are not linked at all — so there is simply no heap to exhaust.
Why do this — and what it costs. The win is determinism: the RAM the system needs is fixed and visible in the .map file, there is no run-time allocation to fail, and there is no fragmentation. That is why safety standards (IEC 61508, IEC 62304 for medical, automotive/aerospace guidance) favour or require it. The cost is boilerplate (you declare every buffer) and rigidity (sizes are fixed at compile time — you cannot create or delete objects on demand). A mix is allowed: a mostly-static design can keep a small heap for the few things that genuinely need dynamic lifetime.
5. RTOS Task Architecture
Each of your objects — Task, Queue, Mutex — is paired with the static storage you declare (TCB, stack, queue storage), and the kernel's own Idle and Timer tasks get their memory through the two callbacks. Everything lives in .bss, sized at link time. The panels give the steps to make a build fully static and the static-vs-dynamic trade-off.
6. Memory: Build Time vs Run Time
Free RAM during start-up — dynamic carves at run time, static is fixed from reset:
Dynamic shrinks the heap as each object is built and a late create can fail; static reserves all the RAM at link time, so creation allocates nothing and cannot fail.
Anatomy of a static create — you provide the storage, it returns the handle:
xTaskCreateStatic takes two extra arguments over xTaskCreate — your stack buffer and your TCB — and the same pattern applies to every object.
The lesson both diagrams carry: move the allocation from run time to link time. What the linker places can be measured and guaranteed; what a run-time malloc places can fail when you least expect it.
7. Schematic Circuit & Pin Configuration
- This lab is about RAM allocation, not I/O — any minimal peripheral set is fine (an LED to show the tasks run).
- Assign the pins in <project>.cydwr → Pins.
8. PSoC Creator Configuration
- FreeRTOSConfig.h: configSUPPORT_STATIC_ALLOCATION = 1; for the fully-static build (Exp02) also configSUPPORT_DYNAMIC_ALLOCATION = 0.
- configUSE_TIMERS decides whether the Timer-task callback is required; if 1, you must provide vApplicationGetTimerTaskMemory.
- Keep configCHECK_FOR_STACK_OVERFLOW = 2 (static stacks still overflow if under-sized; Lab 13 applies).
- Build, then open the .map file to read the RAM each buffer takes.
9. Provided Template
You are given Lab22_Template_main.c:
- Given: all includes (note #include "FreeRTOS_PSoC.h"), the static storage and creation for a queue and a mutex, both kernel-memory callbacks (Idle and Timer) already written, and a complete main() that starts the scheduler.
- You write: the static storage for Task A and Task B (TCB + stack arrays) and their xTaskCreateStatic calls; then (Exp02) switch configSUPPORT_DYNAMIC_ALLOCATION = 0 and confirm the build.
- Do not change the two kernel-memory callbacks.
static StaticTask_t xTaskABuf;
static StackType_t ucTaskAStack[WORKER_STACK];
static TaskHandle_t hTaskA;
hTaskA = xTaskCreateStatic(vWorker, "A", WORKER_STACK, NULL,
tskIDLE_PRIORITY + 1u, ucTaskAStack, &xTaskABuf);
configASSERT(hTaskA != NULL);
10. Lab Experiments
Exp01 — Basic: create tasks statically. Declare the TCB and stack buffers for Task A and Task B and create them with xTaskCreateStatic. Run the app (the given static queue and mutex already work). Expected: the system runs exactly as a dynamic one would — but every object's memory is a named static buffer you can point to.
Exp02 — Advanced: switch the heap off. Set configSUPPORT_DYNAMIC_ALLOCATION = 0 and rebuild. Fix any link errors (they point at a leftover dynamic create). Confirm in the .map file that heap_4.c / pvPortMalloc are not linked. Expected: a fully static build with no heap at all, and the knowledge that no run-time allocation can occur.
Exp03 — Challenge: account for the RAM. From the .map file, add up the RAM used by your task stacks, the TCBs, the queue storage, and the kernel's Idle/Timer tasks. Compare it against the total SRAM. Then deliberately omit one kernel-memory callback and observe the link/scheduler failure, to prove why they are required. (Optional: convert a static timer or event group too.) Expected: a build-time RAM budget for the whole system, and a demonstration of the mandatory callbacks.
11. Testing & Verification
| # | Pass condition |
|---|---|
| Exp01 | Tasks created with xTaskCreateStatic run correctly; all storage is static. |
| Exp02 | configSUPPORT_DYNAMIC_ALLOCATION = 0 builds; heap_4/pvPortMalloc absent from the map. |
| Exp03 | RAM budget computed from the map; omitting a kernel callback is shown to break the build/scheduler. |
Submit: your static task creation, your Exp02 map-file evidence that no heap is linked, and your Exp03 RAM budget.
12. Q&A & Troubleshooting
- Link error about vApplicationGetIdleTaskMemory → the callback is missing; with static allocation on, you must define it (and the timer one if timers are used).
- Scheduler never starts → a kernel-memory callback returns bad buffers (wrong size or NULL); check the depth you report matches the array.
- Compile error on xTaskCreate → you left a dynamic create in a configSUPPORT_DYNAMIC_ALLOCATION = 0 build; switch it to the static form.
- ...Static() returns NULL → an argument is wrong (e.g. a storage array smaller than len × item); it is never an out-of-memory failure.
- A static stack overflows → static does not mean safe-sized; size it from the high-water mark (Lab 13) and keep configCHECK_FOR_STACK_OVERFLOW = 2.
- Buffers in the wrong section → declaring them non-static or on a stack is a bug; module-level static puts them in .bss as intended.
13. Analysis Questions & Lab Report
- Where does an object's memory come from in a dynamic vs a static create, and when is each allocated?
- Why must you provide vApplicationGetIdleTaskMemory (and the timer one) in a static build?
- What does configSUPPORT_DYNAMIC_ALLOCATION = 0 guarantee, and how do you confirm it from the map file?
- What does a fully static design win, and what does it cost? When would you still keep a heap?
Your report should include your static creation code, your map-file evidence, your RAM budget, and answers to the four questions.