PSoC5LP RTOS
Lab 25 — DMA + RTOS
FreeRTOS on PSoC 5LP · Lab Series Platform: CY8CKIT-059 (PSoC 5LP, Cortex-M3) · PSoC Creator 4.4 · FreeRTOS Kernel V11 Pattern: task starts a DMA → blocks → DMA-done ISR notifies → task processes one block Pairs with: Chapter 15 — Interrupts on PSoC 5LP (the DMA-done interrupt) & Chapter 8 — Queues
1. Introduction & Objectives
Moving a block of data — a burst of ADC samples, a frame to a display, a buffer over SPI — by looping in a task burns CPU and starves every other task for the whole transfer. The PSoC 5LP has a DMA controller (DMAC) that moves data in hardware, in parallel with the CPU. Under an RTOS the pattern is clean and powerful: a task starts the DMA, then blocks on a notification; the DMA-done ISR wakes it when the block is complete; the task processes one whole buffer. During the transfer the CPU is free and other tasks run. This is the deferred-interrupt pattern of Lab 12, applied to bulk data movement.
After this lab you can:
- Explain why a CPU copy starves other tasks, and how DMA frees the CPU during a transfer.
- Drive a PSoC DMAC channel and transaction descriptor (TD) from firmware.
- Use the RTOS pattern: arm DMA → block on a notification → done-ISR notifies → process.
- Keep the DMA-done ISR tiny and on the NVIC 2–7 safe side (it calls *FromISR).
- (Extension) Sustain gap-free capture with double-buffering (ping-pong).
2. Prerequisites
Hardware - CY8CKIT-059 PSoC 5LP Prototyping Kit - RobotSoC Kit: an analog source for the ADC (or any peripheral that produces a burst); a timing pin / LED to show the CPU is free
Software - PSoC Creator 4.4 with the FreeRTOS V11 BaseProject
Chapters (sync progress) - Required: Chapter 15 (Interrupts on PSoC 5LP — the DMA-done / nrq interrupt) and Chapter 8 (Queues — handing the block to a task). - Related: Lab 12 (the deferred-interrupt pattern and the NVIC 2–7 safety line), Lab 06/07 (notifications and queues), Lab 15 (what to do with the captured block). - Peripheral prerequisite: the DMAC / TD basics (channels, transaction descriptors) are covered in the separate (non-FreeRTOS) DMA lab.
3. Reading Materials / References
- Chapter 15 slides — Interrupts on PSoC 5LP (interrupt priorities, the *FromISR rules, the DMA nrq terminal).
- PSoC DMA: <DMA>_DmaInitialize, CyDmaTdAllocate, CyDmaTdSetConfiguration, CyDmaTdSetAddress, CyDmaChSetInitialTd, CyDmaChEnable.
- FreeRTOS API: vTaskNotifyGiveFromISR, ulTaskNotifyTake, portYIELD_FROM_ISR.
4. Background & Theory of Operation
What the DMAC does. The DMA controller is a piece of hardware that moves data from a source to a destination — peripheral→memory, memory→peripheral, or memory→memory — without the CPU. On the PSoC 5LP you program it through a channel and one or more transaction descriptors (TDs): a TD holds the byte count, whether to increment the source/destination address, whether to raise the terminal-count output (nrq), and which TD to run next (for chaining). Once you enable the channel, the hardware moves the block on its own and the CPU is free to do anything else.
Why it matters under an RTOS. Copying the same block in a task is a for loop that holds the CPU for the entire transfer — at a high priority it starves everything below it; at a low priority it is slow and still wastes cycles. DMA changes the economics completely: the task issues a few register writes to start the transfer, then blocks. While the hardware moves the data, the scheduler runs other tasks on the now-free CPU. When the block is done the DMAC raises nrq, a tiny DMA-done ISR fires, and — exactly like Lab 12 — it just notifies the waiting task and yields. The task wakes with a full buffer ready to process.
The pattern, step by step. 1. The Acquire task arms the DMA: point the TD at the RAM buffer and enable the channel. 2. The task blocks on ulTaskNotifyTake(pdTRUE, portMAX_DELAY) — it uses no CPU while it waits. 3. The DMAC moves N items in hardware; other tasks run. 4. On completion the DMAC's nrq fires the DMA-done ISR, which does vTaskNotifyGiveFromISR + portYIELD_FROM_ISR and nothing else. 5. The task wakes, processes the whole buffer (filter, analyse, forward), and loops to arm the next transfer.
Safety and correctness. - The DMA-done ISR calls a *FromISR function, so it must sit at NVIC 2–7 (Lab 12's syscall line). At 0–1 it would corrupt the kernel. - The buffer is written by hardware behind the compiler's back — declare it volatile, and never read it before the notify arrives (the DMA owns it until then). The notification is the ownership hand-off; do not race it with a plain flag. - There is no data-cache on the Cortex-M3 of this part, so no cache-coherency step is needed — but the ordering rule (process only after the notify) still holds.
Double-buffering (ping-pong). A single buffer leaves a gap: while the task processes it, nothing is being captured. With two buffers the DMA fills B while the task drains A, then they swap — continuous, gap-free capture at a rate the CPU could never sustain by hand. Implement it by chaining two TDs (A→B→A…), each with the terminal-count output enabled so every block completion notifies, and have the task alternate which buffer it processes each wake. The rule stays the same: never touch a buffer while the DMA owns it, and let the notification mark the hand-off.
5. RTOS Task Architecture
The Acquire task sets up a TD and starts the DMAC, then blocks on a notification. The DMAC moves the burst from the ADC/SPI into the RAM buffer with no CPU involvement; on terminal count its nrq fires the DMA-done ISR (NVIC 2–7), which notifies the task. The task wakes and processes one whole block, while during the transfer other tasks ran on the free CPU. The panels give the reasons DMA pays off under an RTOS and the double-buffering extension.
6. The Transfer Over Time
CPU-copy vs DMA — who moves the block, and what the CPU does:
A CPU copy pins the task to the transfer and starves the others; with DMA the task blocks, the hardware moves the data, and other tasks run until the done-ISR wakes it.
Double-buffering (ping-pong) — fill one buffer while the task drains the other:
The DMA and the task never touch the same buffer at once; the notification hands ownership across at each swap, giving continuous capture.
The lesson both diagrams carry: offload the move, block the task. The CPU spends almost nothing on the transfer, so it is available for real work — and with two buffers the capture never stops.
7. Schematic Circuit & Pin Configuration
- Source peripheral → the ADC (or SPI/UART) result register the DMA reads.
- Timing pin / LED → toggled by another task to show it runs while the DMA transfers.
- Assign the pins in <project>.cydwr → Pins.
8. PSoC Creator Configuration
- A DMA component (the DMA Wizard, or the DMAC used directly) sized to the block; an interrupt component on its nrq (terminal-count) terminal.
- Set the DMA-done interrupt NVIC priority to 3 (within 2–7), or isr_DMA_SetPriority(3) at startup (the template does this).
- FreeRTOSConfig.h (already set): configMAX_SYSCALL_INTERRUPT_PRIORITY = (2<<5), configUSE_TIME_SLICING = 1 (course default), heap_4.
9. Provided Template
You are given Lab25_Template_main.c:
- Given: all includes (note #include "FreeRTOS_PSoC.h"), the volatile RAM buffer, the task handle, a dma_arm() helper sketch, and a complete main() that sets up the channel + TD, installs the DMA-done ISR at a safe 2–7 priority, and makes the Acquire task.
- You write: DMA_Done_ISR() (notify + yield) and vAcquireTask() (arm → block → process). The Exp02 ping-pong path is sketched in a comment.
- Do not change the boot sequence in main().
CY_ISR(DMA_Done_ISR) /* wired to the DMA 'nrq' terminal */
{
BaseType_t woken = pdFALSE;
vTaskNotifyGiveFromISR(hAcquire, &woken); /* hand the buffer over */
portYIELD_FROM_ISR(woken);
}
/* acquire task */
dma_arm(); /* start the transfer */
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); /* block - CPU free */
process(g_buf); /* one whole block, ready */
10. Lab Experiments
Exp01 — Basic: one DMA block, task blocks
Write the done-ISR and the Acquire task. Capture N_SAMPLES into g_buf by DMA; have a second task toggle an LED/pin at a steady rate to prove it keeps running during the transfer. Expected: the buffer fills correctly, the task wakes only when the block is done, and the second task's activity shows the CPU was free the whole time.
Exp02 — Advanced: double-buffer for gap-free capture
Add a second buffer and chain two TDs (A→B→A…) so the DMA fills one while the task processes the other, alternating on each notify. Raise the sample rate until a single-buffer version would drop data, and show the ping-pong version does not. Expected: continuous capture at a rate the CPU could not copy by hand, with the task still running at its own priority.
Exp03 — Challenge: measure the CPU you got back
Compare, with a timing pin or the run-time stats from Lab 23, the CPU time spent per block by (a) a CPU for-loop copy and (b) the DMA path. Compute how much CPU the DMA returned to the rest of the system at your block size and rate. Expected: a measured, quantified CPU saving, and a statement of the block sizes/rates where DMA is worth the setup cost.
11. Testing & Verification
| # | Pass condition |
|---|---|
| Exp01 | A DMA block fills the buffer; the task blocks (not polls); a second task runs during the transfer. |
| Exp02 | Ping-pong double-buffering captures with no gaps at a rate a single buffer drops. |
| Exp03 | The CPU time reclaimed by DMA vs a CPU copy is measured and quantified. |
Submit: your done-ISR and Acquire task, your Exp02 double-buffer, and your Exp03 CPU-saving measurement.
12. Q&A & Troubleshooting
- HardFault / assert when the DMA completes → the DMA-done ISR is at NVIC 0–1; set it to 2–7 before calling *FromISR.
- Task never wakes → the TD's terminal-count output (nrq / TD_TERMOUT_EN) is not enabled, or the ISR is not wired to it.
- Buffer has stale / partial data → you read g_buf before the notify, or forgot volatile; process only after ulTaskNotifyTake returns.
- Only the first block transfers → the channel was not re-armed / the TD not preserved; re-enable it each round (or chain TDs for continuous mode).
- Wrong addresses → PSoC DMA uses 16-bit address halves (HI16/LO16); set the high part in DmaInitialize and the low part per TD.
- Still starving other tasks → you busy-wait on a DMA status flag instead of blocking on the notification; block, don't poll.
13. Analysis Questions & Lab Report
- Why does a CPU for-loop copy starve other tasks, and how does DMA free the CPU during the transfer?
- State the RTOS DMA pattern in order (arm, block, notify, process). Where does the deferred-interrupt rule from Lab 12 apply?
- Why must the buffer be volatile, and why must you not read it before the notify?
- (Extension) How does ping-pong double-buffering achieve gap-free capture, and what must never happen to a buffer the DMA owns?
Your report should include your done-ISR and Acquire task, your double-buffer path, your CPU-saving measurement, and answers to the four questions.