Skip to main content
NFern.1
Associate III
January 19, 2026
Solved

STM32U545 : Configuring ADC1 with GPDMA using "Standard Request Mode".

  • January 19, 2026
  • 2 replies
  • 281 views

Hi,

Do we have sample code demonstrating continuous conversions for U545 ADC1 with GPDMA using "Standard Request Mode". The sample code should use two or more channels - say IN1, IN2. 

I am migrating a project from F105/F405 and am a little overwhelmed by the setting options to be configured with U545.

The ADC_DMA_Transfer example at STM32Cube\Repository\STM32Cube_FW_U5_V1.8.0\Projects\NUCLEO-U575ZI-Q\Examples\ADC\ADC_DMA_Transfer  uses Linked-List Mode and only demonstrates a single ADC channel. 

Thanks.

Best answer by Saket_Om

Hello @NFern.1 

Unfortunately, there is no example ADC with DMA standard mode in STM32CubeU5. However, you can refer to the example ADC_SingleConversion_TriggerSW_DMA in STM32CubeH5.

2 replies

Saket_OmBest answer
Technical Moderator
January 19, 2026

Hello @NFern.1 

Unfortunately, there is no example ADC with DMA standard mode in STM32CubeU5. However, you can refer to the example ADC_SingleConversion_TriggerSW_DMA in STM32CubeH5.

"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.Saket_Om"
NFern.1
NFern.1Author
Associate III
January 19, 2026

Thanks @Saket_Om , I will refer the H5 project and update with further queries if any.

NFern.1
NFern.1Author
Associate III
January 20, 2026

Thanks @Saket_Om , I could get it to work.

I am sharing my implementation for review and suggestions by the Community. The complete project can be found at https://github.com/NereusF1/ADC-DMA-Test_N-U545RE-Q

Tested using NUCLEO-U545RE-Q.

ADC1: VREFINT, IN1, IN2, Temperature Sensor
ADC2: IN3, IN4
ADC calibration done at start-up

CubeMX settings

ADC1 Configuration

ADC1_1.png

If using both ADC1 and ADC4, they would both need the same Clock Prescaler setting: Asynchronous clock divided by 1 (?)

ADC1_2.png

ADC1_3.png

ADC1_4.png

No changes required in the GPIO Configuration done automatically by CubeMX.

ADC1_6.png

DMA configuration is done at the GPDMA1 node in CubeMX.

ADC1_5.png

ADC4 Configuration

ADC4_1.png

If using both ADC1 and ADC4, they would both need the same Clock Prescaler setting: Asynchronous clock divided by 1 (?)

ADC4_2.png

ADC4_3.png

ADC4_4.png

No changes required in the GPIO Configuration done automatically by CubeMX.

ADC4_5.png

DMA configuration is done at the GPDMA1 node in CubeMX.

Assign DMA Channels for ADC1 and ADC4

GPDMA1_1.png

Configure DMA CH1 for ADC1

GPDMA1_3.png

Configure DMA CH0 for ADC4

GPDMA1_4.png

In the NVIC Node, Enable DMA Channel 0 and Channel 1 Interrupts

NVIC_1.png

No changes required in the NVIC Code Generation done automatically by CubeMX.

NVIC_2.png

Clk1.png

 

Clk2.png

Since my process values are slow moving DC values, I am using slower 4MHz clock for ADC.

ICACHE.png

Use the CubeMX default.

SYS.png

To support printf for the Debugger.

Debug.png

Code added to main.c

 
/* USER CODE BEGIN PD */
#define ADC1ChannelCount 4
#define ADC4ChannelCount 2
#define AccValBufLen 100
/* USER CODE END PD */
/* USER CODE BEGIN PV */

uint32_t ADC1_res_buffer[ADC1ChannelCount*2]; // create a buffer with length equal to the Number of Conversions x 2 ; Word size = uint32_t
uint16_t ADC1_res_bkp_buffer[ADC1ChannelCount];
uint32_t ADC1_acc_val_buffer[ADC1ChannelCount]; //accumulated value
uint16_t ADC1AccValBufCount; // accumulate 100 maybe 1000 samples
uint32_t ADC1_acc_val_batch_buffer[ADC1ChannelCount];
uint16_t ADC1AccValBufBatchCount;
float ADC1_avg_val_buffer[ADC1ChannelCount];
float ADC1_pin_Volts_buffer[ADC1ChannelCount];

uint32_t cal_vref_data;
uint32_t vref_voltage_mv;
uint32_t vrefint_data;

uint32_t ADC1_ch1_raw;
uint32_t ADC1_ch1_mV;

uint32_t ADC1_ch2_raw;
uint32_t ADC1_ch2_mV;

uint32_t temp_raw;
int32_t temp_celsius;

uint32_t ADC4_res_buffer[ADC4ChannelCount*2]; // create a buffer with length equal to the Number of Conversions x 2 ; Word size = uint32_t
uint16_t ADC4_res_bkp_buffer[ADC4ChannelCount];
uint32_t ADC4_acc_val_buffer[ADC4ChannelCount]; //accumulated value
uint16_t ADC4AccValBufCount; // accumulate 100 maybe 1000 samples
uint32_t ADC4_acc_val_batch_buffer[ADC4ChannelCount];
uint16_t ADC4AccValBufBatchCount;
float ADC4_avg_val_buffer[ADC4ChannelCount];
float ADC4_pin_Volts_buffer[ADC4ChannelCount];

uint32_t ADC4_ch1_raw;
uint32_t ADC4_ch1_mV;

uint32_t ADC4_ch2_raw;
uint32_t ADC4_ch2_mV;

/* USER CODE END PV */

ADC Calibration.

Linking our memory buffers to the DMA process and ADC start-up.

 /* USER CODE BEGIN 2 */
	HAL_Delay(2000); // hopefully 2s is a long time in the MCU world for the ADC peripheral to power up and get ready for calibration
	HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED); // this is optional and uses internal calibration values programmed by the factory
	HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET_LINEARITY, ADC_SINGLE_ENDED);
	HAL_ADCEx_Calibration_Start(&hadc4, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED);
	HAL_ADCEx_Calibration_Start(&hadc4, ADC_CALIB_OFFSET_LINEARITY, ADC_SINGLE_ENDED);
	HAL_Delay(1000); // wait for things to settle down before starting the ADC process

	HAL_ADC_Start_DMA(&hadc1, ADC1_res_buffer, ADC1ChannelCount*2); // this actually starts the process: DMA-transfer the data stream from &hadc1 into ADC1_res_buffer, which holds 2 conversions
	HAL_ADC_Start_DMA(&hadc4, ADC4_res_buffer, ADC4ChannelCount*2);
 /* USER CODE END 2 */

 

 /* USER CODE BEGIN WHILE */
 while (1)
 {
		if(ADC1AccValBufBatchCount>0)
		{
			for (int i=0; i<ADC1ChannelCount; i++)
			{
				ADC1_avg_val_buffer[i]=(ADC1_avg_val_buffer[i] + (((float)ADC1_acc_val_batch_buffer[i]/(float)ADC1AccValBufBatchCount)))/2.0;
				ADC1_acc_val_batch_buffer[i]=0; // clear the accumulated value
			}
			ADC1AccValBufBatchCount=0; // clear the accumulated count

	 // Calculate the VREF using VREFINT
			if(ADC1_avg_val_buffer[0]<1) // protect from divide by zero
			{
				vref_voltage_mv=0; //adcbufAvgValue[0] will ramp-up and VREF calc will ramp-down slowly due to ADC averaging delays
			}
			else
			{
				// Read calibration data into RAM first if ICache is on
				cal_vref_data = *(__IO uint32_t *)VREFINT_CAL_ADDR; // Example address

				vrefint_data = ADC1_avg_val_buffer[0];

				// Calculate voltage in mV
				vref_voltage_mv = __HAL_ADC_CALC_VREFANALOG_VOLTAGE(cal_vref_data, vrefint_data, ADC_RESOLUTION_14B);

				ADC1_ch1_raw = ADC1_avg_val_buffer[1]; // Get raw ch1 reading
				ADC1_ch1_mV = __HAL_ADC_CALC_DATA_TO_VOLTAGE(&hadc1, vref_voltage_mv, ADC1_ch1_raw, ADC_RESOLUTION_14B); // Example ch1 calculation

				ADC1_ch2_raw = ADC1_avg_val_buffer[2]; // Get raw ch1 reading
				ADC1_ch2_mV = __HAL_ADC_CALC_DATA_TO_VOLTAGE(&hadc1, vref_voltage_mv, ADC1_ch2_raw, ADC_RESOLUTION_14B); // Example ch1 calculation

				// Or for temperature:
				temp_raw = ADC1_avg_val_buffer[3]; // Get raw temp sensor reading
				temp_celsius = __HAL_ADC_CALC_TEMPERATURE(&hadc1, vref_voltage_mv, temp_raw, ADC_RESOLUTION_14B); // Example Temp calculation
			}

		}

		if(ADC4AccValBufBatchCount>0)
		{
			for (int i=0; i<ADC4ChannelCount; i++)
			{
				ADC4_avg_val_buffer[i]=(ADC4_avg_val_buffer[i] + (((float)ADC4_acc_val_batch_buffer[i]/(float)ADC4AccValBufBatchCount)))/2.0;
				ADC4_acc_val_batch_buffer[i]=0; // clear the accumulated value
			}
			ADC4AccValBufBatchCount=0; // clear the accumulated count

	 // Calculate the VREF using VREFINT
			if(ADC4_avg_val_buffer[0]<1) // protect from divide by zero
			{
				vref_voltage_mv=0; //adcbufAvgValue[0] will ramp-up and VREF calc will ramp-down slowly due to ADC averaging delays
			}
			else
			{
				ADC4_ch1_raw = ADC4_avg_val_buffer[0]; // Get raw ch1 reading
				ADC4_ch1_mV = __HAL_ADC_CALC_DATA_TO_VOLTAGE(&hADC4, vref_voltage_mv, ADC4_ch1_raw, ADC_RESOLUTION_12B); // Example ch1 calculation

				ADC4_ch2_raw = ADC4_avg_val_buffer[1]; // Get raw ch1 reading
				ADC4_ch2_mV = __HAL_ADC_CALC_DATA_TO_VOLTAGE(&hADC4, vref_voltage_mv, ADC4_ch2_raw, ADC_RESOLUTION_12B); // Example ch1 calculation
			}

		}

 /* USER CODE END WHILE */

 

/* USER CODE BEGIN 4 */

// Called when first half of adc buffer is filled
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{

	if(hadc->Instance == ADC1)
	{
			for (int i=0; i<ADC1ChannelCount ; i++)
			{
			 ADC1_acc_val_buffer[i]=ADC1_acc_val_buffer[i]+ADC1_res_buffer[i]; // moved all heavy lifting to 100ms timer loop
			 ADC1_res_bkp_buffer[i]=(uint16_t)ADC1_res_buffer[i]; // for viewing the instantaneous value only
			}

			ADC1AccValBufCount++;
			if(ADC1AccValBufCount>=AccValBufLen)
			{
				for (int i=0; i<ADC1ChannelCount; i++)
				{
					ADC1_acc_val_batch_buffer[i]=ADC1_acc_val_buffer[i]; // hand-over for processing in less critical threads
					ADC1_acc_val_buffer[i]=0; // clear the accumulated value
				}
				ADC1AccValBufBatchCount=ADC1AccValBufCount; // hand-over for processing in less critical threads
				ADC1AccValBufCount=0; // clear the accumulated count
			}
	}

	if(hadc->Instance == ADC4)
	{
			for (int i=0; i<ADC4ChannelCount ; i++)
			{
			 ADC4_acc_val_buffer[i]=ADC4_acc_val_buffer[i]+ADC4_res_buffer[i]; // moved all heavy lifting to 100ms timer loop
			 ADC4_res_bkp_buffer[i]=(uint16_t)ADC4_res_buffer[i]; // for viewing the instantaneous value only
			}

			ADC4AccValBufCount++;
			if(ADC4AccValBufCount>=AccValBufLen)
			{
				for (int i=0; i<ADC4ChannelCount; i++)
				{
					ADC4_acc_val_batch_buffer[i]=ADC4_acc_val_buffer[i]; // hand-over for processing in less critical threads
					ADC4_acc_val_buffer[i]=0; // clear the accumulated value
				}
				ADC4AccValBufBatchCount=ADC4AccValBufCount; // hand-over for processing in less critical threads
				ADC4AccValBufCount=0; // clear the accumulated count
			}
	}

}

// Called when adc buffer is completely filled
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{

	if(hadc->Instance == ADC1)
	{
		for (int i=0; i<ADC1ChannelCount; i++)
		{
		 ADC1_acc_val_buffer[i]=ADC1_acc_val_buffer[i]+ADC1_res_buffer[ADC1ChannelCount+i]; // moved all heavy lifting to 100ms timer loop
		 ADC1_res_bkp_buffer[i]=(uint16_t)ADC1_res_buffer[ADC1ChannelCount+i]; // for viewing the instantaneous value only
		}

		ADC1AccValBufCount++;
		if(ADC1AccValBufCount>=AccValBufLen)
		{
			for (int i=0; i<ADC1ChannelCount; i++)
			{
				ADC1_acc_val_batch_buffer[i]=ADC1_acc_val_buffer[i]; // hand-over for processing in less critical threads
				ADC1_acc_val_buffer[i]=0; // clear the accumulated value
			}
			ADC1AccValBufBatchCount=ADC1AccValBufCount; // hand-over for processing in less critical threads
			ADC1AccValBufCount=0; // clear the accumulated count
		}
	}

	if(hadc->Instance == ADC4)
	{
		for (int i=0; i<ADC4ChannelCount; i++)
		{
		 ADC4_acc_val_buffer[i]=ADC4_acc_val_buffer[i]+ADC4_res_buffer[ADC4ChannelCount+i]; // moved all heavy lifting to 100ms timer loop
		 ADC4_res_bkp_buffer[i]=(uint16_t)ADC4_res_buffer[ADC4ChannelCount+i]; // for viewing the instantaneous value only
		}

		ADC4AccValBufCount++;
		if(ADC4AccValBufCount>=AccValBufLen)
		{
			for (int i=0; i<ADC4ChannelCount; i++)
			{
				ADC4_acc_val_batch_buffer[i]=ADC4_acc_val_buffer[i]; // hand-over for processing in less critical threads
				ADC4_acc_val_buffer[i]=0; // clear the accumulated value
			}
			ADC4AccValBufBatchCount=ADC4AccValBufCount; // hand-over for processing in less critical threads
			ADC4AccValBufCount=0; // clear the accumulated count
		}
	}
}

/* USER CODE END 4 */

 

TCONV per channel = TSMPL + TSAR = [814_min + 17_for_14bits] × Tadc
TCONV per channel = 831 × (ADC_Clk_PreScaler/Fadc) = 831*(1/4Mhz) = 207.75us
For ADC1 with 4 channels = 207.75us x 4 = 831us ~ 1ms assuming delays for the DMA buffer complete call-backs.
Now accumulating 100 samples = 1ms x 100 = 100ms.

So every 100ms we hand-over a new accumulated count from the Conversion Complete Call-back functions.
Hence the earlier accumulated count should be processed before this new accumulated count arrives to ensure
that we do not miss some readings.

I plan to move this checking / processing to a 10ms timer loop in production code.

 

Watch1.png

Readings are within +/-10mV of the signal source.

 

Attached:   .ioc file and main.c file

 

References:

ADC_SingleConversion_TriggerSW_DMA 

https://community.st.com/t5/stm32-mcus-products/stm32u545re-q-not-getting-adc1-conversion-data-via-gpdma/td-p/776887