2025-09-21 9:11 PM
Hello,
I am trying to use the GPDMA linked list on an STM32U5 to toggle seven chip-select GPIOs before and after SPI transfers.
Setup:
MCU: STM32U575ZITX
I build a linked list of nodes:
Write CSx_Pin to GPIOx->BRR (drive CS low)
Dummy delay node (memory→memory)
Write CSx_Pin to GPIOx->BSRR (drive CS high)
word width, DataSize = 4, SrcInc = FIXED, DstInc = FIXED.
Destination addresses are the GPIO BSRR/BRR registers.
Problem:
Only 3 out of 7 chip-select pins actually toggle when the DMA queue runs. The working pins are:
PE3
PE1
PB6
The non-working ones are on GPIOG (PG9, PG11, PG13) and PB8.
If I write to these pins directly from the CPU with HAL_GPIO_WritePin, they work correctly. Only DMA writes fail.
Questions:
Are all GPIO ports on STM32U5 supposed to be reachable by the GPDMA?
Is there a known limitation where certain GPIO ports (e.g. GPIOG) cannot be written by DMA?
Any clarification on which GPIO ports are DMA-accessible would be appreciated.
Thank you.
Linked List Setup Code :
#define ADC_COUNT 7U
#define CS7_Pin GPIO_PIN_3
#define CS7_GPIO_Port GPIOE
#define CS1_Pin GPIO_PIN_9
#define CS1_GPIO_Port GPIOG
#define CS2_Pin GPIO_PIN_11
#define CS2_GPIO_Port GPIOG
#define CS3_Pin GPIO_PIN_13
#define CS3_GPIO_Port GPIOG
#define CS4_Pin GPIO_PIN_6
#define CS4_GPIO_Port GPIOB
#define CS5_Pin GPIO_PIN_8
#define CS5_GPIO_Port GPIOB
#define CS6_Pin GPIO_PIN_1
#define CS6_GPIO_Port GPIOE
DMA_NodeTypeDef cs_low_node[ADC_COUNT];
DMA_QListTypeDef adc_queue;
DMA_NodeTypeDef adc_delay_node[ADC_COUNT];
DMA_NodeTypeDef adc_get_spi_node[ADC_COUNT];
DMA_NodeTypeDef cs_high_node[ADC_COUNT];
static volatile uint32_t cs_low_dest[ADC_COUNT] = {(uint32_t)&(CS1_GPIO_Port->BRR),
(uint32_t)&(CS2_GPIO_Port->BRR),
(uint32_t)&(CS3_GPIO_Port->BRR),
(uint32_t)&(CS4_GPIO_Port->BRR),
(uint32_t)&(CS5_GPIO_Port->BRR),
(uint32_t)&(CS6_GPIO_Port->BRR),
(uint32_t)&(CS7_GPIO_Port->BRR)};
static volatile uint32_t cs_source[ADC_COUNT] = {
CS1_Pin,
CS2_Pin,
CS3_Pin,
CS4_Pin,
CS5_Pin,
CS6_Pin,
CS7_Pin
};
static volatile uint32_t cs_high_dest[ADC_COUNT] = {(uint32_t)&(CS1_GPIO_Port->BSRR),
(uint32_t)&(CS2_GPIO_Port->BSRR),
(uint32_t)&(CS3_GPIO_Port->BSRR),
(uint32_t)&(CS4_GPIO_Port->BSRR),
(uint32_t)&(CS5_GPIO_Port->BSRR),
(uint32_t)&(CS6_GPIO_Port->BSRR),
(uint32_t)&(CS7_GPIO_Port->BSRR)};
static volatile uint32_t delay_source;
static volatile uint32_t delay_dest;
uint8_t adc1_buffer[5];
uint8_t adc2_buffer[5];
uint8_t adc3_buffer[5];
uint8_t adc4_buffer[5];
uint8_t adc5_buffer[5];
uint8_t adc6_buffer[5];
uint8_t adc7_buffer[5];
static volatile uint32_t adc_data_dst[ADC_COUNT] = {
(uint32_t)&(adc1_buffer[0]),
(uint32_t)&(adc2_buffer[0]),
(uint32_t)&(adc3_buffer[0]),
(uint32_t)&(adc4_buffer[0]),
(uint32_t)&(adc5_buffer[0]),
(uint32_t)&(adc6_buffer[0]),
(uint32_t)&(adc7_buffer[0])
};
HAL_StatusTypeDef adc_data_stream_dma_config(void)
{
HAL_StatusTypeDef ret = HAL_OK;
/* DMA node configuration declaration */
DMA_NodeConfTypeDef pNodeConfig;
uint8_t i;
/* 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_SRC_DATAWIDTH_WORD;
pNodeConfig.Init.SrcBurstLength = 1U;
pNodeConfig.Init.DestBurstLength = 1U;
pNodeConfig.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
pNodeConfig.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
pNodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_MASKED;
pNodeConfig.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
pNodeConfig.DataHandlingConfig.DataAlignment = DMA_DATA_RIGHTALIGN_ZEROPADDED;
pNodeConfig.SrcAddress = 0;
pNodeConfig.DstAddress = 0;
pNodeConfig.DataSize = 0;
for(i = 0;i <ADC_COUNT;i++)
{
pNodeConfig.Init.Direction = DMA_MEMORY_TO_PERIPH;
pNodeConfig.SrcAddress = (uint32_t)&cs_source[i];
pNodeConfig.DstAddress = cs_low_dest[i];
pNodeConfig.DataSize = 4U;
ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &cs_low_node[i]);
ret |= HAL_DMAEx_List_InsertNode_Tail(&adc_queue, &cs_low_node[i]);
pNodeConfig.Init.Direction = DMA_MEMORY_TO_MEMORY;
pNodeConfig.SrcAddress = (uint32_t)&delay_source;
pNodeConfig.DstAddress = (uint32_t)&delay_dest;
pNodeConfig.DataSize = 4U;
ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &adc_delay_node[i]);
ret |= HAL_DMAEx_List_InsertNode_Tail(&adc_queue, &adc_delay_node[i]);
pNodeConfig.Init.Direction = DMA_MEMORY_TO_PERIPH;
pNodeConfig.Init.Request = DMA_REQUEST_SW;
pNodeConfig.SrcAddress = (uint32_t)&cs_source[i];
pNodeConfig.DstAddress = cs_high_dest[i];
pNodeConfig.DataSize = 4U;
ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &cs_high_node[i]);
ret |= HAL_DMAEx_List_InsertNode_Tail(&adc_queue, &cs_high_node[i]);
}
return ret;
}
Solved! Go to Solution.
2025-09-22 7:29 AM - last edited on 2025-09-22 7:39 AM by Andrew Neil
I solved the problem. The issue was caused by how GPDMA writes to the GPIO registers. When I first configured the DMA to write a **32-bit word** into `GPIOx->BSRR` or `GPIOx->BRR`, only some GPIO pins actually toggled (in my case, only PE3, PE1, PB6 worked).
The solution was to configure the DMA for **byte transfers**, and point each DMA node to the exact byte within the GPIO register that corresponds to the pin. For example:
#define GPIO_BSRR_OFFSET 0x18
#define GPIO_BRR_OFFSET 0x28
#define GPIO_BSRR_BYTE_ADDR(port_base, pin) ((uint32_t)( (port_base) + GPIO_BSRR_OFFSET + ((pin) >> 3) ))
#define GPIO_BRR_BYTE_ADDR(port_base, pin) ((uint32_t)( (port_base) + GPIO_BRR_OFFSET + ((pin) >> 3) ))
// Example: CS1 = PG9
uint32_t cs1_low = GPIO_BRR_BYTE_ADDR(GPIOG_BASE, 9);
uint32_t cs1_high = GPIO_BSRR_BYTE_ADDR(GPIOG_BASE, 9);
// Mask reduced to a single byte
uint8_t cs1_mask = (1U << (9 & 0x7));
With this approach:
* DMA transfer width = **BYTE**
* Source = one-byte mask (e.g. `0x02` for PG9)
* Destination = the correct `BSRR` or `BRR` byte address for the pin
Now all 7 GPIOs toggle correctly via the GPDMA linked list.
Edited to apply proper source code formatting - please see How to insert source code for future reference.
2025-09-22 5:40 AM
All GPIO ports are accessible by the GPDMA per the System Architecture figure in the reference manual. Recheck your assumptions and any security settings you have enabled.
2025-09-22 7:29 AM - last edited on 2025-09-22 7:39 AM by Andrew Neil
I solved the problem. The issue was caused by how GPDMA writes to the GPIO registers. When I first configured the DMA to write a **32-bit word** into `GPIOx->BSRR` or `GPIOx->BRR`, only some GPIO pins actually toggled (in my case, only PE3, PE1, PB6 worked).
The solution was to configure the DMA for **byte transfers**, and point each DMA node to the exact byte within the GPIO register that corresponds to the pin. For example:
#define GPIO_BSRR_OFFSET 0x18
#define GPIO_BRR_OFFSET 0x28
#define GPIO_BSRR_BYTE_ADDR(port_base, pin) ((uint32_t)( (port_base) + GPIO_BSRR_OFFSET + ((pin) >> 3) ))
#define GPIO_BRR_BYTE_ADDR(port_base, pin) ((uint32_t)( (port_base) + GPIO_BRR_OFFSET + ((pin) >> 3) ))
// Example: CS1 = PG9
uint32_t cs1_low = GPIO_BRR_BYTE_ADDR(GPIOG_BASE, 9);
uint32_t cs1_high = GPIO_BSRR_BYTE_ADDR(GPIOG_BASE, 9);
// Mask reduced to a single byte
uint8_t cs1_mask = (1U << (9 & 0x7));
With this approach:
* DMA transfer width = **BYTE**
* Source = one-byte mask (e.g. `0x02` for PG9)
* Destination = the correct `BSRR` or `BRR` byte address for the pin
Now all 7 GPIOs toggle correctly via the GPDMA linked list.
Edited to apply proper source code formatting - please see How to insert source code for future reference.