STM32 EmbeddedC Baremetal Programming
Working with USART and DMA in STM32 (ARM Cortex M3)
A step-by-step guide on how to use DMA to transmit and receive data to and from USART
I/O is one of our embedded application's slowest and most time-consuming parts. The application runs the best when the CPU is given the responsibility it is meant for i.e. processing. If we can delegate the task of reading/writing from memory to reading/writing the peripherals to someone else, then we can achieve this. This is when DMA comes to our help.
DMA (Direct Memory Acess)
Direct memory access (DMA) is used in order to provide high-speed data transfer between peripherals and memory as well as memory to memory. Data can be quickly moved by DMA without any CPU actions. This keeps CPU resources free for other operations.
STM32F1 has a single DMA controller with 7 channels. It also has an arbiter for handling the priority between the DMA requests.
Features
- 7 channels with individual software triggers
- 4 software-programmable priority levels
- Independent source and destination transfer sizes (byte, half-word, and word)
- Support for circular buffer management
- 3 event flags for transfer-complete, half-transfer-complete and transfer-error
- Programmable number of transfers: up to 65535
- Access to Flash, SRAM, APB1, APB2 and AHB peripherals as source and destination
Transfer direction
Data can be transferred in any one of the following directions
- Memory to Memory
- Memory to Peripheral
- Peripheral to Memory
- Peripheral to Peripheral
DMA Transactions
After an event, the peripheral sends a request signal to the DMA Controller. The DMA controller serves the request depending on the channel priorities. As soon as the DMA Controller accesses the peripheral, an Acknowledge is sent to the peripheral by the DMA Controller.
The peripheral releases its request as soon as it gets the Acknowledge from the DMA Controller. Once the request is de-asserted by the peripheral, the DMA Controller releases the Acknowledge. If there are more requests, the peripheral can initiate the next transaction.
This complete flow is termed as 1 DMA Transaction. It consists of the following 3 operations:
- Data is loaded from the memory pointed by
CPAR/CMAR
register into an internal holding register - Data is then transferred from the holding register to the address pointed by the memory pointed by
CMAR/CPAR
register according to the direction of the transaction - Decrement the pending transactions counter
DMA Channels
Each channel can handle DMA transfer between a peripheral register located at a fixed address and a memory address. The amount of data to be transferred (up to 65535) is programmable. The register which contains the number of data items to be transferred is decremented after each transaction.
Programmable data sizes
Transfer data sizes of the peripheral and memory are fully programmable through the PSIZE
and MSIZE
bits in the DMA_CCRx
register.
Pointer incrementation
The memory and peripheral counters can optionally be incremented after each transaction. This feature can be enabled/disabled using the PINC
and MINC
bits in the DMA_CCRx
register.
Circular mode
After the counter (number of data items to be transferred) reaches 0, it is reloaded with the initial value and the process repeats itself.
Channel configuration procedure
The following sequence should be followed to configure the DMA channel. In this example, we have configured channel 4 of DMA1 to transmit data on USART1 in circular mode.
Set peripheral address
Set the peripheral register address in the DMA_CPARx
register. The data will be moved to this address from the memory after the peripheral event.
Set memory address
Set the memory address in the DMA_CMARx
register. The data will be written to or read from this memory after the peripheral event.
Set the total no. of transactions
Configure the total number of data to be transferred in the DMA_CNDTRx
register. After each peripheral event, this value will be decremented.
Configure channel priority
Configure the channel priority using the PL[1:0] bits in the DMA_CCRx
register.
Configure transaction properties
Configure data transfer direction, peripheral & memory incremented mode, and peripheral & memory data size in the DMA_CCRx
register.
Enable circular mode and interrupts
Enable circular mode to repeat transactions continuously and enable interrupt after half and/or full transfer.
Activate channel
Activate the channel by setting the ENABLE bit in the DMA_CCRx register. As soon as the channel is enabled, it can serve any DMA request from the peripheral connected to the channel.
Once half of the bytes are transferred, the half-transfer flag (HTIF) is set and an interrupt is generated if the Half-Transfer Interrupt Enable bit (HTIE) is set. At the end of the transfer, the Transfer Complete Flag (TCIF) is set and an interrupt is generated if the Transfer Complete Interrupt Enable bit (TCIE) is set.
USART
The USART1 Transmitter is mapped to Channel 4 of DMA1. You can configure the USART1 peripheral by following the directions given in the following article.
Enable DMA Request for USART Transmitter
USART transmitter can be configured to send requests to DMA when the data register is empty. This can be achieved by setting the DMAT
bit in the USARTx_CR3
register.
main() function
This function sets up the clock and initializes the DMA and USART1 peripherals.
as circular mode is enabled, we need not do anything after this. The DMA controller will retransmit the string continuously.
Getting the source code
The complete source code for this demo is available in the following GitHub repository. Please follow the instructions listed in the README file for building and flashing the binary on the target micro-controller.