all
Chapter 17 of 20

DMA Controller — Tiva C

Eslam El Hefny Apr 17, 2025 7 min read
85% done

DMA Controller — Tiva C

Overview

The TM4C123GH6PM includes a micro-DMA (uDMA) controller with 32 channels. DMA allows peripherals to transfer data directly to or from memory without CPU involvement, dramatically increasing throughput and freeing the CPU for other tasks. This is essential for high-speed ADC sampling, audio streaming, and bulk UART/SSI transfers.


Beginner Level — What & Why

What is DMA?

Direct Memory Access (DMA) is a hardware mechanism where data moves between memory and peripherals automatically, without the CPU reading and writing each byte. The CPU sets up the transfer and then continues executing other code while the DMA engine works in parallel.

Real-World Analogy

Imagine you need to move 100 boxes from one room to another. Without DMA, you carry each box yourself (CPU polling). With DMA, you hire a moving company (the DMA controller) — you give them the source, destination, and count, and they do the work while you go do something else.

What Problem Does It Solve?

  • Offloads high-bandwidth data movement from the CPU
  • Enables continuous ADC sampling without per-sample interrupts
  • Allows UART/SSI reception into large buffers while the CPU processes other data
  • Enables memory-to-memory copy (like memcpy) at maximum bus speed

Key Terms

Term Meaning
Channel One DMA path (source → destination)
Control structure SRAM structure describing src, dst, count, mode
Primary Main transfer descriptor
Alternate Second descriptor for ping-pong mode
Ping-pong Double-buffered continuous transfer
Arbitration How many items transferred before CPU can intervene
AMBA Advanced Microcontroller Bus Architecture

Intermediate Level — How It Works

uDMA Architecture

The uDMA has 32 channels. Each channel has a primary and alternate control structure (each 16 bytes) stored in SRAM at the control table base address (must be 1024-byte aligned). Total control table size = 32 channels × 2 structures × 16 bytes = 1024 bytes.

Control Structure Format (16 bytes each)

Word Offset Field Description
0 0x0 SRCENDP Source end pointer (last byte address)
1 0x4 DSTENDP Destination end pointer (last byte address)
2 0x8 CHCTL Channel control word
3 0xC Reserved

CHCTL fields:

Bits Field Description
31:26 DSTINC Destination address increment (0=byte, 1=half, 2=word, 3=none)
25:24 DSTSIZE Destination size
23:18 SRCINC Source address increment
17:16 SRCSIZE Source size
15:14 reserved
13:4 XFERSIZE Number of transfers - 1
3 NXTUSEBURST Use burst on next transfer
2:0 XFERMODE 0=stop, 1=basic, 2=auto, 3=ping-pong, 4=mem scatter-gather

Key uDMA Registers

Register Address Description
DMASTAT 0x400FF000 Status
DMACFG 0x400FF004 Configuration (MASTEN bit to enable)
DMACTLBASE 0x400FF008 Control table base address
DMASWREQ 0x400FF14 Software trigger
DMAUSEBURSTSET 0x400FF018 Use burst on channels
DMAREQMASKSET 0x400FF020 Mask (disable) channel requests
DMAENASET 0x400FF028 Enable channels
DMAALTSET 0x400FF038 Select alternate control structure
DMAPRIOSET 0x400FF040 Set high priority on channels
DMACHMAP0 0x400FF510 Channel 0–7 peripheral assignment

TivaWare DriverLib API

#include "driverlib/udma.h"

/* 1024-byte aligned control table in SRAM */
#pragma DATA_ALIGN(ui8ControlTable, 1024)
uint8_t ui8ControlTable[1024];

/* Enable uDMA peripheral */
SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA);
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_UDMA));

/* Enable the uDMA controller */
uDMAEnable();

/* Set control table base */
uDMAControlBaseSet(ui8ControlTable);

/* Configure a channel for memory-to-memory transfer (ch30) */
uDMAChannelAttributeDisable(UDMA_CHANNEL_SW,
    UDMA_ATTR_USEBURST | UDMA_ATTR_ALTSELECT |
    UDMA_ATTR_HIGH_PRIORITY | UDMA_ATTR_REQMASK);

uDMAChannelControlSet(UDMA_CHANNEL_SW | UDMA_PRI_SELECT,
    UDMA_SIZE_32  |   // 32-bit transfers
    UDMA_SRC_INC_32  |  // source increments by 4 bytes
    UDMA_DST_INC_32  |  // destination increments by 4 bytes
    UDMA_ARB_4);        // arbitrate every 4 items

/* Set source, destination, count */
uDMAChannelTransferSet(UDMA_CHANNEL_SW | UDMA_PRI_SELECT,
    UDMA_MODE_AUTO,  // auto: runs all transfers then stops
    srcArray,        // source address
    dstArray,        // destination address
    ARRAY_SIZE);     // number of items

/* Enable channel and trigger */
uDMAChannelEnable(UDMA_CHANNEL_SW);
uDMAChannelRequest(UDMA_CHANNEL_SW);  // SW trigger for ch30

/* Wait for completion */
while (uDMAChannelIsEnabled(UDMA_CHANNEL_SW));

Advanced Level — Deep Dive

Bare-Metal uDMA Memory-to-Memory Setup

#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_udma.h"

/* Control table — must be 1024-byte aligned */
#pragma DATA_ALIGN(g_sDMAControlTable, 1024)
static tDMAControlTable g_sDMAControlTable[64];  // 32 primary + 32 alternate

void DMA_MemCopy32(uint32_t *pSrc, uint32_t *pDst, uint32_t count)
{
    /* Enable uDMA */
    HWREG(SYSCTL_RCGCDMA) |= (1U << 0);
    while (!(HWREG(SYSCTL_PRDMA) & (1U << 0)));

    /* Master enable */
    HWREG(UDMA_BASE + UDMA_O_CFG) = UDMA_CFG_MASTEN;

    /* Set control table base (must be 1024-byte aligned) */
    HWREG(UDMA_BASE + UDMA_O_CTLBASE) = (uint32_t)g_sDMAControlTable;

    /* Clear attributes for channel 30 (SW) */
    HWREG(UDMA_BASE + UDMA_O_ALTCLR)   = (1U << 30);
    HWREG(UDMA_BASE + UDMA_O_PRIOCLR)  = (1U << 30);
    HWREG(UDMA_BASE + UDMA_O_REQMASKCLR) = (1U << 30);

    /* Fill primary control structure for channel 30 */
    /* SRCENDP = last source word */
    g_sDMAControlTable[30].pvSrcEndAddr =
        (void *)(pSrc + count - 1);
    /* DSTENDP = last destination word */
    g_sDMAControlTable[30].pvDstEndAddr =
        (void *)(pDst + count - 1);
    /* CHCTL: 32-bit, src/dst increment, count-1, auto mode */
    g_sDMAControlTable[30].ui32Control =
        UDMA_CHCTL_DSTINC_32  |
        UDMA_CHCTL_DSTSIZE_32 |
        UDMA_CHCTL_SRCINC_32  |
        UDMA_CHCTL_SRCSIZE_32 |
        UDMA_CHCTL_ARBSIZE_4  |
        ((count - 1) << UDMA_CHCTL_XFERSIZE_S) |
        UDMA_CHCTL_XFERMODE_AUTO;

    /* Enable channel 30 and software-trigger */
    HWREG(UDMA_BASE + UDMA_O_ENASET)  = (1U << 30);
    HWREG(UDMA_BASE + UDMA_O_SWREQ)   = (1U << 30);

    /* Busy-wait for completion (channel auto-disables when done) */
    while (HWREG(UDMA_BASE + UDMA_O_ENASET) & (1U << 30));
}

Ping-Pong Mode for Continuous ADC Sampling

In ping-pong mode, the DMA alternates between the primary and alternate control structures, providing a continuous stream without interruption:

/* Two buffers: when DMA finishes primary → fires interrupt,
   switches to alternate → CPU processes primary buffer,
   CPU refills primary → DMA switches back, and so on */

static uint32_t g_ui32ADCBuf0[128];
static uint32_t g_ui32ADCBuf1[128];

void DMA_ADC_PingPong_Init(void)
{
    /* Channel 14 = ADC0 SS3 */
    uDMAChannelAttributeDisable(UDMA_CHANNEL_ADC3, UDMA_ATTR_ALL);

    uDMAChannelControlSet(UDMA_CHANNEL_ADC3 | UDMA_PRI_SELECT,
        UDMA_SIZE_32 | UDMA_SRC_INC_NONE |
        UDMA_DST_INC_32 | UDMA_ARB_4);

    uDMAChannelControlSet(UDMA_CHANNEL_ADC3 | UDMA_ALT_SELECT,
        UDMA_SIZE_32 | UDMA_SRC_INC_NONE |
        UDMA_DST_INC_32 | UDMA_ARB_4);

    /* Primary: ADC FIFO → Buffer 0 */
    uDMAChannelTransferSet(UDMA_CHANNEL_ADC3 | UDMA_PRI_SELECT,
        UDMA_MODE_PING_PONG,
        (void *)(ADC0_BASE + ADC_O_SSFIFO3),
        g_ui32ADCBuf0, 128);

    /* Alternate: ADC FIFO → Buffer 1 */
    uDMAChannelTransferSet(UDMA_CHANNEL_ADC3 | UDMA_ALT_SELECT,
        UDMA_MODE_PING_PONG,
        (void *)(ADC0_BASE + ADC_O_SSFIFO3),
        g_ui32ADCBuf1, 128);

    uDMAChannelEnable(UDMA_CHANNEL_ADC3);
}

/* In DMA interrupt handler: */
void uDMA_Handler(void)
{
    uDMAIntClear(UDMA_INT_SOFTREQ | UDMA_INT_DMAERR);

    if (!uDMAChannelModeGet(UDMA_CHANNEL_ADC3 | UDMA_PRI_SELECT))
    {
        /* Primary buffer is done — refill it */
        uDMAChannelTransferSet(UDMA_CHANNEL_ADC3 | UDMA_PRI_SELECT,
            UDMA_MODE_PING_PONG,
            (void *)(ADC0_BASE + ADC_O_SSFIFO3),
            g_ui32ADCBuf0, 128);
        /* Process g_ui32ADCBuf0 */
    }
    else
    {
        /* Alternate buffer done — refill alternate */
        uDMAChannelTransferSet(UDMA_CHANNEL_ADC3 | UDMA_ALT_SELECT,
            UDMA_MODE_PING_PONG,
            (void *)(ADC0_BASE + ADC_O_SSFIFO3),
            g_ui32ADCBuf1, 128);
        /* Process g_ui32ADCBuf1 */
    }
}

Channel Assignments (DMACHMAP)

The TM4C123 maps peripherals to uDMA channels. Some channels have multiple assignments selectable via DMACHMAP registers:

Channel Assignment
0 UART0 RX
1 UART0 TX
8 UART1 RX
14 ADC0 SS3
24 SSI0 RX
25 SSI0 TX
30 Software (SW)

Gotchas

  • Control table 1024-byte alignment is mandatory — the uDMA will fault if it is not aligned.
  • Source and destination end pointers, not start pointers, are stored in the control structure. For a 10-word transfer starting at 0x20000000: end pointer = 0x20000000 + (10-1)*4 = 0x20000024.
  • uDMA and peripheral interrupt interact — the peripheral interrupt fires when the DMA writes the last byte; the uDMA interrupt fires when the control structure mode transitions to STOP.
  • UDMA_MODE_BASIC vs UDMA_MODE_AUTO: AUTO reloads the control structure automatically; BASIC stops and requires reconfiguration.
  • Scatter-gather mode uses an array of control structures in SRAM as a task list — useful for non-contiguous memory transfers.

Step-by-Step Example

/*
 * dma_memcopy.c
 * Uses uDMA software channel (ch30) to copy 1024 bytes in one burst
 * Board  : TM4C123GXL EK LaunchPad
 * SDK    : TivaWare_C_Series-2.2.x
 */

#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "inc/hw_memmap.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "driverlib/udma.h"
#include "driverlib/interrupt.h"
#include "utils/uartstdio.h"
#include "driverlib/uart.h"
#include "driverlib/pin_map.h"

#define COPY_SIZE  256  /* 256 words = 1024 bytes */

/* DMA control table — 1024-byte aligned */
#pragma DATA_ALIGN(g_sDMATable, 1024)
static uint8_t g_sDMATable[1024];

static uint32_t g_ui32Src[COPY_SIZE];
static uint32_t g_ui32Dst[COPY_SIZE];

static volatile bool g_bDmaDone = false;

void uDMAError_Handler(void)
{
    uDMAErrorStatusClear();
}

int main(void)
{
    uint32_t i;

    SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL |
                   SYSCTL_OSC_MAIN   | SYSCTL_XTAL_16MHZ);

    /* UART for output */
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_UART0));
    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOA));
    GPIOPinConfigure(GPIO_PA0_U0RX); GPIOPinConfigure(GPIO_PA1_U0TX);
    GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
    UARTStdioConfig(0, 115200, SysCtlClockGet());

    /* Fill source with test pattern */
    for (i = 0; i < COPY_SIZE; i++) g_ui32Src[i] = i * 0x01010101;
    memset(g_ui32Dst, 0, sizeof(g_ui32Dst));

    /* Enable uDMA */
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA);
    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_UDMA));
    uDMAEnable();
    uDMAControlBaseSet(g_sDMATable);

    /* Configure channel 30 (software) */
    uDMAChannelAttributeDisable(UDMA_CHANNEL_SW, UDMA_ATTR_ALL);
    uDMAChannelControlSet(UDMA_CHANNEL_SW | UDMA_PRI_SELECT,
        UDMA_SIZE_32 | UDMA_SRC_INC_32 | UDMA_DST_INC_32 | UDMA_ARB_4);
    uDMAChannelTransferSet(UDMA_CHANNEL_SW | UDMA_PRI_SELECT,
        UDMA_MODE_AUTO, g_ui32Src, g_ui32Dst, COPY_SIZE);

    UARTprintf("Starting DMA copy of %d words...\r\n", COPY_SIZE);

    uDMAChannelEnable(UDMA_CHANNEL_SW);
    uDMAChannelRequest(UDMA_CHANNEL_SW);

    /* Wait for completion */
    while (uDMAChannelIsEnabled(UDMA_CHANNEL_SW));

    /* Verify */
    bool ok = (memcmp(g_ui32Src, g_ui32Dst, COPY_SIZE * 4) == 0);
    UARTprintf("DMA copy %s!\r\n", ok ? "PASSED" : "FAILED");

    while (1);
}

Summary

Key Point Details
Channels 32 (0–31), channel 30 = software SW
Control table 1024 bytes, must be 1024-byte aligned in SRAM
Structure size 16 bytes (src end, dst end, ctrl word, reserved)
End pointers Last address of transfer, not start
Transfer modes Basic, Auto, Ping-Pong, Scatter-Gather
Channel trigger Peripheral signal or software (uDMAChannelRequest)
Ping-pong Double-buffer continuous transfer with interrupt
Max speed Limited by AHB bus (~320 MB/s theoretical)

Next Chapter

USB Controller

Share: