Getting Started with STM32F103: UART Transmitter with DMA

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

  • Kenji Posted December 30, 2022 9:05 am

    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?

    • Husamuldeen Posted December 30, 2022 9:20 am

      Hi,
      Double check if you are disabling the DMA in the interrupt handler.

      • Kenji Posted December 31, 2022 12:29 am

        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.

  • Kenji Posted December 31, 2022 12:31 am

    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;

    • Husamuldeen Posted December 31, 2022 4:18 pm

      Try c language rather than cpp.

  • Kenji Posted December 31, 2022 12:38 am

    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.

  • Kenji Posted January 1, 2023 6:57 am

    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.

  • Kenji Posted January 1, 2023 11:56 am

    Finally, I put the IRQHandler function in main.cpp and enclose by extern “C” {}, it works.

Add Comment

Your email address will not be published. Required fields are marked *