2025-11-04 6:37 PM
Hello,
I am using the Nucleo U575ZI-Q board. I need to use GPDMA synchronized with a timer to set multiple GPIO pins synchronously, perform SPI transmission, and configure PWM periods.
I have found a working combination: software request + trigger TRGO. However, TRGO is only available for TIM15 and TIM2, which limits its versatility. Ideally, I would like to use hardware requests (such as REQUEST_TIM15_UP).
Set TIM15 to output TRGO. Configure GPDMA1_CHANNEL7 with LinkedList for DMA_REQUEST_SW and set the trigger to GPDMA1_TRIGGER_TIM15_TRGO. The direction should be set to Memory-to-Memory. Specify the source as memory and the destination as GPIOA->BSRR or SPI2->TXDR. This will allow you to generate the waveform shown in the attached oscilloscope.
uint32_t gpio_node_data[4] = {
0x00000100U, // PA8 = High -> bit8 set (1 << 8)
0x01000000U, // PA8 = Low -> bit24 set (1 << (8 + 16))
0xFFFFFFFF,
0xFFFFFFFF
};
uint32_t spi2_node_data[4] = {
0xAAAAAAAA,
0x55555555,
0x11111111,
0xFFFFFFFF
};
HAL_StatusTypeDef MX_TimingSPI_GPIO_O_I_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_FIXED;
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_EACH_LL_ITEM_TRANSFER;
pNodeConfig.TriggerConfig.TriggerMode = DMA_TRIGM_BLOCK_TRANSFER;
pNodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_RISING;
pNodeConfig.TriggerConfig.TriggerSelection = GPDMA1_TRIGGER_TIM15_TRGO;
pNodeConfig.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
pNodeConfig.DataHandlingConfig.DataAlignment = DMA_DATA_RIGHTALIGN_ZEROPADDED;
pNodeConfig.SrcAddress = (uint32_t)&gpio_node_data[0];
pNodeConfig.DstAddress = (uint32_t)&(GPIOA->BSRR);
pNodeConfig.DataSize = sizeof(uint32_t);
/* Build GPIO_High_Node Node */
ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &GPIO_High_Node);
/* Insert GPIO_High_Node to Queue */
ret |= HAL_DMAEx_List_InsertNode_Tail(&TimingSPI_GPIO_O_I, &GPIO_High_Node);
/* Set node configuration ################################################*/
pNodeConfig.Init.TransferEventMode = DMA_TCEM_LAST_LL_ITEM_TRANSFER;
pNodeConfig.SrcAddress = (uint32_t)&gpio_node_data[1];
/* Build GPIO_Low_Node Node */
ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &GPIO_Low_Node);
/* Insert GPIO_Low_Node to Queue */
ret |= HAL_DMAEx_List_InsertNode_Tail(&TimingSPI_GPIO_O_I, &GPIO_Low_Node);
/* Set node configuration ################################################*/
pNodeConfig.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
pNodeConfig.SrcAddress = (uint32_t)&spi2_node_data[0];
pNodeConfig.DstAddress = (uint32_t)&(SPI2->TXDR);
/* Build SPI2_TX_n1 Node */
ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &SPI2_TX_n1);
/* Insert SPI2_TX_n1 to Queue */
ret |= HAL_DMAEx_List_InsertNode_Tail(&TimingSPI_GPIO_O_I, &SPI2_TX_n1);
/* Set node configuration ################################################*/
pNodeConfig.SrcAddress = (uint32_t)&spi2_node_data[1];
/* Build SPI2_TX_n2 Node */
ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &SPI2_TX_n2);
/* Insert SPI2_TX_n2 to Queue */
ret |= HAL_DMAEx_List_InsertNode_Tail(&TimingSPI_GPIO_O_I, &SPI2_TX_n2);
/* Set node configuration ################################################*/
pNodeConfig.SrcAddress = (uint32_t)&spi2_node_data[2];
/* Build SPI2_TX_n3 Node */
ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &SPI2_TX_n3);
/* Insert SPI2_TX_n3 to Queue */
ret |= HAL_DMAEx_List_InsertNode_Tail(&TimingSPI_GPIO_O_I, &SPI2_TX_n3);
ret |= HAL_DMAEx_List_SetCircularModeConfig(&TimingSPI_GPIO_O_I, &GPIO_High_Node);
return ret;
}
HAL_TIM_PWM_Start(&htim15, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim15, TIM_CHANNEL_2);
SPI2->CR1 |= SPI_CR1_SPE;
SPI2->CR1 |= SPI_CR1_CSTART;
MX_TimingSPI_GPIO_O_I_Config();
HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel7, &TimingSPI_GPIO_O_I);
HAL_DMAEx_List_Start(&handle_GPDMA1_Channel7);
---
ChatGPT advised to do the following, but it does not work (it seems that GPDMA1 is not activated).
// gpio_node_data should be non-const
static uint32_t gpio_node_data[2] = { 0x00000100U, 0x01000000U, 0, 0 };
// Node configuration (only changes are shown)
pNodeConfig.Init.Request = GPDMA1_REQUEST_TIM15_UP; // HW request
pNodeConfig.Init.Direction = DMA_MEMORY_TO_PERIPH; // Write to PERIPH
pNodeConfig.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0 | DMA_DEST_ALLOCATED_PORT0;
pNodeConfig.DataSize = sizeof(uint32_t); // 4
pNodeConfig.TriggerConfig.TriggerMode = DMA_TRIGM_BLOCK_TRANSFER;
pNodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_RISING;
// TriggerSelection is unnecessary when using DMAMUX, or align it with DMAMUX settings
// pNodeConfig.TriggerConfig.TriggerSelection = GPDMA1_TRIGGER_TIM15_TRGO; // Usually left to DMAMUX
pNodeConfig.SrcAddress = (uint32_t)&gpio_node_data[0];
pNodeConfig.DstAddress = (uint32_t)&(GPIOA->BSRR);
Disable the trigger: pNodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_MASKED;
Hard request and: pNodeConfig.Init.Request = GPDMA1_REQUEST_TIM15_UP;
Direction: Memory to Memory pNodeConfig.Init.Direction = DMA_MEMORY_TO_MEMORY;
Set the actual destination to memory:
pNodeConfig.SrcAddress = (uint32_t)&gpio_node_data[0];
pNodeConfig.DstAddress = (uint32_t)&gpio_node_data[3];
→ GPDMA operates, and transfer occurs between memory.
Disable the trigger: pNodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_MASKED;
Hard request and: pNodeConfig.Init.Request = GPDMA1_REQUEST_TIM15_UP;
Direction: Memory to Memory pNodeConfig.Init.Direction = DMA_MEMORY_TO_MEMORY;
Set the actual destination to peripheral:
pNodeConfig.SrcAddress = (uint32_t)&gpio_node_data[0];
pNodeConfig.DstAddress = (uint32_t)&(GPIOA->BSRR);
→ Writing to the peripheral does not occur (GPIO does not change).
Disable the trigger: pNodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_MASKED;
Hard request and: pNodeConfig.Init.Request = GPDMA1_REQUEST_TIM15_UP;
Direction: Memory to Peripheral pNodeConfig.Init.Direction = DMA_MEMORY_TO_PERIPH;
Set the actual destination to peripheral:
pNodeConfig.SrcAddress = (uint32_t)&gpio_node_data[0];
pNodeConfig.DstAddress = (uint32_t)&(GPIOA->BSRR);
→ Writing to the peripheral does not occur (GPIO does not change).