Skip to main content
Associate II
December 16, 2025
Question

STM32H723: Remove 1.65 V bias after ADC conversion for DC voltage measurement

  • December 16, 2025
  • 2 replies
  • 476 views

MCU: STM32H723VET6
ADC: ADC1 / ADC2 / ADC3 (12-bit, single-ended)
VREF: 3.3 V
Application: Digital Multi meter (DC / AC voltage measurement)

 

Problem Description

In my DMM hardware design, the Analog frontend applies a mid-scale level shift (VREF/2 = 1.65 V) before the ADC input.

This is done intentionally to support bipolar AC measurements using a single-ended ADC:

 

 
Input signal → amplifier/divider → +1.65V offset → ADC (03.3V)

As expected:

  • 0 V input → ADC reads ~1.65 V

  • Positive/negative AC swings are in around mid-scale

However, for DC voltage measurement, I want the behaviour to be:

  • No input connected → display 0.000 V

  • Applied DC voltage → display the same voltage

  • Mid-scale offset should NOT appear in the final DC reading


Observed Issue

If the mid-scale offset is not removed after ADC conversion, the firmware reports:

  • ~3 V on 6 V range

  • ~30 V on 60 V range

  • ~300 V on 600 V range

even when no voltage is applied.

This is because the ADC correctly reads ~1.65 V, but that bias is still present during DC scaling.

 

Solution Implemented:

 
float Apply_Voltage_Calibration(float v, Voltage_Range_t range, uint8_t phase)
{
 Calibration_Point_t* cal = &user_calibration.voltage[range][phase-1];
 if (cal->gain == 0.0f)
 cal->gain = 1.0f;
 v -= cal->offset;
 v *= cal->gain;
 return v;
}


used in this below function

float Measure_Voltage_DC(uint8_t phase, Voltage_Range_t range)
{
 // Auto-range
 if(range == VOLT_RANGE_AUTO) {
 range = Voltage_Auto_Range(phase);
 }

 // Configure voltage gain, divider, enable frontend
 Configure_Voltage_Range(phase, range);

 // Select voltage MUX path
 MUX_Select_All_Voltage();

 // Enable correct voltage input channel (phase 1–3 -> channels 4–6)
 Channel_Enable(phase + 3, true);
 HAL_Delay(10); // settling

 // Choose the correct ADC instance & channel
 ADC_HandleTypeDef* adc = NULL;
 uint32_t adc_channel = 0;
 switch(phase)
 {
 case 1: adc = &hadc3; adc_channel = ADC_CHANNEL_10; break; // V1 -> ADC3 CH10 (PC0)
 case 2: adc = &hadc1; adc_channel = ADC_CHANNEL_16; break; // V2 -> ADC1 CH16 (PA0)
 case 3: adc = &hadc2; adc_channel = ADC_CHANNEL_3; break; // V3 -> ADC2 CH3 (PA6)
 default: return 0.0f;
 }

#define DC_SAMPLES 64
 uint16_t samples[DC_SAMPLES];

 // Acquire samples (same logic, but HAL)
 for(int i = 0; i < DC_SAMPLES; i++)
 {
 samples[i] = ADC_Read_Single_Channel_HAL(adc, adc_channel);
 HAL_Delay(1); // ~100us minimum tick
 }

 // Compute average
 uint32_t sum = 0;
 for(int i = 0; i < DC_SAMPLES; i++)
 sum += samples[i];

 float adc_avg = (float)sum / (float)DC_SAMPLES;

 // Convert ADC average -> voltage
 float voltage = ADC_To_Voltage_DC(adc_avg, range);

 // Apply calibration
 voltage = Apply_Voltage_Calibration(voltage, range, phase);

 // Disable channel after use
 //Channel_Enable(phase + 3, false);
 return voltage;
}

This is an issue i am facing, kindly help me to get out of this problem.

 


Edited to apply source code formatting - please see How to insert source code for future reference.

2 replies

TDK
Super User
December 16, 2025

Use a pull resistor to set the voltage to what you want when nothing else is connected. “Not connected” is not a voltage level that can be read. The adc can only read what is on the pin.

"If you feel a post has answered your question, please click ""Accept as Solution""."
RobK1
Associate II
December 16, 2025

The best solution would be to make your frontend switchable for AC/DC, so that no offset is applied in DC mode. Although all the DMM's I have ever worked with can measure both positive and negative voltages in DC ...

 

The simplest solution, of course, is to just subtract the half-scale value from your raw ADC readings.

 

Oh, and please use this option to insert readable code:

RobK1_0-1765898221190.png