Skip to main content
DJ1
Associate III
May 9, 2023
Question

UART DMA Rx/Tx

I am working on STM32F401RE, transmitting data via UART using DMA 1(channel 6) , also i want to receive data using DMA 1 (channel 5). I want to use the DMA in single byte transfers in direct mode. Can you help me how to do so correctly because i tried following single byte transfers from the reference manual but it didn't work. I am getting output for the same in Circular mode. Can any share proper procedure for the same.

This topic has been closed for replies.

11 replies

waclawek.jan
Super User
May 9, 2023

I don't see point in transferring 1 byte using DMA.

JW

Danish1
Lead III
May 9, 2023

"it didn't work" doesn't give us much of a clue as to why or how things weren't as you expected.

Help us to help you. Show us what you did (i.e. source code), what you expected to happen, what actually happened.

How strongly do you know the hardware is set up as needed? Does the UART behave when you're doing direct and/or interrupt-driven transfers?

The Reference Manual is the definitive source for everything we need to know about an stm32. But it can be difficult to understand what's written there. So it's useful sometimes to look at the STM32Cube sample code as well.

I'm not sure what you mean by "direct mode" and "single byte transfers". The overheads of DMA mean it's only worth doing when you have multiple bytes to send and/or receive.

There's a complication for UART RX with DMA. If you don't know how many characters that will arrive, the DMA won't give a "transfer complete" interrupt when a message is complete.

DJ1
DJ1Author
Associate III
May 10, 2023
void dma_init()
{
	// Disable the direct mode
	//DMA1->DMA_S6FCR |= (1 << 2);
	// FIFO threshold setting
	DMA1->DMA_S6FCR |= (3 << 0);
	// Set channel 4 for DMA stream6 for UART_Tx
	DMA1->DMA_S6CR |= (4 << 25);
	// MBURST selection for INCR4 beats
	//DMA1->DMA_S6CR |= (1 << 23);
	// PBURST selection for single transfer of byte beats
	DMA1->DMA_S6CR &= ~(3 << 21);
	// MSIZE 1 byte
	DMA1->DMA_S6CR &= ~(3 << 13);
	// PSIZE 1 byte
	DMA1->DMA_S6CR &= ~(3 << 12);
	// MINC EN, PINC at 0
	DMA1->DMA_S6CR |= (1 << 10);
	// Circular mode enable
	//DMA1->DMA_S6CR |= (1 << 8);
	// DIR 1, Memory to Peripheral
	DMA1->DMA_S6CR |= (1 << 6);
	// Flow Controller here is DMA
	DMA1->DMA_S6CR &= ~(1 << 5);
	// No of Data Register: store number of bytes
	DMA1->DMA_S6NDTR = 8;
}
 
void dma_call()
{
	// Peripheral Address
	DMA1->DMA_S6PAR = (uint32_t) &(USART_2.pUSARTx->USART_DR);
	// Memory address
	DMA1->DMA_S6M0AR = (uint32_t) tx;
	// Enable DMA bit
	DMA1->DMA_S6CR |= (1 << 0);
 
}
 
 
 
int main()
{
	GPIOA_PCLK_EN;
 
	// timer init
	tim_init();
 
	// GPIO AF
	GPIO_USART.pGPIOx = GPIOA;
	GPIO_USART.GPIO_PinConfig.GPIO_PinMode = GPIO_MODE_AF;
	GPIO_USART.GPIO_PinConfig.GPIO_PinAltFunMode = 7;
	GPIO_USART.GPIO_PinConfig.GPIO_PinOPType = GPIO_OUT_PUSHPULL;
	GPIO_USART.GPIO_PinConfig.GPIO_PinOutSpeed = GPIO_OUT_SPEED_HIGH;
	GPIO_USART.GPIO_PinConfig.GPIO_PinPuPdControl = GPIO_OUT_PU;
 
	// Here it is very important to call the GPIO_init func for each and every pin initialization
	// bcoz or else pinmode selection does not happen properly if dealing with multiple pin init.
	// Rx Pin
	GPIO_USART.GPIO_PinConfig.GPIO_PinNumber = PIN_NO_3;
	GPIO_Init(&GPIO_USART);
	// Tx Pin
	GPIO_USART.GPIO_PinConfig.GPIO_PinNumber = PIN_NO_2;
	GPIO_Init(&GPIO_USART);
 
 
 
	// RCC enable
	USART2_CLK_EN;
 
	// USART 2 peripheral init
	USART_2.pUSARTx = USART2;
	USART_2.USART_Config.Baud_Rate = USART_BAUD_9600;
	USART_2.USART_Config.NoOfStopBits = USART_STOPB_1_;
	USART_2.USART_Config.WordLength = USART_WORDLEN_8;
	USART_2.USART_Config.USART_Mode = USART_TxRx;
	USART_2.USART_Config.ParityControl = USART_Parity_Disable;
	USART_2.USART_Config.HWFlowControl = USART_DI_RTS_CTS;
	// USART Init
	USART_Init(&USART_2);
 
	//UART DMA transmitter enable
	USART_2.pUSARTx->USART_CR3 |= (1 << 7);
 
	// Enable UART RxNE Interrupt
	// USART_2.pUSARTx->USART_CR1 |= ((1 << 5));
	// USART Peripheral Enable
	USART_EnDi(USART_2.pUSARTx, 1);
 
	// DMA CLK EN
	DMA1_CLK_EN;
	dma_init();
 
	// dma function call
	dma_call();
 
 
 
	while(1)
	{
	}
}

Danish1
Lead III
May 11, 2023
	// MSIZE 1 byte
	DMA1->DMA_S6CR &= ~(3 << 13);
	// PSIZE 1 byte
	DMA1->DMA_S6CR &= ~(3 << 12);

@DJ​ Watch out. PSIZE runs from 11 to 12 - not 12 to 13.

There are lots of useful #defines in e.g. #include <stm32f4xx.h> so the sort of thing I do is:

DMA1->DMA_S6CR = (channel_number * DMA_SxCR_CHSEL_0) | DMA_SxCR_PL_1 | DMA_SxCR_MINC | DMA_SxCR_DIR_0 | DMA_SxCR_TCIE;

It's possible you've made other errors in your register accesses, so please re-code making use of those #defines to see if it makes a difference.

I don't bother with the DMA FIFO at all. There might be some saving in terms of number of bus accesses, but UARTs are generally so slow that a few extra bus accesses won't have significant impact on the performance of your system.

DJ1
DJ1Author
Associate III
May 10, 2023

NOTE: In the above code all the header files as well as controller specific header file has also been developed by us, so you might find the names to be little different.

If you go through the reference manual for the same MCU board you will find there is "Direct Mode" and "FIFO Mode", in case of FIFO mode the data gets transmitted based on the FIFO threshold level. In case of Direct mode the byte gets transferred without FIFO. In case of "Circular DMA mode" the code is working fine as it is continuously transmitting data, but i want the DMA transfer to happen only once. Meaning that entire buffer of data should be transmitted and then the DMA should halt.

This is Direct Mode:

"When the DMA is configured in direct mode (FIFO disabled), to transfer data in memory-to-

peripheral mode, the DMA preloads one data from the memory to the internal FIFO to

ensure an immediate data transfer as soon as a DMA request is triggered by a peripheral."

Things i tried:

  1. I tried enabling the DMA interrupts like Half transfer and Transfer Complete but it is not even generating an interrupt though my data is getting received as well as transmitted in case of "Circular Mode."
  2. I tried disabling the "Direct mode" by setting the DMDIS bit in DMA FIFO control register. Again with FIFO mode only, circular buffer is working but if i want data transfers to happen only once that is not happening.

Now, as you said there is no point in using DMA for single byte. Got it. But what if i dont want the transmission to take place continuously. It should transmit the data buffer of lets say 10 bytes, all at once and then stop. Again when my data gets updated due to some external dependencies then iw ill transmit that updated buffer again. This is how I want to control DMA for my application. Only when required.

At last simply consider i have a buffer of 10 bytes which gets updated after a certain interval. I will call my "dma_call" function once i get all my data inside buffer updated. Then only the DMA transfer should take place. How can i achieve that. Please do share some insights.

Thankyou for replying!

DJ1
DJ1Author
Associate III
May 10, 2023

You may find additional header files and code files inside the zip.

Piranha
Principal III
May 10, 2023

Use the CMSIS defined peripheral, register and bit field definitions, not reinvent those.

DJ1
DJ1Author
Associate III
May 11, 2023

True. But it was our need to work in a manner without any dependencies and involving only the peripherals we are using for our application, just because of that we developed the files with our own conventions.

What is your say, on how to resolve the requirement.

"At last simply consider i have a buffer of 10 bytes which gets updated after a certain interval. I will call my "dma_call" function once i get all my data inside buffer updated. Then only the DMA transfer should take place. How can i achieve that. Please do share some insights."

waclawek.jan
Super User
May 10, 2023

Polled UART Rx works?

Read out and check/post content of UART, DMA and relevant GPIOn registers.

JW

DJ1
DJ1Author
Associate III
May 11, 2023

In the New Folder zip file, i have attached all the registers and GPIO's being used and the related files. If you can just help me with a basic process on how to send a specific length of data using UART Tx DMA that also only when needed. It would be of great help!

Piranha
Principal III
May 11, 2023

The transmit with DMA is simple. When you have the data in the buffer, just set up a single DMA transfer. When that finishes, check the state of the buffer and, if necessary, set up another transfer. When the buffer overlaps, set the transfer to the end of the buffer and let the beginning of the buffer be handled by the next transfer.

DMA1->DMA_S6CR

You've made it worse. The DMA streams are effectively a separate peripheral instances. With the original definitions those can be swapped easily, but not possible with your implementation. I hope you do understand that unused definitions have no impact on the generated code. And, if still doing it, I would just cut out the unnecessary parts so that it can be easily reviewed and maintained later with merge tools.