Skip to main content
Explorer
June 12, 2026
Question

STM32H5 GPDMA software controlled double buffering

  • June 12, 2026
  • 1 reply
  • 69 views

Hello,

im in the process of trying to make a LED Matrix driver with the gpdma on my stm32h562. Currently, I have everything working with one buffer, but want to implement double buffering so i can fill one buffer with data, and read from the other.


I was looking around on the internet on how to implement this, but i found only one thread with the same problem, but i cant figure out how to implement the solution into my project, as there is no LLIx.nextLLI definition anywhere. Im using CubeMx to generate me code.
 

/* USER CODE BEGIN Header */
/**
******************************************************************************
* File Name : linked_list.c
* Description : This file provides code for the configuration
* of the LinkedList.
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "linked_list.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "main.h"
#include "Colours.h"
#include "Buffers.h"
#include "Fonts.h"
/* USER CODE END Includes */

DMA_NodeTypeDef RGB_transfer1;
DMA_QListTypeDef RGB_queue;
DMA_NodeTypeDef RGB_transfer2;
DMA_NodeTypeDef ACTrans;
DMA_QListTypeDef ACtransQ;

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

extern uint32_t BsrrBuf1[BsrrBufXsize * BsrrBufYsize];
extern uint32_t BsrrBuf2[BsrrBufXsize * BsrrBufYsize];



uint32_t ACtransferLUT[] = {
(1U << (4 + 16)) | (1U << (3 + 16)) | (1U << (2 + 16)) | (1U << (1 + 16)),
(1U << (4)) | (1U << (3 + 16)) | (1U << (2 + 16)) | (1U << (1 + 16)),
(1U << (4 + 16)) | (1U << (3)) | (1U << (2 + 16)) | (1U << (1 + 16)),
(1U << (4)) | (1U << (3)) | (1U << (2 + 16)) | (1U << (1 + 16)),
(1U << (4 + 16)) | (1U << (3 + 16)) | (1U << (2)) | (1U << (1 + 16)),
(1U << (4)) | (1U << (3 + 16)) | (1U << (2)) | (1U << (1 + 16)),
(1U << (4 + 16)) | (1U << (3)) | (1U << (2)) | (1U << (1 + 16)),
(1U << (4)) | (1U << (3)) | (1U << (2)) | (1U << (1 + 16)),
(1U << (4 + 16)) | (1U << (3 + 16)) | (1U << (2 + 16)) | (1U << (1)),
(1U << (4)) | (1U << (3 + 16)) | (1U << (2 + 16)) | (1U << (1)),
(1U << (4 + 16)) | (1U << (3)) | (1U << (2 + 16)) | (1U << (1)),
(1U << (4)) | (1U << (3)) | (1U << (2 + 16)) | (1U << (1)),
(1U << (4 + 16)) | (1U << (3 + 16)) | (1U << (2)) | (1U << (1)),
(1U << (4)) | (1U << (3 + 16)) | (1U << (2)) | (1U << (1)),
(1U << (4 + 16)) | (1U << (3)) | (1U << (2)) | (1U << (1)),
(1U << (4)) | (1U << (3)) | (1U << (2)) | (1U << (1))
};

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/**
* @brief DMA Linked-list RGB_queue configuration
* @param None
* @retval None
*/
HAL_StatusTypeDef MX_RGB_queue_Config(void)
{
HAL_StatusTypeDef ret = HAL_OK;
/* DMA node configuration declaration */
DMA_NodeConfTypeDef pNodeConfig;

/* Set node configuration ################################################*/
pNodeConfig.NodeType = DMA_GPDMA_LINEAR_NODE;
pNodeConfig.Init.Request = DMA_REQUEST_SW;
pNodeConfig.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
pNodeConfig.Init.Direction = DMA_MEMORY_TO_MEMORY;
pNodeConfig.Init.SrcInc = DMA_SINC_INCREMENTED;
pNodeConfig.Init.DestInc = DMA_DINC_FIXED;
pNodeConfig.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_WORD;
pNodeConfig.Init.DestDataWidth = DMA_DEST_DATAWIDTH_WORD;
pNodeConfig.Init.SrcBurstLength = 1;
pNodeConfig.Init.DestBurstLength = 1;
pNodeConfig.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
pNodeConfig.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
pNodeConfig.Init.Mode = DMA_NORMAL;
pNodeConfig.TriggerConfig.TriggerMode = DMA_TRIGM_SINGLE_BURST_TRANSFER ;
pNodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_RISING;
pNodeConfig.TriggerConfig.TriggerSelection = GPDMA2_TRIGGER_TIM12_TRGO;
pNodeConfig.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
pNodeConfig.DataHandlingConfig.DataAlignment = DMA_DATA_RIGHTALIGN_ZEROPADDED;
pNodeConfig.SrcAddress = (uint32_t)&BsrrBuf1[0];
pNodeConfig.DstAddress = ((uint32_t)&(GPIOC->BSRR));
pNodeConfig.DataSize = 16384U;

/* Build RGB_transfer1 Node */
ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &RGB_transfer1);

/* Insert RGB_transfer1 to Queue */
ret |= HAL_DMAEx_List_InsertNode_Tail(&RGB_queue, &RGB_transfer1);

/* Set node configuration ################################################*/
pNodeConfig.SrcAddress = (uint32_t)&BsrrBuf2[0];

/* Build RGB_transfer2 Node */
ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &RGB_transfer2);

/* Insert RGB_transfer2 to Queue */
ret |= HAL_DMAEx_List_InsertNode_Tail(&RGB_queue, &RGB_transfer2);

ret |= HAL_DMAEx_List_SetCircularModeConfig(&RGB_queue, &RGB_transfer1);

return ret;
}

/**
* @brief DMA Linked-list ACtransQ configuration
* @param None
* @retval None
*/
HAL_StatusTypeDef MX_ACtransQ_Config(void)
{
HAL_StatusTypeDef ret = HAL_OK;
/* DMA node configuration declaration */
DMA_NodeConfTypeDef pNodeConfig;

/* Set node configuration ################################################*/
pNodeConfig.NodeType = DMA_GPDMA_LINEAR_NODE;
pNodeConfig.Init.Request = DMA_REQUEST_SW;
pNodeConfig.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
pNodeConfig.Init.Direction = DMA_MEMORY_TO_MEMORY;
pNodeConfig.Init.SrcInc = DMA_SINC_INCREMENTED;
pNodeConfig.Init.DestInc = DMA_DINC_FIXED;
pNodeConfig.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_WORD;
pNodeConfig.Init.DestDataWidth = DMA_DEST_DATAWIDTH_WORD;
pNodeConfig.Init.SrcBurstLength = 1;
pNodeConfig.Init.DestBurstLength = 1;
pNodeConfig.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
pNodeConfig.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
pNodeConfig.Init.Mode = DMA_NORMAL;
pNodeConfig.TriggerConfig.TriggerMode = DMA_TRIGM_SINGLE_BURST_TRANSFER ;
pNodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_RISING;
pNodeConfig.TriggerConfig.TriggerSelection = GPDMA1_TRIGGER_TIM2_TRGO;
pNodeConfig.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
pNodeConfig.DataHandlingConfig.DataAlignment = DMA_DATA_RIGHTALIGN_ZEROPADDED;
pNodeConfig.SrcAddress = (uint32_t)&ACtransferLUT[0];
pNodeConfig.DstAddress = ((uint32_t)&(GPIOB->BSRR));
pNodeConfig.DataSize = 64U;

/* Build ACTrans Node */
ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &ACTrans);

/* Insert ACTrans to Queue */
ret |= HAL_DMAEx_List_InsertNode_Tail(&ACtransQ, &ACTrans);

ret |= HAL_DMAEx_List_SetCircularMode(&ACtransQ);

return ret;
}

 

1 reply

ST Employee
June 17, 2026

Hello ​@pollkky ,

Your linked-list setup already alternates between BsrrBuf1 and BsrrBuf2. What is still needed is a completion callback to know which buffer DMA has just finished, so the CPU can refill the other one.

 

volatile uint8_t rgb_active_buf = 0;   // For example: 0 = BsrrBuf1, 1 = BsrrBuf2


void HAL_DMA_TransferCompleteCallback(DMA_HandleTypeDef *hdma)
{
    if (hdma == &hdma_gpdma1_channelX)   // replace with your handle
    {
        rgb_active_buf ^= 1U;            // switch to the other buffer
    }
}


// Then, whenever you prepare new LED data:


if (rgb_active_buf == 0)
{
    // DMA is using BsrrBuf1, so CPU may fill BsrrBuf2
}
else
{
    // DMA is using BsrrBuf2, so CPU may fill BsrrBuf1
}

kind regards, 

DHIF Khaled

"Please mark my answer as best by clicking on the “Accept as solution"" button if it fully answered your question. This will help other users find this solution faster.​"