AN-1561: DMA Programming for the ADuCM4050
INTRODUCTION
The direct memory access (DMA) controller performs data transfer tasks and offloads these tasks from the ADuCM4050 microcontroller unit (MCU). The DMA provides high speed data transfer between peripherals and memory. Data can be quickly moved by the DMA without any CPU actions, which keeps the CPU resources free for other operations.
This application note describes how to use the DMA functionalities of the ADuCM4050 microcontroller. This application note also provides the programming steps, DMA diagrams, and DMA code snippets necessary to program the DMA controller.
DMA CONTROLLER FEATURES
The DMA controller supports the following features:
- 27 independent DMA channels
- Two programmable priority levels for each DMA channel
- Each priority level arbitrates using a fixed priority that is determined by the DMA channel number
- Each DMA channel can access a primary and/or alternate channel control data structure
- Support for the following transfer types:
- Memory to memory
- Memory to peripheral
- Peripheral to memory
- Support for the following DMA cycle types:
- Basic
- Auto request
- Ping pong
- Scatter gather
- Support for multiple DMA transfer data widths (8 bit, 16 bit, and 32 bit)
- Each DMA channel can have independent source and destination increment and decrement controls
For more information about DMA controller refer to ADuCM4050 Ultra Low Power ARM Cortex-M4F MCU with Integrated Power Management Hardware Reference Manual.
DMA
DMA CHANNELS
DMA has 27 channels, each dedicated to managing memory access requests from peripherals.
Table 1 shows the DMA channel assignments.
Channel Node | Peripheral |
0 | SPI2 transmit |
1 | SPI2 receive |
2 | SPORT0A |
3 | SPORT0B |
4 | SPI0 transmit |
5 | SPI0 receive |
6 | SPI1 transmit |
7 | SPI1 receive |
8 | UART0 transmit |
9 | UART0 receive |
10 | I2C slave transmit |
11 | I2C slave receive |
12 | I2C master |
13 | Cryptography in |
14 | Cryptography out |
15 | Flash |
16 to 23 | Software DMA |
24 | Analog-to-digital converter (ADC) receive |
25 | UART1 transmit |
26 | UART1 receive |
DMA SYSTEM BLOCK DIAGRAM
Figure 1 shows the DMA system block diagram.
HOW DOES DMA WORK?
DMA transfers data between memory and peripherals based on the information provided in the DMA controller. When a DMA request is generated (either by software DMA request or a peripheral DMA request), the DMA controller collects the information from the corresponding channel control data structure and performs the desired transfer.
DMA CONTROLLER
The DMA controller is the main part of the DMA block that performs the transfer. The controller picks up transfer information from the channel control data structure and performs the transaction after receiving the DMA transfer request.
CHANNEL CONTROL DATA STRUCTURE
Every channel has two control data structures: a primary and an alternate. A separate space from the memory has to be declared containing either or both the primary and alternate data structures for each channel depending on the application complexity. This memory space is where the DMA controller picks up the information about the transfer.
Primary and Alternate Data Structure and Database Pointer
The primary and alternate data structures contain the DMA descriptors for all DMA channels. These data structures are declared in the RAM area with 16 bytes for each channel descriptor. The database pointer contains the start address of these descriptors which is how the DMA controller understands where the descriptor information for the particular channel is picked up. When receiving a transfer request, the DMA controller reads the base pointer, traverses to the specified descriptor of the channel, and performs the transfer as specified by the descriptor. If the descriptor information is defined in another memory space, move the descriptor information to the channel data structure memory space before enabling the DMA controller. The alternate data structure base pointer is a read only register whose value adjusts automatically when the primary descriptor base pointer changes.
The 16 byte DMA descriptor consists of all of the DMA transaction information. The descriptor must be copied to the particular channel control data structure before enabling the DMA controller as the DMA picks up the information about the transfer from the channel control data structure. Table 2 shows the elements of the DMA descriptor channels.
Name | Description | Size (byte) |
SRC_END_PTR | The end address of the source data | 4 |
DST_END_PTR | The end address of the destination data | 4 |
CHL_CFG | Provides the control information of the DMA transfer | 4 |
RESERVED | Reserved | 4 |
Refer to the ADuCM4050 Ultra Low Power ARM Cortex-M4F MCU with Integrated Power Management Hardware Reference Manual for further details on the elements of the DMA descriptor.
DMA PROGRAMMING MODEL
The general programming sequence for a DMA setup follows:
- Set up the DMA descriptor for data transmission.
- Set up the DMA base pointer.
- Enable the desired DMA channel.
- Enable the DMA controller in the DMA configuration register (DMA_CFG).
- Generate the software DMA request in the DMA channel software request register (DMA_SWREQ) or enable the peripheral that generates the interrupt to the DMA controller.
See the DMA Modes section for specific information on the DMA setup per mode.
DMA MODES
Auto Request (ARQ) Mode and Basic Mode
The ARQ and basic modes are the most basic of all the modes and consist of a single descriptor transfer. ARQ mode is for memory to memory transfer, and basic mode is for memory to peripheral or peripheral to memory transfers.
In both modes, the DMA descriptor can be first built up and then moved into the channel data structure or can be written directly into the channel data structure.
The programming sequence for these modes follows:
- Populate the DMA descriptor and move it to the channel data structure or write it directly into the channel data structure.
- Point the primary control database (PCD) pointer to the base of the channel control data structure.
- Clear the channel request mask for the particular DMA channel.
- Enable the request source interrupt for the DMA and the DMA completion interrupt (optional).
- Enable the corresponding DMA channel.
- Enable the DMA controller by setting the DMA_CFG register to 1.
- Generate the software DMA request (ARQ DMA) or wait for the peripheral DMA request to be generated (basic DMA).
ARQ and basic modes allow a maximum of 1024 transfers at a time, and the DMA controller must be re-enabled after every transfer.
The sequence diagram for basic mode is shown in Figure 2, which is identical to ARQ mode, except for Step 7.
Example Code: Memory to Memory ARQ Transfer Within Two Data Blocks of the RAM
This example demonstrates how to perform a DMA ARQ transfer between blocks of memory. This example uses the Channel 16 DMA (software DMA) descriptor and copies the data from srcPtr[] to destPtr[].
/* Build the channel descriptor */
ChannelDesc[16].srcEndPtr =(unsigned int) &srcPtr[61];
ChannelDesc[16].destEndPtr =(unsigned int) &destPtr[61];
ChannelDesc[16].ctrlCfg.src_inc = 0;
ChannelDesc[16].ctrlCfg.dst_inc = 0;
ChannelDesc[16].ctrlCfg.src_size = 0;
ChannelDesc[16].ctrlCfg.n_minus_1 = 61u;
ChannelDesc[16].ctrlCfg.r_power = 0;
ChannelDesc[16].ctrlCfg.cycle_ctrl = 2u;
/* give the address of the built channel descriptor to the DMA controller */
*pREG_DMA0_PDBPTR = (unsigned int) &ChannelDesc[0];
/* Enable the DMA channel 16 */
*pREG_DMA0_EN_SET = (1u << 16u);
/* Enable the peripherals to create DMA requests on channel 16 */
*pREG_DMA0_RMSK_CLR = (1u << 16u);
/* enable the DMA controller */
*pREG_DMA0_CFG = 1u;
/* Generate a software DMA request on channel 16 */
*pREG_DMA0_SWREQ = (1u << 16u);
Example Code: Memory to Peripheral Basic Mode Data Transfer with UART
This example demonstrates how to perform a basic hardware request transfer between the memory blocks and the peripheral buffer register. The example uses the Channel 8 DMA (UART0 transmit) and sends out data from srcPts[] to the universal asynchronous receiver transmitter (UART) terminal.
/* Build the channel descriptor */
ChannelDesc[8].srcEndPtr =(unsigned int) &srcPtr[61];
ChannelDesc[8].destEndPtr =(unsigned int)pREG_UART0_TX;
ChannelDesc[8].ctrlCfg.src_inc = 0;
ChannelDesc[8].ctrlCfg.dst_inc = 3;
ChannelDesc[8].ctrlCfg.src_size = 0;
ChannelDesc[8].ctrlCfg.n_minus_1 = 61u;
ChannelDesc[8].ctrlCfg.r_power = 0;
ChannelDesc[8].ctrlCfg.cycle_ctrl = 1u;
/* enable the DMA controller */
*pREG_DMA0_CFG = 1u;
/* give the address of the built channel descriptor to the DMA controller */
*pREG_DMA0_PDBPTR = (unsigned int) &ChannelDesc[0];
/* Enable the peripherals to create DMA requests on channel 8 */
*pREG_DMA0_RMSK_CLR = (1u << 8u);
/* Enable the DMA channel 8 */
*pREG_DMA0_EN_SET = (1u << 8u);
/* Configure DMA Channel 8 to use primary data structure */
*pREG_DMA0_ALT_CLR = (1u << 8u);
/* pin mux for UART 0 */
*((volatile uint32_t *)REG_GPIO0_CFG) |= UART0_TX_PORTP0_MUX | UART0_RX_PORTP0_MUX ;
/* UART 0 configuration */
/* baud rate = 9600*/
*pREG_UART0_DIV = 0x1C;
*pREG_UART0_FBR = (0x1<<BITP_UART_FBR_FBEN)|(0x3<<BITP_UART_FBR_DIVM)|(0x2E<<BITP_UART_FBR_DIVN);
*pREG_UART0_LCR2 = (0x3<<BITP_UART_LCR2_OSR);
/* parity , stop */
*pREG_UART0_LCR = (0x3<<BITP_UART_LCR_WLS);
/* Enable DMA request from UART0 to DMA controller */
*pREG_UART0_IEN |=(1u << 4u);
Memory Scatter Gather and Peripheral Scatter Gather Mode
The memory scatter gather mode is a repeated ARQ mode and peripheral scatter gather mode is a repeated basic mode where both scatter gather modes have multiple DMA descriptors setup and fired at once. This mode is useful when a huge transfer is performed simultaneously.
This mode involves setting up all the descriptors and moving the descriptors one by one into the alternate channel data structure where the DMA transaction happens. This movement of descriptors from memory to the alternate channel descriptor (ACD) is performed by the primary DMA. Therefore, the primary descriptor of the particular DMA channel populates with information where the source end is the end of the declared descriptors, the destination is the alternate channel descriptors, the word size, and the arbitration sizes, and increments set to 4. Because the arbitration is set to 4, the DMA controller performs four transfers of the word size of the primary (which is the movement of one descriptor into the alternate), then shifts to the alternate where it performs the actual data transfer and then moves back to the primary. The cycle continues till the cycle hits a basic mode DMA descriptor. The DMA done interrupt generates after the completion of each transfer of the descriptor.
The programming sequence for scatter gather mode follows:
- Define the DMA descriptors with the information regarding the transfers to be carried out with the last DMA descriptor in basic mode (if DMA transaction termination is needed).
- Populate the primary channel data structure with the details of moving the declared DMA descriptors into the alternate channel data structure with an arbitration of 4.
- Point the PCD pointer to the base of the channel descriptors.
- Clear the channel request mask for the particular DMA channel.
- Enable the request source interrupt for the DMA and the DMA completion interrupt (optional).
- Enable the corresponding DMA channel.
- Enable the DMA controller by setting the DMA_CFG register to 1.
- Generate the software DMA request (memory scatter gather DMA mode) or wait for the peripheral DMA request to be generated (peripheral scatter gather DMA mode).
The sequence diagram for scatter gather is shown in Figure 3.
Example Code: Memory to Memory Scatter Gather Software Request Data Transfer Within Two Data Blocks of the RAM
This example demonstrates how to perform a DMA scatter gather transfer between blocks of memory. This example uses the DMA Channel 16 (software DMA) descriptor and copies the data from
srcPtr1[]
to
destPtr1[]
,
srcPtr2[]
to
destPtr2[]
, and
srcPtr3[]
to
destPtr3[]
.
/*Build the scatter gather descriptor 1 to copy srcPtr1[] to destPtr1[]*/
ScatterGatherDesc[0].srcEndPtr =(unsigned int) &srcPtr1[9];
ScatterGatherDesc[0].destEndPtr =(unsigned int)&destPtr1[9];
ScatterGatherDesc[0].ctrlCfg.src_inc = 0;
ScatterGatherDesc[0].ctrlCfg.dst_inc = 0;
ScatterGatherDesc[0].ctrlCfg.src_size = 0;
ScatterGatherDesc[0].ctrlCfg.n_minus_1 = 9u;
ScatterGatherDesc[0].ctrlCfg.r_power = 0;
ScatterGatherDesc[0].ctrlCfg.cycle_ctrl = 5u;
/* alternate memory to memory scatter gather */
Build the scatter gather descriptor 2 to copy srcPtr2[] to destPtr2[]*/
ScatterGatherDesc[1].srcEndPtr =(unsigned int) &srcPtr2[9];
ScatterGatherDesc[1].destEndPtr =(unsigned int)&destPtr2[9];
ScatterGatherDesc[1].ctrlCfg.src_inc = 0;
ScatterGatherDesc[1].ctrlCfg.dst_inc = 0;
ScatterGatherDesc[1].ctrlCfg.src_size = 0;
ScatterGatherDesc[1].ctrlCfg.n_minus_1 = 9u;
ScatterGatherDesc[1].ctrlCfg.r_power = 0;
ScatterGatherDesc[1].ctrlCfg.cycle_ctrl = 5u;
/* alternate memory to memory scatter gather */
* Build the scatter gather descriptor 3 to copy srcPtr3[] to destPtr3[]*/
ScatterGatherDesc[2].srcEndPtr =(unsigned int) &srcPtr3[9];
ScatterGatherDesc[2].destEndPtr =(unsigned int)&destPtr3[9];
ScatterGatherDesc[2].ctrlCfg.src_inc = 0;
ScatterGatherDesc[2].ctrlCfg.dst_inc = 0;
ScatterGatherDesc[2].ctrlCfg.src_size = 0;
ScatterGatherDesc[2].ctrlCfg.n_minus_1 = 9u;
ScatterGatherDesc[2].ctrlCfg.r_power = 0;
ScatterGatherDesc[2].ctrlCfg.cycle_ctrl = 2u;
/* the last descriptor has to be ARQ to stop the DMA */
/* enable the DMA controller */
*pREG_DMA0_CFG = 1u;
/* Enable the DMA channel 16 */
*pREG_DMA0_EN_SET = (1u << 16u);
/* Enable the peripherals to create DMA requests on channel 16 */
*pREG_DMA0_RMSK_CLR = (1u << 16u);
/* give the address of the built channel descriptor to the DMA controller */
*pREG_DMA0_PDBPTR = (unsigned int) &ChannelDesc[0];
/* Locate the Alternate channel descriptor for channel 16 in memory */
pChannelDescAlternate =(ADI_DMA_DESC*)((*pREG_DMA0_ADBPTR) + (16 * 16));
uint8_t* ptypIntScatterGatherDesc =(uint8_t*)pScatterGatherDesc;
uint8_t* ptypIntChannelDescAlternate =(uint8_t*)pChannelDescAlternate;
/* Build the primary channel descriptor to move ScatterGatherDesc to ChannelDescAlternate*/
ChannelDesc[16].srcEndPtr =(unsigned int)((ptypIntScatterGatherDesc + 11*4)+3);
ChannelDesc[16].destEndPtr =(unsigned int)((ptypIntChannelDescAlternate + 3*4)+3);
ChannelDesc[16].ctrlCfg.src_inc = 2;
ChannelDesc[16].ctrlCfg.dst_inc = 2;
ChannelDesc[16].ctrlCfg.src_size = 2;
ChannelDesc[16].ctrlCfg.n_minus_1 = 11u;
ChannelDesc[16].ctrlCfg.r_power = 2;
ChannelDesc[16].ctrlCfg.cycle_ctrl = 4u;
/* Generate a software DMA request on channel 16 */
*pREG_DMA0_SWREQ = (1u << 16u);
Ping Pong Mode
The ping pong mode is useful for continuous transfer of data without any breaks during the transfer. In this mode, the DMA controller switches between the primary and alternate descriptors until the controller hits a basic mode descriptor.
Initially, both the primary and the alternate data structures are populated with the DMA descriptor information. The transfer starts from the primary data structure. When the transfer completes, the DMA controller immediately picks up the alternate data structure and starts the next transaction without any delay in switching. When the alternate transfer completes, the transfer goes back to the primary data structure, and the cycle continues until a basic mode descriptor is encountered.
Ensure that when the primary data structure completes and the alternate data structure is transferring, the primary data structure is reset before the alternate data structure completes the transfer and vice versa for the alternate data structure reset.
As previously explained, when a data structure completes a transaction, only the N − 1 field and the cycle control field resets unless the source and destination fields are updated.
The programming sequence for ping pong mode follows:
- Define all DMA descriptors with the information about the transfers carried out with the last DMA descriptor in basic mode (if DMA transaction termination is needed).
- Copy the populated primary and alternate descriptors to the primary and alternate data structure.
- Point the PCD pointer to the base of the primary channel descriptors.
- Clear the channel request mask for the particular channel.
- Enable the request source interrupt for the DMA and the DMA completion interrupt.
- Enable the corresponding DMA channel.
- Enable the DMA controller by setting the DMA_CFG register to 1.
- Generate the software DMA request (in case of a software ping pong DMA request) or wait for the peripheral DMA request to generate.
- In the DMA_DONE interrupt routine, create a flag that signifies which of the primary or alternate transfers is complete. Accordingly, reset the corresponding fields in the primary or alternate descriptor of the channel once the particular transfer completes.
The sequence diagram for ping pong mode is shown in Figure 4.
Example Code: Peripheral to Memory Ping Pong Data Transfer with UART
This example demonstrates how to perform a ping pong hardware request transfer from the memory blocks to the peripheral buffer register. The example uses the Channel 9 DMA (UART0 Rx), the ping and pong alternately stores the received data from UART into srcPtr[] and destPtr[], respectively. When five characters are received from the UART, the ping switches to pong or vice versa. The received five characters are then sent out via the UART.
/* Build the primary channel descriptor */
ChannelDesc[9].srcEndPtr =(unsigned int) pREG_UART0_RX;
ChannelDesc[9].destEndPtr =(unsigned int)&srcPtr[4];
ChannelDesc[9].ctrlCfg.src_inc = 3;
ChannelDesc[9].ctrlCfg.dst_inc = 0;
ChannelDesc[9].ctrlCfg.src_size = 0;
ChannelDesc[9].ctrlCfg.n_minus_1 = 4u;
ChannelDesc[9].ctrlCfg.r_power = 0;
ChannelDesc[9].ctrlCfg.cycle_ctrl = 3u;
/* enable the DMA controller */
*pREG_DMA0_CFG = 1u;
/* Enable the DMA channel 9 */
*pREG_DMA0_EN_SET = (1u << 9u);
/* Enable the peripherals to create DMA requests on channel 9 */
*pREG_DMA0_RMSK_CLR = (1u << 9u);
/* give the address of the built channel descriptor to the DMA controller */
*pREG_DMA0_PDBPTR = (unsigned int) &ChannelDesc[0];
/* Locate the Alternate channel descriptor for channel 9 in memory */
ChannelDescAlternate =(ADI_DMA_DESC*)((*pREG_DMA0_ADBPTR) + (16 * 9));
/* Build the Alternate channel descriptor */
ChannelDescAlternate->srcEndPtr =(unsigned int)pREG_UART0_RX;
ChannelDescAlternate->destEndPtr =(unsigned int)&destPtr[4];
ChannelDescAlternate->ctrlCfg.src_inc = 3;
ChannelDescAlternate->ctrlCfg.dst_inc = 0;
ChannelDescAlternate->ctrlCfg.src_size = 0;
ChannelDescAlternate->ctrlCfg.n_minus_1 = 4u;
ChannelDescAlternate->ctrlCfg.r_power = 0;
ChannelDescAlternate->ctrlCfg.cycle_ctrl = 3u;
/* Enable the DMA channel 9 interrupt in NVIC */
NVIC_EnableIRQ(DMA0_CH9_DONE_IRQn);
/* pin mux for UART 0 */
*((volatile uint32_t *)REG_GPIO0_CFG) |= UART0_TX_PORTP0_MUX | UART0_RX_PORTP0_MUX ;
/* UART 0 configuration */
/* baud rate = 9600*/
*pREG_UART0_DIV = 0x1C;
*pREG_UART0_FBR = (0x1<<BITP_UART_FBR_FBEN)|(0x3<<BITP_UART_FBR_DIVM)|(0x2E<<BITP_UART_FBR_DIVN);
*pREG_UART0_LCR2 = (0x3<<BITP_UART_LCR2_OSR);
/* parity , stop */
*pREG_UART0_LCR = (0x3<<BITP_UART_LCR_WLS);
/* Enable DMA request from UART0 RX to DMA controller */
*pREG_UART0_IEN |=(1u << 5u);