In the previous guide (here), we took a look how to configure the UART to receive a character using interrupt and echo back the received character. In this guide, we shall use DMA to transmit data.
In this guide, we shall cover the following:
- Enable DMA for TX in UART.
- Configure the DMA.
- Send data using DMA.
- Code.
- Results.
1. Enable DMA for TX in UART:
To enable DMA Transmit for UART, the DMAT in Control Register 3 of USART2 (USART_CR3) need to be set:
/*Enable DMA for TX part(DMAT)*/ USART2->CR3|=USART_CR3_DMAT;
Thats all for UART part.
2. Configure the DMA:
Before we start configuring the DMA, we need to enable clock access to it. To find which bus the DMA is connected to, we need to check the block diagram of STM32F103 in the datasheet which states that DMA is connected to AHB bus:
Hence, we can enable it as following:
/*Enable clock access to DMA1*/ RCC->AHBENR|=RCC_AHBENR_DMA1EN;
Then we need to know which channel is for UART2_TX, from the reference manual, we can find that channel7 is responsible for USART2_TX:
Hence, the channel shall be configured as following:
- Memory increment mode.
- Direction is read from memory.
- Transfer Complete interrupt.
DMA1_Channel7->CCR|=DMA_CCR_MINC|DMA_CCR_DIR|DMA_CCR_TCIE;
Enable DMA1_Channel7 interrupt in NVIC:
NVIC_EnableIRQ(DMA1_Channel7_IRQn);
Set the peripheral address to be USART2->DR as following:
/*Set the peripheral address to be USART2->DR*/ DMA1_Channel7->CPAR=(uint32_t)&USART2->DR;
For the interrupt handler:
- Check if the interrupt source is transfer complete, if it is,
- Set done to 1.
- Disable the DMA channel.
- Clear the pending interrupt flag.
void DMA1_Channel7_IRQHandler(void) { if((DMA1->ISR &DMA_ISR_TCIF7)) { /*Set done to 1.*/ done=1; /*Disable DMA1_Channel7*/ DMA1_Channel7->CCR&=~DMA_CCR_EN; /*Clear the pending interrupt flag*/ DMA1->IFCR|=DMA_IFCR_CTCIF7; } }
3. Send data using DMA:
To send the data, We need the following steps:
- Clear the pending flag.
- Set the memory address to be the buffer to be send.
- Set the length to be the length of the data to be send.
- Enable the DMA channel.
void uart_dma_transmit(char *data, uint16_t length) { /*Clear transfer complete flag */ DMA1->IFCR|=DMA_IFCR_CTCIF7; /*Set the memory address to be the data buffer */ DMA1_Channel7->CMAR=(uint32_t)data; /*Set the number of transfer*/ DMA1_Channel7->CNDTR=length; /*Enable DMA1_channel7 stream*/ DMA1_Channel7->CCR|=DMA_CCR_EN; }
Within while(1), we can call the function as following:
uint16_t len=sprintf(data_send,"Counter value=%d\r\n",counter++); uart_dma_transmit(data_send,len); while(done==0); done=0;
4. Code:
The entire code as following:
#include "stm32f1xx.h" #include "stdio.h" #define Perpher_CLK 8000000 #define Baudrate 115200 static uint16_t compute_uart_bd(uint32_t PeriphClk, uint32_t BaudRate) { return ((PeriphClk + (BaudRate/2U))/BaudRate); } static void uart_set_baudrate(USART_TypeDef *USARTx, uint32_t PeriphClk, uint32_t BaudRate) { USARTx->BRR = compute_uart_bd(PeriphClk,BaudRate); } void uart2_write(char ch) { /*Make sure the transmit data register is empty*/ while(!(USART2->SR & USART_SR_TXE)){} /*Write to transmit data register*/ USART2->DR = (ch & 0xFF); } volatile uint8_t done=0; char data_send[100]; uint8_t counter; void uart_dma_transmit(char *data, uint16_t length) { /*Clear transfer complete flag */ DMA1->IFCR|=DMA_IFCR_CTCIF7; /*Set the memory address to be the data buffer */ DMA1_Channel7->CMAR=(uint32_t)data; /*Set the number of transfer*/ DMA1_Channel7->CNDTR=length; /*Enable DMA1_channel7 stream*/ DMA1_Channel7->CCR|=DMA_CCR_EN; } int main(void) { /*UART2 Pin configures*/ //enable clock access to GPIOA RCC->APB2ENR|=RCC_APB2ENR_IOPAEN; //Enable clock access to alternate function RCC->APB2ENR|=RCC_APB2ENR_AFIOEN; /*Confgiure PA2 as output maximum speed to 50MHz * and alternate output push-pull mode*/ GPIOA->CRL|=GPIO_CRL_MODE2; GPIOA->CRL|=GPIO_CRL_CNF2_1; GPIOA->CRL&=~GPIO_CRL_CNF2_0; /*Configure PA3 as Input floating*/ /*Set mode to be input*/ GPIOA->CRL &=~(GPIO_CRL_MODE3); GPIOA->CRL|=GPIO_CRL_CNF3_0; GPIOA->CRL&=~GPIO_CRL_CNF3_1; /*Don't remap the pins*/ AFIO->MAPR&=~AFIO_MAPR_USART2_REMAP; /*USART2 configuration*/ //enable clock access to USART2 RCC->APB1ENR|=RCC_APB1ENR_USART2EN; //Transmit Enable USART2->CR1 |= USART_CR1_TE; //Enable receiver USART2->CR1 |= USART_CR1_RE; /*Enable DMA for TX part(DMAT)*/ USART2->CR3|=USART_CR3_DMAT; /*Set baudrate */ uart_set_baudrate(USART2,Perpher_CLK,Baudrate); /* DMA Section*/ /*Enable clock access to DMA1*/ RCC->AHBENR|=RCC_AHBENR_DMA1EN; /* Configure DMA as following: * Memory increment mode. * Direction read from memory. * Transfer complete interrupt. * */ DMA1_Channel7->CCR|=DMA_CCR_MINC|DMA_CCR_DIR|DMA_CCR_TCIE; /*Enable DMA1_Channel interrupt in NVIC*/ NVIC_EnableIRQ(DMA1_Channel7_IRQn); /*Set the peripheral address to be USART2->DR*/ DMA1_Channel7->CPAR=(uint32_t)&USART2->DR; //Enable UART USART2->CR1 |= USART_CR1_UE; while(1) { uint16_t len=sprintf(data_send,"Counter value=%d\r\n",counter++); uart_dma_transmit(data_send,len); while(done==0); done=0; } } void DMA1_Channel7_IRQHandler(void) { if((DMA1->ISR &DMA_ISR_TCIF7)) { /*Set done to 1.*/ done=1; /*Disable DMA1_Channel7*/ DMA1_Channel7->CCR&=~DMA_CCR_EN; /*Clear the pending interrupt flag*/ DMA1->IFCR|=DMA_IFCR_CTCIF7; } }
5. Results:
After connected FTDI USB-TTL converter with RX pin of FTDI to PA2, open your favourite serial terminal program, set the baudrate to 115200 and you should get the following:
Happy coding đ
8 Comments
Great Tutorial~
I follow steps in this article, but I found DMA1_Channel7_IRQHandler triggered only once, when I press the boot button. And then no more transmission. My MCU is STM32F103X6T6, Could you please make a downloadable demo file like other articles of yours?
Hi,
Double check if you are disabling the DMA in the interrupt handler.
Thanks for the reply. Yes, I disable DMA in DMA1_Channel7_IRQHandler. but the function IRQHandler not triggered.And If I disable DMA in main.cpp loop, UART transmit periodically. So, I suppose the problem is IRQHandler not called.
uart_dma.h
class UART_DMA_K {
public:
volatile unsigned char inprogress = 0;
volatile unsigned char done = 0;
UART_DMA_K(){
RCC->AHBENR |= RCC_AHBENR_DMA1EN;//enable DMA1
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;//enable portA
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
GPIOA->CRL |= GPIO_CRL_MODE2;//PA2,TX,50Mhz
GPIOA->CRL &= ~GPIO_CRL_CNF2_0;//clear CNF2
GPIOA->CRL |= GPIO_CRL_CNF2_1;//PA2,TX,push-pull
GPIOA->CRL &= ~GPIO_CRL_MODE3;//PA3,RX,input mode
GPIOA->CRL &= ~GPIO_CRL_CNF3_0;//clear CNF10
GPIOA->CRL |= GPIO_CRL_CNF3_1;//PA3,RX,floating input
AFIO->MAPR &= ~AFIO_MAPR_USART2_REMAP;
Try c language rather than cpp.
Thanks for the reply. I do disabled DMA in the DMA1_Channel7_IRQHandler ,but not working. If I put DMA1_Channel7->CCR &= ~DMA_CCR_EN in main loop, UART transmit periodically. So, I suppose that DMA1_Channel7_IRQHandler not triggered.
Yesďź C works! and I found that compile in C++, the .list file,there is no DMA1_Channel7_Handler function , instead , there is a ADC1_2_IRQHandler. It should be the source of problem.
Thanks for the great tutorial. I learned a lot.
Finally, I put the IRQHandler function in main.cpp and enclose by extern “C” {}, it works.
Add Comment