on
2022-11-16
11:44 PM
- edited on
2024-06-04
06:20 AM
by
Laurids_PETERSE
The new DMA module (which includes the GPDMA and the LPDMA) available in series such as the STM32U5 are slightly different than the regular DMA. In this article, we’ll discuss the setup for using GPDMA in a similar way as the standard DMA available on most STM32 series.
The main application benefit for using DMA in any application is to off-load the CPU for data transfer from a memory mapped source to a memory mapped destination. In the image below, it is possible to see the power domains associated with the GPDMA and LPDMA:
As it can be seen in the above image, the GPDMA comes with 2 Ports. The Port0 should be typically allocated for transfers to/from peripherals as there is a direct hardware data path to APB peripherals, outside the AHB matrix. Port1 on the other hand should be typically allocated for transfers to/from memory. In any case, the GPDMA target can be addressed from any port.
Another interesting factor to take into account is that each channel has a FIFO size and it follows this rule: from channel0 to channel 11 the FIFO is 8bytes (2words) – these channels are ideal for transfers from//to an APB/AHB peripheral to SRAM, for example, UART to a buffer. Channels 12 to 15 have a 32byte (8 words) FIFO, so they are better suited for transfers between a high speed AHB peripheral and SRAM, or for transfer from/to external memories.
Alright, now that we got the basics, let’s dive into a real project and see how that goes. For this portion we’ll use STM32CubeIDE (v1.10.1) with the NUCLEO-U575ZI-Q but this code can be easily ported to any STM32 series that has the GPDMA.
Create a new project using the given board as the starting point, this will assign the pins automatically for this board, including the serial port, LED, keys and the USB. If you have any doubts on how to create a project from scratch using a board, we have several articles with this information, but here is one you can use for that purpose (up to the end of step #5). Just remember to select the board with GPDMA you are using to replicate this content. As we are only interested in the UART for this demo, we’ll just disregard the additional portion.
In the *.ioc created, we can locate the “Connectivity” section and expand it to select the USART1, which is connected to the VCOM. Here we’ll use a fairly typical setup for the USART: 115200/8/N/1
Now, the part we really want to interact with, the GPDMA. If you click in the “DMA Settings” tab, it will show the message to go to GPDMA1:
Following the previous guidelines for selecting the GPDMA channels, we’ll assign Channels 10 and 11, both with 2 word FIFO and we’ll set them in Standard Request Mode.
As you’ve noticed in the drop list options there were 2 possible selections, the Standard Request Mode and the Linked List Mode, if you want to know more about the Linked List Mode and why would you use it, please refer to this explanation from ARM’s documentation:
“A linked list is a list of elements that include pointers to the next element in the list. This means that elements can be placed anywhere in memory and created and removed individually without affecting elements around them. They can be used to create arrays of unknown size in memory."
The LLI in the DMA controller consists of four words containing the Source address, Destination Address, LLI register and Control register in that order. For some systems, the LLI data structures can and should be made four-word aligned, to make the loading of LLIs more efficient. Information on how to program the PL080 for scatter/gather is given in Section 3.6 on page 3-35 of the Technical Reference Manual.”
OK, now that the difference is clear, let’s configure the 2 channels: one will be used for USART TX and the other one for USART RX. Just for the sake of showing that both Ports work with the same peripheral, the TX will use Port0 and RX will use Port1.
Here is the configuration for the TX:
Here is the configuration for the RX:
These were all the needed steps, so we can now generate the code (Alt+K).
In the main.c file, create 2 global buffers:
/* USER CODE BEGIN PV */
uint8_t pRxBuff[10];
uint8_t pTxBuff[10] = "Count: \r\n";
/* USER CODE END PV */
In the main function, before the endless loop, add the reception call and a local variable that we will use to count from 0..9 in the main loop and then start over again:
/* USER CODE BEGIN 2 */
HAL_UART_Receive_DMA(&huart1, pRxBuff, 10);
uint8_t u8Inc = 0x30; // 0x30 => ‘0’ ASCII
/* USER CODE END 2 */
Inside the endless loop, let’s transmit the same message every 5 seconds:
/* USER CODE BEGIN 3 */
pTxBuff[7] = u8Inc++;
if(u8Inc > 0x39){
u8Inc = 0x30;
}
HAL_UART_Transmit_DMA(&huart1, pTxBuff, 10); HAL_Delay(5000);
Finally, create the reception complete callback to echo whenever the 10 bytes are received.
/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Transmit_DMA(&huart1,(uint8_t *) "Message Received!\r\n", sizeof("Message Received!\r\n"));
HAL_UART_Receive_DMA(&huart1, pRxBuff, 10);
}
/* USER CODE END 4 */
Here is a quick demonstration of the code flow, where we can see the message typed ‘Hello ST!!” in the pRxBuff and the local variable being used to change the message in the pTxBuff:
Just as a minor note, in case you are not aware, the STM32CubeIDE can be used with its terminal function to send and receive data, which was used in the demonstration above. You can check how to do it in this article and, in case you want to customize your IDE to dark theme, please check this other one.
Hope you enjoyed it!
Hi!!
I noticed your example uses `HAL_UART_Receive_DMA()`
Would you happen to have an example that uses `HAL_UARTEx_ReceiveToIdle_DMA()` ? I tried to use it but got some very strange behavior. Is there some sort of setting in the IOC configuration in order to properly detect IDLE?
Any help would be greatly appreciated.
Warm regards, Anthony
HAL_UART_TxCpltCallback Triggered but HAL_UART_RxCpltCallback was not triggered. Do any more parameters need to be set?
Dev Board uses Nucleo-U5A5ZJ-Q.
Regards
Simon
PS: I had been trying GPDMA with the above board with SPI1 using hal_spi_transmitreceive_dma for past 1 months:face_with_tears_of_joy:. Nothing seems to work.
@B.Montanari could you please share the full project example? unfortunately the configuration is not working and I'am curious to spot the differences in order to understand why HAL_UART_RxCpltCallback is not triggered at all.