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

DMA + RTOS — HARDWARE MOVES THE DATA WHILE THE TASK SLEEPS ADC / SPI a burst of data DMAC (hardware) channel + TD(s) moves N items, no CPU nrq (done) on terminal count RAM buffer filled by the DMA DMA-done ISR (NVIC 2–7) notify task terminal count → nrq Acquire Task 1. set up TD, start DMA 2. ulTaskNotifyTake() — BLOCK 3. (woken) process buffer sleeps during the whole transfer notify task starts the transfer, then blocks Why DMA under an RTOS · The transfer runs in hardware, in parallel — the CPU is free the whole time. · The task BLOCKS (ulTaskNotifyTake), it does not poll or busy-copy — so other tasks run meanwhile. · The done-ISR just notifies (deferred-interrupt, Lab 12); it must sit at NVIC 2–7 to call *FromISR. A CPU memcpy of the same block would starve every other task. Double-buffering (ping-pong) · Two buffers: DMA fills B while the task processes A, then they swap — continuous capture, no gaps. · Chain TDs (or two channels); never touch a buffer while the DMA owns it — the notify marks the hand-off. · Lets you sustain a sample rate the CPU could not copy fast enough by hand. Mark the buffer as volatile; order with the notification, not a flag race. The pattern: task starts the DMA → blocks on a notification → done-ISR wakes it → task processes one whole block. CPU cost ≈ zero during the move.

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:

CPU-copy vs DMA — who moves the block, and what the CPU does Same N-item transfer; with a CPU copy the task is pinned to it, with DMA the task sleeps and other work runs CPU copy — the task is pinned to the move Acquire task Other tasks Data moved for(i..N) buf[i]=read(); // CPU busy the whole transfer starved — cannot run while the copy loops N items moved by the CPU The transfer time is CPU time — every other task waits. DMA — hardware moves it, the task sleeps Acquire task Other tasks DMAC (hw) start BLOCKED on ulTaskNotifyTake process block other tasks run on the free CPU N items moved by hardware (CPU idle) done → notify Conceptual / idealized

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:

Double-buffering (ping-pong) — fill one buffer while the task drains the other DMA and the task never touch the same buffer at once; the hand-off is the notification, giving gap-free capture DMAC (hw) task buffers fill A fill B fill A fill B process A process B process A (idle: first fill) swap+notify swap+notify swap+notify A: DMA A: task / B: DMA B: task / A: DMA A: task / B: DMA Never a gap, never a clash: while the DMA fills one buffer, the task drains the other; the notify hands ownership across. Sustains a rate the CPU could not keep up with by copying, and the task still runs at its own priority. Conceptual / idealized

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

  1. Why does a CPU for-loop copy starve other tasks, and how does DMA free the CPU during the transfer?
  2. State the RTOS DMA pattern in order (arm, block, notify, process). Where does the deferred-interrupt rule from Lab 12 apply?
  3. Why must the buffer be volatile, and why must you not read it before the notify?
  4. (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.

© 2026 Air Supply Information Center (Air Supply BBS)