Skip to main content
Tim Goll
Associate II
February 13, 2026
Solved

STM32 "dual regular simultaneous mode only" - with DMA

  • February 13, 2026
  • 3 replies
  • 342 views

Searching through the forum it seems like there are tens of posts regarding this topic. I read many of them, but I struggle to find a solution that applies to me.

First of, I use an STM32H743VIT.

My goal: Use ADC1 and ADC2, one channel each. The two ADCs should be tied together. The whole conversion should be triggered by TIM4 update event and the data should be moved via DMA.

What works: If I just use TIM4 and two DMA streams that move the data for each ADC individually, it works. As soon as I try to use the dual mode, it does nothing.

When I'm in dual mode I have confirmed that the timer is still running. TIM4->CNT is changing. I've spent almost two hours with the debugger in the SFR and checked the registers. I'm not sure if I'm close to the issue, but there is something that seems suspicious based on the research I did: DMA1->S0NDTR->NDT does not change, indicating that the DMA stream does nothing. However ADC1->CR->ADSTART is at 0x1, indicating that it tries to do something but doesn't do it. There are more registers that I checked, but these seem like the most important ones.

I also spent a while discussing registers with Gemini. Not sure if anything meaningful was learned int his discussion, but:
- ADC1/2->ISR->ADRDY is 0x1, both are ready
- DMA1->LISR and DMA1->HISR are bot 0x0, so no errors

My code actually triggering the conversion is rather simple:

HAL_ADC_Start(params->hadc_2); // probably wrong, but I've seen this somewhere, it doesn't change anything though
HAL_TIM_Base_Start(params->htim);
HAL_ADCEx_MultiModeStart_DMA(dev->hadc_1, (uint32_t *) dev->buffer, dev->size);

// HAL_ADC_ConvCpltCallback is never triggered

 

ADC 1 config:

TimGoll_0-1770943363914.png

TimGoll_1-1770943389172.png

ADC2 config:

TimGoll_2-1770943419751.png

Here's the auto generated code:

static void MX_ADC1_Init(void)
{

 /* USER CODE BEGIN ADC1_Init 0 */

 /* USER CODE END ADC1_Init 0 */

 ADC_MultiModeTypeDef multimode = {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_DIV1;
 hadc1.Init.Resolution = ADC_RESOLUTION_16B;
 hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
 hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
 hadc1.Init.LowPowerAutoWait = DISABLE;
 hadc1.Init.ContinuousConvMode = DISABLE;
 hadc1.Init.NbrOfConversion = 1;
 hadc1.Init.DiscontinuousConvMode = DISABLE;
 hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T4_TRGO;
 hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
 hadc1.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR;
 hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
 hadc1.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
 hadc1.Init.OversamplingMode = ENABLE;
 hadc1.Init.Oversampling.Ratio = 2;
 hadc1.Init.Oversampling.RightBitShift = ADC_RIGHTBITSHIFT_1;
 hadc1.Init.Oversampling.TriggeredMode = ADC_TRIGGEREDMODE_MULTI_TRIGGER;
 hadc1.Init.Oversampling.OversamplingStopReset = ADC_REGOVERSAMPLING_RESUMED_MODE;
 if (HAL_ADC_Init(&hadc1) != HAL_OK)
 {
 Error_Handler();
 }

 /** Configure the ADC multi-mode
 */
 multimode.Mode = ADC_DUALMODE_REGSIMULT;
 multimode.DualModeData = ADC_DUALMODEDATAFORMAT_32_10_BITS;
 multimode.TwoSamplingDelay = ADC_TWOSAMPLINGDELAY_1CYCLE;
 if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
 {
 Error_Handler();
 }

 /** Configure Regular Channel
 */
 sConfig.Channel = ADC_CHANNEL_3;
 sConfig.Rank = ADC_REGULAR_RANK_1;
 sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
 sConfig.SingleDiff = ADC_SINGLE_ENDED;
 sConfig.OffsetNumber = ADC_OFFSET_NONE;
 sConfig.Offset = 0;
 sConfig.OffsetSignedSaturation = DISABLE;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 Error_Handler();
 }
 /* USER CODE BEGIN ADC1_Init 2 */

 /* USER CODE END ADC1_Init 2 */

}

static void MX_ADC2_Init(void)
{

 /* USER CODE BEGIN ADC2_Init 0 */

 /* USER CODE END ADC2_Init 0 */

 ADC_ChannelConfTypeDef sConfig = {0};

 /* USER CODE BEGIN ADC2_Init 1 */

 /* USER CODE END ADC2_Init 1 */

 /** Common config
 */
 hadc2.Instance = ADC2;
 hadc2.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
 hadc2.Init.Resolution = ADC_RESOLUTION_16B;
 hadc2.Init.ScanConvMode = ADC_SCAN_DISABLE;
 hadc2.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
 hadc2.Init.LowPowerAutoWait = DISABLE;
 hadc2.Init.ContinuousConvMode = DISABLE;
 hadc2.Init.NbrOfConversion = 1;
 hadc2.Init.DiscontinuousConvMode = DISABLE;
 hadc2.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
 hadc2.Init.Overrun = ADC_OVR_DATA_PRESERVED;
 hadc2.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
 hadc2.Init.OversamplingMode = ENABLE;
 hadc2.Init.Oversampling.Ratio = 2;
 hadc2.Init.Oversampling.RightBitShift = ADC_RIGHTBITSHIFT_1;
 hadc2.Init.Oversampling.TriggeredMode = ADC_TRIGGEREDMODE_MULTI_TRIGGER;
 hadc2.Init.Oversampling.OversamplingStopReset = ADC_REGOVERSAMPLING_RESUMED_MODE;
 if (HAL_ADC_Init(&hadc2) != HAL_OK)
 {
 Error_Handler();
 }

 /** Configure Regular Channel
 */
 sConfig.Channel = ADC_CHANNEL_4;
 sConfig.Rank = ADC_REGULAR_RANK_1;
 sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
 sConfig.SingleDiff = ADC_SINGLE_ENDED;
 sConfig.OffsetNumber = ADC_OFFSET_NONE;
 sConfig.Offset = 0;
 sConfig.OffsetSignedSaturation = DISABLE;
 if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
 {
 Error_Handler();
 }
 /* USER CODE BEGIN ADC2_Init 2 */

 /* USER CODE END ADC2_Init 2 */

}

static void MX_TIM4_Init(void)
{

 /* USER CODE BEGIN TIM4_Init 0 */

 /* USER CODE END TIM4_Init 0 */

 TIM_MasterConfigTypeDef sMasterConfig = {0};
 TIM_OC_InitTypeDef sConfigOC = {0};

 /* USER CODE BEGIN TIM4_Init 1 */

 /* USER CODE END TIM4_Init 1 */
 htim4.Instance = TIM4;
 htim4.Init.Prescaler = 0;
 htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
 htim4.Init.Period = 94-1;
 htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
 htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
 if (HAL_TIM_OC_Init(&htim4) != HAL_OK)
 {
 Error_Handler();
 }
 sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
 if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
 {
 Error_Handler();
 }
 sConfigOC.OCMode = TIM_OCMODE_TOGGLE;
 sConfigOC.Pulse = 0;
 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
 if (HAL_TIM_OC_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
 {
 Error_Handler();
 }
 /* USER CODE BEGIN TIM4_Init 2 */

 /* USER CODE END TIM4_Init 2 */
 HAL_TIM_MspPostInit(&htim4);

}


I'm thankful for any helpful pointer as I'm currently a bit lost on what else to try.

Best answer by mƎALLEm

Ok.

May be it would be helpful to refer to the example provided in the CubeHAL and inspire from it:

https://github.com/STMicroelectronics/STM32CubeH7/blob/master/Projects/STM32H743I-EVAL/Examples/ADC/ADC_DualModeInterleaved/

It is using the interleaved mode but you can do the necessary modifications to make it in simultaneous mode.

3 replies

mƎALLEm
Technical Moderator
February 13, 2026

Hello,

First thing to check is the buffer location of the DMA transfer. Please read this article: The most probable reason to have an issue with DMA or BDMA transfers on STM32H7

"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."
Tim Goll
Tim GollAuthor
Associate II
February 13, 2026

First of, thank you for your response! I checked again and can add the following: 

I'm using DMA1 and I have the D-cache feature of the STM disabled. Also memory protection is disabled. The address of my buffer is 0x240023c4, which seems to be in the correct range.

It works if I don't try to combine the two ADCs, pointing to an issue (probably with my configuration) regarding the dual mode. Since the registers indicate that the DMA is waiting on the ADC and the ADCs are ready, it seems to me like there is something preventing the ADCs from actually triggering.

mƎALLEm
mƎALLEmBest answer
Technical Moderator
February 13, 2026

Ok.

May be it would be helpful to refer to the example provided in the CubeHAL and inspire from it:

https://github.com/STMicroelectronics/STM32CubeH7/blob/master/Projects/STM32H743I-EVAL/Examples/ADC/ADC_DualModeInterleaved/

It is using the interleaved mode but you can do the necessary modifications to make it in simultaneous mode.

"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."
Tim Goll
Tim GollAuthor
Associate II
February 14, 2026

Got it working in a minimal example. So it must be something in my larger code base. I will try again tomorrow and let you know what's the issue if I get it working. Right now I have the feeling that I did the same both times.

Tim Goll
Tim GollAuthor
Associate II
February 15, 2026

Well, it is working now. And I'm not entirely sure what I changed. Probably something that I've overlooked.