STM32U5 ADC + TIM TRGO + DMA: repeated samples when reading ADC at 100us interval
I am using STM32U575 (MCLK = 160 MHz) with ADC1 in scan mode (4 channels).
ADC conversions are triggered by TIM3 TRGO and results are transferred by DMA in circular mode.
Continuous conversion is disabled.
TIM3 is configured to trigger ADC every 100 us (10 samples per ms).
ADC results are copied to a snapshot buffer in HAL_ADC_ConvCpltCallback().
In the application task, ADC values are read from this snapshot buffer every 100 us.
However, I observe repeated ADC values (e.g. A, A, B, B…) instead of updated data at each 100 us read.
Reducing the application read rate decreases the number of repeated samples.

Is this behavior expected when using ADC scan mode + DMA circular?
What is the recommended way to ensure ADC data is updated strictly at 100 us intervals
(1 ms / 10 samples) without repeated values?
Should ADC data be processed only in the DMA transfer complete callback,
or is there a better synchronization method?
Related code:
//task
for (uint8_t i = 0; i < POINTS_PER_MS ; i++)
{
adc = ADC_GetValue(ch1);
if (temp_adc > adc)
{
temp_adc = adc;
OPT_ADC = adc;
}
DWT_Delay_us(POINTS_DELAY_US);
}
uint16_t ADC_GetValue(uint8_t channel)
{
if (channel < ADC_BUFFRER_SIZE)
return ADCValueSnapshot[channel];
else
return 0;
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
if (hadc->Instance == ADC1)
{
#if 1
for (uint8_t i = 0; i < ADC_BUFFRER_SIZE; i++)
{
ADCValueSnapshot[i] = (uint16_t)ADCValue[i];
}
// ADC_ConversionComplete = 1;
#endif
}
}
//ADC setting
static void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Common config
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;
hadc1.Init.Resolution = ADC_RESOLUTION_14B;
hadc1.Init.GainCompensation = 0;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 4;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T3_TRGO;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.TriggerFrequencyMode = ADC_TRIGGER_FREQ_LOW;
hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
hadc1.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
hadc1.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR;
hadc1.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_36CYCLES;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = ADC_REGULAR_RANK_2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_3;
sConfig.Rank = ADC_REGULAR_RANK_3;
sConfig.SamplingTime = ADC_SAMPLETIME_68CYCLES;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_4;
sConfig.Rank = ADC_REGULAR_RANK_4;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
// Note: 68 cycle->3.4us, 814 cycles->40.7us
/* USER CODE END ADC1_Init 2 */
}
//Tim3 setting
static void MX_TIM3_Init(void)
{
/* USER CODE BEGIN TIM3_Init 0 */
/* USER CODE END TIM3_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM3_Init 1 */
/* USER CODE END TIM3_Init 1 */
htim3.Instance = TIM3;
htim3.Init.Prescaler = 160 - 1;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 100 - 1;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM3_Init 2 */
/* USER CODE END TIM3_Init 2 */
}
//DMA
void ADC_DMA_Init()
{
__HAL_LINKDMA(&hadc1, DMA_Handle, handle_GPDMA1_Channel1);
if (HAL_ADC_Start_DMA(&hadc1, (uint32_t *)ADCValue, (ADC_BUFFRER_SIZE)) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_Base_Start_IT(&htim3);
}
