2025-09-29 7:05 AM - edited 2025-09-29 7:10 AM
I need to do long (100k+ samples) PSSI reception with GPDMA using linked list on STM32H7R3L8H6H. CubeMX version is v6.15.0 and CubeIDE is v1.19.0. Since single DMA request can't be that long, I am thinking of doing it with two circular linked-list nodes. There are no good examples (at least I didn't find) so I am trying to do with my own logic.
The idea is to transfer data from PSSI peripheral to XSPI1 PSRAM with 8192 byte blocks. When one block finishes and second one starts, I increase the finished block destination address by 8192. Requests execute in circle.
I setup GPDMA channel 12 for the linked list:
And here is the linked list with two nodes N1 and N2 configured exactly the same (except name):
Generated setup function in linked_list.c
HAL_StatusTypeDef MX_PSSI_DMA_LL_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 = GPDMA1_REQUEST_PSSI;
pNodeConfig.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
pNodeConfig.Init.Direction = DMA_PERIPH_TO_MEMORY;
pNodeConfig.Init.SrcInc = DMA_SINC_FIXED;
pNodeConfig.Init.DestInc = DMA_DINC_INCREMENTED;
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_PORT1;
pNodeConfig.Init.TransferEventMode = DMA_TCEM_EACH_LL_ITEM_TRANSFER;
pNodeConfig.Init.Mode = DMA_NORMAL;
pNodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_MASKED;
pNodeConfig.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
pNodeConfig.DataHandlingConfig.DataAlignment = DMA_DATA_RIGHTALIGN_ZEROPADDED;
pNodeConfig.SrcAddress = (uint32_t)(&hpssi.Instance->DR);
pNodeConfig.DstAddress = 0;
pNodeConfig.DataSize = 8192;
/* Build PSSI_DMA_LL_N1 Node */
ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &PSSI_DMA_LL_N1);
/* Insert PSSI_DMA_LL_N1 to Queue */
ret |= HAL_DMAEx_List_InsertNode_Tail(&PSSI_DMA_LL, &PSSI_DMA_LL_N1);
/* Set node configuration ################################################*/
/* Build PSSI_DMA_LL_N2 Node */
ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &PSSI_DMA_LL_N2);
/* Insert PSSI_DMA_LL_N2 to Queue */
ret |= HAL_DMAEx_List_InsertNode_Tail(&PSSI_DMA_LL, &PSSI_DMA_LL_N2);
ret |= HAL_DMAEx_List_SetCircularModeConfig(&PSSI_DMA_LL, &PSSI_DMA_LL_N1);
return ret;
}
I call this function manually and I also link GPDMA channel with list:
MX_PSSI_DMA_LL_Config();
HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel12, &PSSI_DMA_LL);
Callback registrations:
HAL_DMA_RegisterCallback(&handle_GPDMA1_Channel12, HAL_DMA_XFER_CPLT_CB_ID, DMATransferComplete);
HAL_DMA_RegisterCallback(&handle_GPDMA1_Channel12, HAL_DMA_XFER_ERROR_CB_ID, DMATransferError);
HAL_DMA_RegisterCallback(&handle_GPDMA1_Channel12, HAL_DMA_XFER_ABORT_CB_ID, DMATransferAbort);
In starting code I set destination address of N1 and N2 node link registers. Link registers are placed into AHB SRAM1 which is configured as non-cacheable with MPU.
Here's the start of DMA:
PSSI_DMA_LL_N1.LinkRegisters[NODE_CDAR_DEFAULT_OFFSET] = (uint32_t)buffer;
PSSI_DMA_LL_N2.LinkRegisters[NODE_CDAR_DEFAULT_OFFSET] = (uint32_t)buffer + 8192;
HAL_DMAEx_List_Start_IT(&handle_GPDMA1_Channel12);
If you wonder why access directly - that's what I plan to do in callback and I need to switch buffers a lot of times anyway. Re-building linked list seems a bit too much. For simplicity reasons I skip the HAL return functions check code from forum.
PSSI is enabled by this time by CubeMX generated code. I see correct values in PSSI DR register with debugger.
In DMATransferComplete I have a plan to increase the finished node destination address with a code like this:
if (node == 0)
{
PSSI_DMA_LL_N1.LinkRegisters[NODE_CDAR_DEFAULT_OFFSET] += 8192;
}
else
{
PSSI_DMA_LL_N2.LinkRegisters[NODE_CDAR_DEFAULT_OFFSET] += 8192;
}
But I run into question - how do know which node just finished? Is there some status register of GPDMA? Or I just blindly toggle the active node counter with node=1-node ? That doesn't feel robust.
Another, and currently even bigger problem is that the linked list does not circulate. By adding a simple incrementing integer into transfer completion callback and printing it out in the main loop I see only 1 and 2. Using debug breakpoints seems to break transfers, that's why I used non-invasive method. I do not get DMA error callbacks. I have seen data reach RAM, but not always.
Reference manual RM04777 chapter 12.4.3 "GPDMA circular buffering with linked-list programming" is creating more questions. It seems to suggest that only the second node should loop and half-transfer completion interrupts should be enabled. I don't grasp that idea...
Need some advice how to get it working.
The whole thing works with normal single 10k PSSI DMA request, so I know the electronics is okay.
One more question: does linked list guarantee no data loss between item switching?