Skip to main content
Graduate
December 16, 2025
Question

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

  • December 16, 2025
  • 2 replies
  • 74 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.

    This topic has been closed for replies.

    2 replies

    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.

    Graduate 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