Precision Accurate DC Voltage Measurement Questions

Hello As the summer is coming along as i wanted to make a Summer project of DC voltage Measurement. I thought it be a very nice learning project to make a accurate DC Multi meter. I know a multi meter like fluke can do the same thing or basically any other Multi Meter can do it. But the point of this project is To learn how i cna make one or what is needed to make one. Set aside the coding part for a minute. What Type of Resistors, ADC would i need? I know a External ADC would be needed in this case. But Which one that is within 0.01+- Percent? I would like to measure from 1v up to 48v and anywhere from Milliamp up to 20 amp range**.** Trying to get a Real true reading. I know a basic UNO board would be a little slow and all. I do have the Arduino zero board I'm going to use. But what am i missing? I also know it is not a over night project and will take some time. But my summer is free now And I'm ready to learn something new.

What Type of Resistors, ADC would i need? I know a External ADC would be needed in this case. But Which one that is within 0.01+- Percent?

Your basic fluke probably isn't that accurate...

I think the Arduino's ADCs is accurate to +/- 1 bit, which is 0.1% at the top of the range, and less as you go down.

You also need a precision voltage reference (or at least a stable voltage reference) and then you can calibrate digitally, using known-good, calibrated meter.

I'd like to measure from 1v up to 48v and anywhere from Milliamp up to 20 amp range.

You can use a rotary switch with a variety of resistors to make a switchable voltage divider. Or, you can use analog-switch chips if you want to make it auto-ranging. Current measurement is tricky and you'll probably be measuring voltage drops of less than 1V across very small resistors.

I know a basic UNO board would be a little slow and all.

It can update an LCD display faster than your eye an brain can keep-up with the changes. You'll need to intentionally slow it down so you can read rapidly-changing voltages.

But what am i missing?

You need an over-voltage protection circuit, and you need a precision rectifier (made with an op-amp) if you want to read negative voltages (that will also work for AC) and you need a comparator (another op-amp) to detect negative or positive.

If you're using a battery that's good because everything will be isolated. If you're using a power supply, don't share the power supply with anything else because the voltmeter's ground has to be floating relative to whatever you are measuring, especially for the current measurements, of in case you get the probes reversed. (Don't use your computer's USB port for power.)

But my summer is free now And I'm ready to learn something new.

Lucky you... The 1st day of Spring was just a few days ago here in California. :smiley: :smiley: :smiley:

First of all you need an ADC with at least 16 bit resolution. I cannot recommend any specific type, you'll have to find out yourself about the non-linearity, missing codes and other characteristics (temperature dependency...) of the selected chip. If not included in the chip, you'll also need a precision voltage reference. Next come thermally stable precision resistors for the voltage dividers, and a precision range selector switch for low currents. In detail the switch and connectors should not add a thermal voltage.

For current measurement an amplifier is required, which should not add too much noise or other effects to the signal. For high currents the simplest solution may be a hall effect based module, including the amplifier. Again the temperature dependency of the shunt resistors should be checked.

Much computation power is not required. The ADC will limit the sampling rate, and the time between two samples will be sufficient for scaling the raw values, including compensation of odd voltage divider ratios. Also a sliding average can be computed, for improved noise suppression. Other average computations can decrease the output rate, depending on the number of samples used to compute the average.

Hello DVDdoug. Today was the first day in a long time we got some good weather. And i thank you for all this Information. I wasn't really sure if DC voltage and current Measurement was the only thing i wanted to do. I also thought about AC as well. But i thought mostly all but one project of mine is basically DC voltage and anywhere from 1.8 v up to 36 v i been doing. and somewhere around 5 milliamps up to 10 amps.

The protection Circuit Is one thing i forgot to put in the Post. Yes that is must no matter what. I'm also looking into that. Basically i wasn't sure also if i was going to make it into a Desktop meter or a portable handheld meter. I have 2 enclosures one i found online based off a cheap handheld kind of design and one is a old HP desktop enclosures. Both are great. But my projects are 75% home use or made at home. So my choice is a Desktop unit.

One thing i was noticing is that some arduino volt meter projects i searched online have a 100 amp Shunt in it. I my self never used one before But i do have a few. So not sure if needed or not.

As far as the LCD is I was thinking of just using a basic 2 line 16 character Display. First line for Voltage then leave some space on the first line for amps and Second line for Watts. I have a very good INA226 i been trying out. From what i been reading It's a very good Chip. This is one of the sites i been looking up the specs on it Here.

Oh so one other thing looks like a Wall Power Supply i will be using i have a very good one that is Accurate 12v 4 amps not a cheap one Ether a friend of mine gave me it.

Hello DrDiettrich thank you for that information. It is also helping me a lot to learn about Voltages and reference's

Update to DVDdoug. I will also look into the precision rectifier area and figure that out. If you have any thoughts in that I would like to here about it, Or if you know a site in mind that i can take a look at please let me know.

I have used an MCP3426 2 channel 16bit adc an ad820 op-amp set up as a differential with 100k/10k with a precision 2.50v (ref3025) and can read +/- 0-100v. The unit works from a battery so this way the voltage input is isolated so the meter reads the input voltage correct no matter which way round you connect it. Comparing it with my calibrated fluke meter it read bang on.
I go away for a few days later if I get time I will post my schematic and code.
Others could take a look and see if the design could be improved along with the code.

This is how I did mine, it will measure up to+/- 100V input. It uses a 2.5V reference voltage fed through 2K resistors giving 1.25v. So when the input voltage is a plus it increases and decreases with a negative. I've also used this on a current meter just changed the values of the resistors to give a gain rather than divide. It runs of a battery pack and get about 40+ hours continuous use from a 3.7V 2500mha battery boosted to 5V.
This is the code for it, Bearing in mind it's part of a wireless meter I did which the range works really well and the readings seem very stable. There may be ways to improve or better methods of reading the voltage or optimisation in cleaner code , but for me it works and happy but feel free to give your thoughts inputs.

Here is the code text was to long

//####################################################
//# Include all the libraries                        #
//####################################################
//#include <LiquidCrystal_I2C.h> // I2C LCD display only for intial setup
#include <LiquidCrystal.h>// Highlight out when ready to use mian display
#include  <Wire.h> //Wire libary 
#include  <MCP342X.h>
#include <SPI.h>   // Comes with Arduino IDE
#include "RF24.h"  // Download and Install (See above)
#define ENABLE_SERIAL // Enable only for debugging
//####################################################
//# Define the output,input pins & voltage referance #
//####################################################
#define VMIN 2.82 // Battery voltage minimum
#define VMAX 4.02  //battery voltage maximum
#define LCD_BK_Manual A2 //Button that turns the unit off
#define Auto_turn_off A1
#define LCD_BL_LIGHT 9 //Controlls the brightness of the LCD
#define AVG_NUM 10   //Number of samples to take for ADC reading
//####################################################
//# Define the Variables                             #
//####################################################
byte Battery_Select;
byte addresses[][6] = {"1Node"}; // Create address for 1 pipe.
int i;
int b;
int CS = 1; //Line counter
const unsigned long refresh_rate = 2 * 60 * 1000UL; // 2 minutes
unsigned long lastUpdate = 0; // Keep track of last update time
int led = 9;
int brightness = 0;
unsigned long LCD_timeout = 10000UL * 60UL; // Auto time out after 5 minutes
unsigned long LCD_previousMillis = 0;
unsigned long LCD_timeoutmillis = 0;
unsigned long interval3 = 50;
unsigned long LCD_currentMillis = 0;
unsigned long LCD_previousMillis1 = 0;
float VResult1; //temp var to hold voltage readng (Raw incoming)
float VResult2; //Temp var to hold battery voltage reading
float VRef = 2.048; // Referance voltage
float V_out;
float Volt_scale = 1.2469375; //Change this if the incoming reading with nothing connected on the input terminals
float volts1;
float volts2;
float res2 = 4700.;
float res1 = 10000.00;
float batteryPercentage;
float percent;// interval at which to update screens
static int16_t  result1;
static int16_t temp1;
static int16_t  result2;
static int16_t temp2;
//####################################################
//#  Specifying the CE and CS pins & LCD Pins        #
//####################################################
RF24 myRadio (6, 10); // "myRadio" is the identifier you will use in following methods
LiquidCrystal lcd(2, 4, 5, 3, 7, 8);
//LiquidCrystal_I2C lcd(0x03F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address
// initialize the library with the numbers of the interface pins
MCP342X myADC1;
MCP342X myADC2;
//####################################################
//#  Setup first power up                            #
//####################################################
void setup() {
#ifdef ENABLE_SERIAL
  Serial.begin(9600);
  Serial.println("");
  Serial.println("=====================================");
  Serial.println("METER TIME TEST");
  Serial.println("=====================================");
#endif //#ifdef ENABLE_SERIAL
  // disable ADC
  ADCSRA = 0;
  // set up the LCD's number of columns and rows:
 //lcd.begin (20,4); // for 20 x 4 LCD module debuging only
// lcd.setBacklight(HIGH);//debuging only used for I2C LCD
lcd.begin(8, 2);// General LCD 2 x 8 with backlight
   pinMode (LCD_BL_LIGHT, OUTPUT);
  pinMode(Auto_turn_off, OUTPUT);
  digitalWrite(Auto_turn_off, LOW);
  digitalWrite(LCD_BL_LIGHT, LOW);
  myRadio.begin();  // Start up the physical nRF24L01 Radio
  myRadio.setChannel(108);  // Above most Wifi Channels
  myRadio.setPALevel(RF24_PA_MAX);  // Uncomment for more power
  myRadio.setDataRate(RF24_250KBPS); // Fast enough.. Better range
  myRadio.openWritingPipe( addresses[0]); // Use the first entry in array 'addresses' (Only 1 right now)
  pinMode(led, OUTPUT);
  analogWrite(led, brightness);
  Wire.begin();  // join I2C bus
  TWBR = 12;  // 400 kHz (maximum)
  myADC1.configure( MCP342X_MODE_CONTINUOUS |
                    MCP342X_CHANNEL_1 |
                    MCP342X_SIZE_16BIT |
                    MCP342X_GAIN_1X
                  );
  myADC2.configure( MCP342X_MODE_CONTINUOUS |
                    MCP342X_CHANNEL_2 |
                    MCP342X_SIZE_16BIT |
                    MCP342X_GAIN_1X
                  );
  lcd.setCursor(0, 0);
  lcd.print("POWERING");
  lcd.setCursor(0, 1);
  lcd.print("  UP  ");// Print something on the display to show
  delay(1000);                                   // it's working.
  lcd.clear();


}
//####################################################
//#  Main Lop starts here                            #
//####################################################
void loop() {
  LCD_timeoutmillis = millis();
  if (LCD_timeoutmillis - LCD_previousMillis1 >= LCD_timeout) {
    LCD_previousMillis1 = LCD_timeoutmillis;
    digitalWrite(Auto_turn_off, HIGH);
  }

  float sum1 = 0;
  myADC1.startConversion();// incoming voltage loop through reading raw adc values AVG_NUM number of times
  for (b = 0; b < AVG_NUM; b++) {
    temp1 = myADC1.getResult(&result1);        // read the input pin
    sum1 += temp1;                       // store sum for averaging
    //delayMicroseconds(50);             // pauses for 50 microseconds
    sum1 = sum1 / AVG_NUM;
  }
  float sum2 = 0;
  myADC2.startConversion();// incoing battery voltage loop through reading raw adc values AVG_NUM number of times
  for (i = 0; i < AVG_NUM; i++) {
    temp2 = myADC2.getResult(&result2);        // read the input pin
    sum2 += temp2;                       // store sum for averaging
    //delayMicroseconds(50);             // pauses for 50 microseconds,may remove yet ?
    sum2 = sum2 / AVG_NUM;
  }
  VResult1 = result1 * VRef / 32768.0; // convert to voltage reading
  VResult2 = result2 * VRef / 32768.0; // convert to voltage reading
  V_out = Volt_scale - VResult1; // 2.500V referance voltage - incoming voltage
  volts1 = V_out * 200.0; // Convert to real time voltage reading
  volts2 =  VResult2 / (res2 / (res1 + res2)); // devide the incoming battery voltage
  batteryPercentage = ((volts2 - VMIN) / (VMAX - VMIN)) * 100; // convert battery voltage to percentage reading
  myRadio.write( &volts1, sizeof(volts1) ); //  Transmit all the data
  //Create delay without blocking.
  LCD_currentMillis = millis();
  if (LCD_currentMillis - LCD_previousMillis > interval3)
  {
    LCD_previousMillis = LCD_currentMillis;
    write_to_lcd();
  }
#ifdef ENABLE_SERIAL
  if (millis() - lastUpdate >= refresh_rate)  {

    CS = CS + 1;
    lastUpdate = millis();
    Serial.print("DATA: ");
    Serial.println(CS);
    Serial.print("VOLTS:");
    Serial.println(volts2, 3);
    Serial.print("BATTERY %");
    Serial.println(batteryPercentage);
  }
#endif //#ifdef ENABLE_SERIAL
  if (batteryPercentage < 25) {
     lcd.setCursor(0, 0);
     lcd.print("CHARGE   ");
     lcd.setCursor(0, 1);
     lcd.print("BATTERY ");
     delay(2000);
     digitalWrite(Auto_turn_off, HIGH);
    }
  
  //  myRadio.write( &volts1, sizeof(volts1) ); //  Transmit the data
}


//####################################################
//#  Display the data on the LCD                     #
//####################################################
void write_to_lcd() {
  lcd.setCursor(0, 1);                             //
  lcd.print(volts1, 2);
  lcd.print(" V   ");
 //lcd.setCursor(0, 3);                             //
  //lcd.print(VResult1,7);
 
  if (batteryPercentage > 60) {
    Battery_Select = 0;
  }
  else if ((batteryPercentage < 60) && (batteryPercentage > 41)) {
    Battery_Select = 1;
  }
  else if ((batteryPercentage < 40) && (batteryPercentage > 30)) {
    Battery_Select = 2;
  }
  else if (batteryPercentage < 30) {
    Battery_Select = 3;
  }
  switch (Battery_Select) {
    case 0:
      lcd.setCursor(0, 0);
      lcd.print(batteryPercentage, 0);
      lcd.print("%     ");
      break;
    case 1:
      lcd.setCursor(0, 0);
      lcd.print("LOW BATT");
      break;
    case 2:
      lcd.setCursor(0, 0);
      lcd.print("BAT FLAT");
      break;
    case 3:
      lcd.setCursor(0, 0);
      lcd.print("CRITICAL");
      break;
  }

}

Thank you Steveiboy and Everyone else who comment on this post. This is helping me a lot to figure out what I'm going to do for this summer project. I will post a update when i get started on this project. Again Thank you to all who have comment here.