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 under 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!
