STM32H5 ADC DMA HAL_ADC_ConvCpltCallback Called Early
I used the STM32H533 Nucleo board example project ADC_MultiChannelSingleConversion as a reference for this code. The actual project is running on an STM32H523.
I am attempting to have a software-triggered array of ADC reads across two channels using the GPDMA. [Edit: for clarification, multiple conversions across a sequence of 2 channels, not just a single conversion]. The plan is to be alerted via interrupt callback that the sequence (triggered by HAL_ADC_Start_DMA) is complete. I do not want the ADC to continue to sample or overwrite the data in the buffer, so I am turning off the ADC in the callback.
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
HAL_ADC_Stop_DMA(hadc);
...
}The issue is that this callback is being triggered after every two-channel sequence is completed, instead of once after the DMA buffer is filled.
The system does not generate any DMA complete or half complete callbacks in this configuration.
Here's what appears to be working:
- ADC conversions are triggered correctly
- DMA is used to move data from ADC peripheral to memory correctly
- Continuous conversions work as intended, only requiring one software start
Here is the relevant code with most boilerplate CubeMX comments removed for brevity:
void MX_ADC1_Init(void) {
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.NbrOfConversion = 2;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;
hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc1.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc1) != HAL_OK) {
Error_Handler();
}
__HAL_ADC_ENABLE_IT(&hadc1, ADC_IT_EOC | ADC_IT_OVR);
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_92CYCLES_5; // TODO: make shorter
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
Error_Handler();
}
}void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
DMA_NodeConfTypeDef DMA_NodeConfig;
if (adcHandle->Instance == ADC1) {
LL_RCC_SetADCDACClockSource(LL_RCC_ADCDAC_CLKSOURCE_HCLK);
HAL_RCC_ADC_CLK_ENABLED++;
if (HAL_RCC_ADC_CLK_ENABLED == 1) {
__HAL_RCC_ADC_CLK_ENABLE();
}
__HAL_RCC_GPIOA_CLK_ENABLE();
/**ADC1 GPIO Configuration
PA0 ------> ADC1_INP0
PA1 ------> ADC1_INP1
*/
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
DMA_NodeConfig.NodeType = DMA_GPDMA_LINEAR_NODE;
DMA_NodeConfig.Init.Request = GPDMA2_REQUEST_ADC1;
DMA_NodeConfig.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
DMA_NodeConfig.Init.Direction = DMA_PERIPH_TO_MEMORY;
DMA_NodeConfig.Init.SrcInc = DMA_SINC_FIXED;
DMA_NodeConfig.Init.DestInc = DMA_DINC_INCREMENTED;
DMA_NodeConfig.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_HALFWORD;
DMA_NodeConfig.Init.DestDataWidth = DMA_DEST_DATAWIDTH_HALFWORD;
DMA_NodeConfig.Init.SrcBurstLength = 1;
DMA_NodeConfig.Init.DestBurstLength = 1;
DMA_NodeConfig.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0 | DMA_DEST_ALLOCATED_PORT0;
DMA_NodeConfig.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
DMA_NodeConfig.Init.Mode = DMA_NORMAL;
DMA_NodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_MASKED;
DMA_NodeConfig.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
DMA_NodeConfig.DataHandlingConfig.DataAlignment = DMA_DATA_RIGHTALIGN_ZEROPADDED;
if (HAL_DMAEx_List_BuildNode(&DMA_NodeConfig, &Node_GPDMA2_Channel0) != HAL_OK) {
Error_Handler();
}
if (HAL_DMAEx_List_InsertNode(&List_GPDMA2_Channel0, NULL, &Node_GPDMA2_Channel0) != HAL_OK) {
Error_Handler();
}
if (HAL_DMAEx_List_SetCircularMode(&List_GPDMA2_Channel0) != HAL_OK) {
Error_Handler();
}
handle_GPDMA2_Channel0.Instance = GPDMA2_Channel0;
handle_GPDMA2_Channel0.InitLinkedList.Priority = DMA_LOW_PRIORITY_HIGH_WEIGHT;
handle_GPDMA2_Channel0.InitLinkedList.LinkStepMode = DMA_LSM_FULL_EXECUTION;
handle_GPDMA2_Channel0.InitLinkedList.LinkAllocatedPort = DMA_LINK_ALLOCATED_PORT0;
handle_GPDMA2_Channel0.InitLinkedList.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
handle_GPDMA2_Channel0.InitLinkedList.LinkedListMode = DMA_LINKEDLIST_CIRCULAR;
if (HAL_DMAEx_List_Init(&handle_GPDMA2_Channel0) != HAL_OK) {
Error_Handler();
}
if (HAL_DMAEx_List_LinkQ(&handle_GPDMA2_Channel0, &List_GPDMA2_Channel0) != HAL_OK) {
Error_Handler();
}
__HAL_LINKDMA(adcHandle, DMA_Handle, handle_GPDMA2_Channel0);
if (HAL_DMA_ConfigChannelAttributes(&handle_GPDMA2_Channel0, DMA_CHANNEL_NPRIV) != HAL_OK) {
Error_Handler();
}
HAL_NVIC_SetPriority(ADC1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(ADC1_IRQn);
} // ... ADC 2 not used with DMA
}
int main(void) {
// ... Other init
MX_GPDMA2_Init();
MX_ADC1_Init();
MX_ADC2_Init();
// ... Other init
}Called to start with:
HAL_ADC_Start_DMA(&hadc1, dma_buffer, dma_buffer_size);
Any assistance or advice on this would be greatly appreciated! I will be as responsive as possible for any follow-up questions.

