cancel
Showing results for 
Search instead for 
Did you mean: 

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

Pandian
Associate II

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 2
TDK
Super User

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
Senior

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