2024-10-16 5:30 AM - edited 2025-05-28 1:55 AM
This article provides a step-by-step guide on configuring the GPDMA linked list in STM32CubeMX. It does not cover the legacy standard DMA approach, which is described in the article "How to configure the GPDMA."
The versions used in this tutorial are:
Alternatively to the direct programming mode, a channel can be programmed by a list of transfers, known as a list of linked-list items (LLI). Each LLI is defined by its data structure. The LLI is mapped in memory and contains an image of the values to be initialized into the DMA channel registers. Thus, the DMA channel registers programming becomes an indirect operation.
The GPDMA has lists containing configuration nodes that the GPDMA uses.
Each linked list node will update the following GPDMA registers after the previous GPDMA node is finished:
Transfer register 1 |
Transfer register 2 |
Block register 1 |
Source address register |
Destination address register |
Transfer register 3 |
Block register 2 |
Linked list register |
TR1 |
TR2 |
BR1 |
SAR |
DAR |
TR3 |
BR2 |
LLR |
Start a new project and enable the instruction cache [ICACHE] to reach the maximum performance.
ICache configuration
GPDMA configuration_1
GPDMA configuration_2
Linked_list_Queue_Config
Node1 configuration_2
Node2 Configuration
Node3 Configuration
The first node in the loop is where the LLR from the last node in the queue is pointed. If there is only one node, it reloads the same configuration upon completion.
LLI List Queue
Now, generate the code and switch to STM32CubeIDE.
Firstly, include the linked_list.h in the main.c by adding between /*USER CODE BEGIN Includes*\ and /*USER CODE END Includes*\
/* USER CODE BEGIN Includes */
#include "linked_list.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PV */
extern uint32_t aSRC_Buffer1[8U];
extern uint32_t aSRC_Buffer2[16U];
extern uint32_t aSRC_Buffer3[24U];
extern uint32_t aDST_Buffer1[8U];
extern uint32_t aDST_Buffer2[16U];
extern uint32_t aDST_Buffer3[24U];
/* USER CODE END PV */
/* USER CODE BEGIN PFP */
static void TransferComplete(DMA_HandleTypeDef *hdma);
static void TransferError(DMA_HandleTypeDef *hdma);
/* USER CODE END PFP */
/* USER CODE BEGIN 0 */
__IO uint32_t TransferCompleteDetected = 0U, TransferErrorDetected = 0U;
/* USER CODE END 0 */
/* USER CODE BEGIN Private defines */
#define BUFFER1_SIZE 8U
#define BUFFER2_SIZE 16U
#define BUFFER3_SIZE 24U
/* USER CODE END Private defines */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
uint32_t aSRC_Buffer1[BUFFER1_SIZE] =
{
0x01020304, 0x05060708, 0x090A0B0C, 0x0D0E0F10,
0x11121314, 0x15161718, 0x191A1B1C, 0x1D1E1F20,
};
uint32_t aSRC_Buffer2[BUFFER2_SIZE] =
{
0x01020304, 0x05060708, 0x090A0B0C, 0x0D0E0F10,
0x11121314, 0x15161718, 0x191A1B1C, 0x1D1E1F20,
0x21222324, 0x25262728, 0x292A2B2C, 0x2D2E2F30,
0x31323334, 0x35363738, 0x393A3B3C, 0x3D3E3F40,
};
uint32_t aSRC_Buffer3[BUFFER3_SIZE] =
{
0x01020304, 0x05060708, 0x090A0B0C, 0x0D0E0F10,
0x11121314, 0x15161718, 0x191A1B1C, 0x1D1E1F20,
0x21222324, 0x25262728, 0x292A2B2C, 0x2D2E2F30,
0x31323334, 0x35363738, 0x393A3B3C, 0x3D3E3F40,
0x41424344, 0x45464748, 0x494A4B4C, 0x4D4E4F50,
0x51525354, 0x55565758, 0x595A5B5C, 0x5D5E5F60,
};
uint32_t aDST_Buffer1[BUFFER1_SIZE];
uint32_t aDST_Buffer2[BUFFER2_SIZE];
uint32_t aDST_Buffer3[BUFFER3_SIZE];
/* USER CODE END PM */
/* USER CODE BEGIN PV */
extern DMA_QListTypeDef Queue;
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
MX_Queue_Config();
/* USER CODE END 2 */
This function creates the node configuration from the structure by using HAL_DMAEx_List_BuildNode. The node is copied in GPDMA registers without linking the nodes yet.
To link the nodes together STM32CubeMX, use HAL_DMAEx_List_InsertNode_Tail to create the queue.
/* USER CODE BEGIN 2 */
MX_Queue_Config();
HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel0, &Queue);
/* USER CODE END 2 */
/* USER CODE BEGIN 4 */
static void TransferComplete(DMA_HandleTypeDef *hdma)
{
TransferCompleteDetected = 1U;
}
static void TransferError(DMA_HandleTypeDef *hdma)
{
TransferErrorDetected = 1U;
}
/* USER CODE END 4 */
/* USER CODE BEGIN 2 */
MX_Queue_Config();
HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel0, &Queue);
HAL_DMA_RegisterCallback(&handle_GPDMA1_Channel0, HAL_DMA_XFER_ERROR_CB_ID, TransferError);
/* USER CODE END 2 */
if (HAL_DMAEx_List_Start_IT(&handle_GPDMA1_Channel0) != HAL_OK)
{
Error_Handler();
}
while ((TransferCompleteDetected == 0) && (TransferErrorDetected == 0U));
if (TransferErrorDetected == 1U)
{
Error_Handler();
}
/* USER CODE END 2 */
By following these steps, you can successfully configure the GPDMA linked list in STM32CubeMX and run it using STM32CubeIDE.
Hello,
Thank you for the fast reply.
Is there any way that I can achieve the same functionality without using interruptions?
Can you suggest me any material, videos, etc... ?
Thank you,
Marc
Halo
On this section of code :
/* USER CODE BEGIN 2 */
MX_Queue_Config();
HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel0, &Queue);
HAL_DMA_RegisterCallback(&handle_GPDMA1_Channel7, HAL_DMA_XFER_CPLT_CB_ID, TransferComplete);
HAL_DMA_RegisterCallback(&handle_GPDMA1_Channel0, HAL_DMA_XFER_ERROR_CB_ID, TransferError);
/* USER CODE END 2 */
On the line 5, why "handle_GPDMA1_Channel7" not "handle_GPDMA1_Channel0"?
Am I missing something ?
thank you.
Hello @Silexman,
Thank you for pointing that out. The use of handle_GPDMA1_Channel7 in the line you mentioned was indeed part of a test scenario and not intended for the final example. I will proceed to correct it!
Hello,
Thank you for the tutorial.
I have a general question about the intended use case for linked-lists in GPDMA: The tutorials, CubeMX examples, and HAL docs (e.g., HAL_DMAEx_List_ functions) seem to emphasize building the entire queue statically during initialization and then executing it unchanged at runtime. Is it a good idea – or even supported – to modify the linked-list dynamically during runtime, such as appending new nodes while the DMA channel is active?
More specifically, in my use case: I'm using an STM32H523 to receive and process data from a sensor and forward them via USART. Without linked-lists, I'd implement a custom FIFO queue in software (e.g., ring buffer of message pointers) and trigger individual DMA transfers sequentially via HAL_UART_Transmit_DMA in the TC interrupt – simple and predictable. With linked-lists, I could offload the FIFO handling to hardware by appending a new node (with updated src addr and length) to the tail for each message, reducing CPU overhead. However, I'm guessing that ensuring atomic modifications (e.g., via disabling interrupts or channel suspension) could be tricky, especially at high rates, and might negate the benefits if not done carefully.
Is the GPDMA linked-list feature intended for such dynamic, runtime-appended queues?
Thanks in advance for any insights or references!
Best regards,
Hello @cm_esomatec,
Thanks for the question.
I think the closest solution of what you're asking about is the link step mode GPDMA_CxCR.LSM = 1), which allows the software to defer the elaboration of the next linked-list item (LLIn+1) until after the current transfer (LLIn) is executed.
It can be used to dynamically elaborate LLIs in memory during run-time (check section 16.4.8 in RM), the DMA channel executes one node at a time, and the CPU can safely prepare and insert the next node after the previous transfer is complete.
Hope that helps!