Skip to main content
Associate II
Nov 5, 2026 1:30 AM UTC
Question

STM32CubeMX 6.18.1, STM32H563, ADC + DMA + multiple channels in LL mode, MX generates wrong code! (here is the fix that works)

Hello,

My environment:
STM32CubeMX 6.18.1,
STM32H563 on custom PCB,
ADC1, GPDMA1, multiple channels in sequencer, DMA/ADC used in LL mode

Initially, I used normal interrupt mode with interrupt triggered after each conversion (every single channel/rank). This worked well, but it is not optimal. I wanted to have a single sequence for all ADC channels (I have 7 ranks configured) with a single interrupt. This DMA approach worked well in F1, F4 and L4 series for me.

So I reconfigured this H563 chip ADC to DMA mode, using GPDMA1, Channel 0. Cube MX generates the initialization code in MX_ADC1_Init() (too long to post it whole). This code did not work no matter what I tried. After some long hours wasted, I think I know why:

Generated code uses linked list configuration u​​nder the hood and it has this declaration inside MX_ADC1_Init():
 

LL_DMA_LinkNodeTypeDef Node_GPDMA1_Channel0 = {0};

 

This local variable then used inside the same function:

// field assignments for NodeConfig go here
LL_DMA_CreateLinkNode(&NodeConfig, &Node_GPDMA1_Channel0);
LL_DMA_ConnectLinkNode(&Node_GPDMA1_Channel0, LL_DMA_CLLR_OFFSET5, &Node_GPDMA1_Channel0, LL_DMA_CLLR_OFFSET5);
LL_DMA_SetLinkedListBaseAddr(GPDMA1, LL_DMA_CHANNEL_0, (uint32_t)&Node_GPDMA1_Channel0);

From what I understand, setting the base address for the newly created linked list here cannot be local variable inside the stack. GPDMA keeps using it from its interrupt later, so it has to be global variable in heap.

So I made three changes:
1: added this global variable, visible everywhere:

LL_DMA_LinkNodeTypeDef GlobalNode_GPDMA1_Channel0 = {0};

2: Added this code to MX_ADC1_Init() in adc.c (inside existing USER CODE BEGIN/END ADC1_Init 1 block):
DMA_BufferADC is also my global variable to store DMA results, declared as uint16_t DMA_BufferADC[ ADC_RANK_COUNT ];

/* USER CODE BEGIN ADC1_Init 1 */

//------------------------------------------------------------------------------------
/* Override GPDMA1 Channel 0 with our global persistent node */
NodeConfig.SrcAddress = LL_ADC_DMA_GetRegAddr(ADC1, LL_ADC_DMA_REG_REGULAR_DATA);
NodeConfig.DestAddress = (uint32_t)&DMA_BufferADC;
NodeConfig.BlkDataLength = ADC_RANK_COUNT * 2; // size in bytes
LL_DMA_CreateLinkNode(&NodeConfig, &GlobalNode_GPDMA1_Channel0);

LL_DMA_ConnectLinkNode(&GlobalNode_GPDMA1_Channel0, LL_DMA_CLLR_OFFSET5, &GlobalNode_GPDMA1_Channel0, LL_DMA_CLLR_OFFSET5);

LL_DMA_SetLinkedListBaseAddr(GPDMA1, LL_DMA_CHANNEL_0, (uint32_t)&GlobalNode_GPDMA1_Channel0);

LL_DMA_ConfigLinkUpdate(GPDMA1, LL_DMA_CHANNEL_0,
(LL_DMA_UPDATE_CTR1 | LL_DMA_UPDATE_CTR2 | LL_DMA_UPDATE_CBR1 |
LL_DMA_UPDATE_CSAR | LL_DMA_UPDATE_CDAR | LL_DMA_UPDATE_CTR3 |
LL_DMA_UPDATE_CBR2 | LL_DMA_UPDATE_CLLR),
(uint32_t)&GlobalNode_GPDMA1_Channel0);

//------------------------------------------------------------------------------------

/* USER CODE END ADC1_Init 1 */

The code above replaces existing settings that were automatically created by Cube MX a few lines earlier. This way any re-generated MX code will be always replaced by my code.

3: Since I am using LL for both DMA and ADC, this requires additional initialization code. This is inserted into existing USER CODE BEGIN/END ADC1_Init 2 block inside MX_ADC1_Init() in adc.c:

  // Run ADC Calibration
LL_ADC_StartCalibration(ADC1, LL_ADC_SINGLE_ENDED);
while (LL_ADC_IsCalibrationOnGoing(ADC1));
__asm__ volatile("nop"); // Wait 4 ADC clock cycles

// enable DMA
LL_DMA_EnableChannel(GPDMA1, LL_DMA_CHANNEL_0);

// enable "DMA transfer complete" interrupt
LL_DMA_EnableIT_TC(GPDMA1, LL_DMA_CHANNEL_0);

// Enable ADC
LL_ADC_Enable(ADC1);
while (!LL_ADC_IsActiveFlag_ADRDY(ADC1));

// Trigger software conversion start once (endless loop of conversions)
LL_ADC_REG_StartConversion(ADC1);

With these changes, everything works as expected now.

 


Interestingly, when I configure another DMA channel for SPI, Cube MX declares this variable as global, outside of MX_SPI1_Init():

DMA_HandleTypeDef handle_GPDMA1_Channel1;

And my SPI1 is set up for HAL, not for LL as for ADC1.

My guess is that the problem with local variable declaration happens only with LL option.

I hope this helps.

Cheers!
 

2 replies

Ghofrane GSOURI
ST Technical Moderator
Nov 5, 2026 9:10 AM UTC

Hello ​@Zman552625 

I believe this is the same exact issue that has been reported here STM32CubeMX 6.15: Incorrect LL code generation for Circular DMA on STM32H523 | Community

The issue is tracked by an internal ticket and it will be fixed as soon as possible. 

 

THX

Ghofrane

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
rich.g.williams
Senior
Nov 5, 2026 9:38 AM UTC

​@Zman552625 

I think you have fixed it.

The Linked List can reside in Flash or SRAM. Values in the Linked List do not change (unless you deliberately change them for some reason).  They can be thought of as a series of “micro” instructions that the DMA process reads and acts upon according to flags in the GPDMA_CxLLR register.  

The lower half of GPDMA_CxLLR contains the lower half of the pointer to the base of the Linked List.

 

Bill's Notes