2025-12-16 6:54 AM - last edited on 2025-12-16 7:12 AM by Andrew Neil
MCU: STM32H723VET6
ADC: ADC1 / ADC2 / ADC3 (12-bit, single-ended)
VREF: 3.3 V
Application: Digital Multi meter (DC / AC voltage measurement)
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:
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
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.
2025-12-16 7:02 AM
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.
2025-12-16 7:14 AM - edited 2025-12-16 7:17 AM
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: