DMA Controller — Tiva C
- Eslam El Hefny
- Tutorials, Tiva c
- April 17, 2025
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) |