2026-01-18 8:24 PM
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.
Solved! Go to Solution.
2026-01-19 12:34 AM
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.
2026-01-19 12:34 AM
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.
2026-01-19 1:48 AM
Thanks @Saket_Om , I will refer the H5 project and update with further queries if any.
2026-01-19 6:33 PM - edited 2026-01-30 8:09 PM
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
If using both ADC1 and ADC4, they would both need the same Clock Prescaler setting: Asynchronous clock divided by 1 (?)
No changes required in the GPIO Configuration done automatically by CubeMX.
DMA configuration is done at the GPDMA1 node in CubeMX.
ADC4 Configuration
If using both ADC1 and ADC4, they would both need the same Clock Prescaler setting: Asynchronous clock divided by 1 (?)
No changes required in the GPIO Configuration done automatically by CubeMX.
DMA configuration is done at the GPDMA1 node in CubeMX.
Assign DMA Channels for ADC1 and ADC4
Configure DMA CH1 for ADC1
Configure DMA CH0 for ADC4
In the NVIC Node, Enable DMA Channel 0 and Channel 1 Interrupts
No changes required in the NVIC Code Generation done automatically by CubeMX.
Since my process values are slow moving DC values, I am using slower 4MHz clock for ADC.
Use the CubeMX default.
To support printf for the Debugger.
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.
Readings are within +/-10mV of the signal source.
Attached: .ioc file and main.c file
References: