Make a Two Range Voltmeter (DVM) With Auto / Manual Range Select Using the Arduino

by Rick-ecircuit in Circuits > Arduino

564 Views, 5 Favorites, 0 Comments

Make a Two Range Voltmeter (DVM) With Auto / Manual Range Select Using the Arduino

protoboard-assembly-connections.JPG

Create a Multi-Range Voltmeter (4V, 20V) using a Resistor Divider and a pair of Solid-State Relays (opto-isolated). A Low-Pass Filter removes unwanted noise. The Arduino's ADC converts the input voltage to a digital number. The C Code drives the Solid-State Relays via Digital Outputs, converts the ADC's digital value to an equivalent input voltage and sends the result to the serial monitor.

Run one of two C programs for either Manual (pushbutton) or Auto Range Selection. You can customize the hardware / software for a higher voltage range with the included Design Equations and Excel Calculator.

Specifications

  1. Voltage ranges: +4V and +20V.
  2. Input R: 1M both ranges
  3. Display update: 0.25s
  4. Low-Pass Filter: fc = 6Hz
  5. Pushbutton for manual range select
  6. Accuracy
  7. 4V Range: Verror = ±15mV ±1.5% of Reading
  8. 20V Range: Verror = ±75mV ±1.5% of Reading
  9. Measurement overange: 25%
  10. 4V Range: +5V
  11. 20V Range: +25V

Schematic and Circuit Overview

protoboard-schematic.JPG

Input Attenuator

For input voltages > 5V, a Resistor Divider is needed to scale down the input to less than 5V (ADC's max input range). Two MOSFET based Solid-State Relays (SSR) select either a direct unity-gain connection (SW1) or the tap on a resistor divider (SW2). Driving digital outputs D12 or D13 to 0V will pull current through the SSR's LED, turning on the switch.

The R Divider scales down the input voltage by a 5:1 ratio or 0.2 (ideally). Given the actual available resistor values we get.

K = v1/vin = R2/(R1+R2)

= 200k/(825k+200k)

= 0.195

The input resistance for both ranges can be calculated by

Rtot = R1+R2

= 200k+825k

= 1.025 M


Low-Pass Filter

A filter reduces external noise sources (60 Hz mains, system clocks, radio frequencies, etc.) that can be picked up by the circuit causing unwanted fluctuations on the signal being digitized by the ADC.

The filter passes the measured input signal below the cutoff frequency, fc.

fc = 1/(2*pi*RP1*CP1)

= 1/(2*pi*200k*0.15uF)

= 5.3 Hz

10-Bit ADC

The ADC converts the Analog input voltage (0 to 5 V) to a Digital output value (0 to 1023 counts). For the Lower Voltage Range (<5V), signals are passed directly to the ADC. For the Upper Voltage Range (>5V) the Resistor Divider Scales down input signal to less then 5V. The Arduino code then futher converts the Digital Value to an equivalent Input Voltage before the resistor divider (see Code section below).

The ADC produces an ouput based on the input voltage (Vadc) and a Refrence Voltage (Vref) which is the Aurduino's 5V Power Supply Level. For example, an ADC input of 2.5V produces an output code of

ADCword = Vadc / Vref * 210

= 2.5V/5.0V*1024

= 512 counts

Push Button

For manual selection of the Voltage Range, a push button creates one of two voltages connected to the Arduino's Digital input D11.

  1. OPEN - 5V through D11's internal pull-up resistor (logic 1)
  2. CLOSED - 0V through the closed switch (logic 0)

The Code will monitor the pushbutton's signal and toggle the Voltage Range when the button is pressed causing a 1 to 0 logic transition.

Arduino Interface

  1. 5V,GND - Power for Opto-Relays
  2. A0 - Analog input voltage representing input voltage.
  3. D12,D13 - Digital outputs to drive 2 Opto-Relays SW1 and SW2.
  4. D11 - Digital input to read state of the pushbutton SW3.
  5. USB - PC Interface and Power from the PC.

Parts List

Feel free to select alternate components for preference / availability / cost. The critical components required to meet the Accuracy Spec are the 0.1% resistors R1=825k and R2=200k. However, you can use 1% resistors with a relaxed accuracy. Also, you can also alternate resistors values close to those specified, just ensure you enter the R1 and R2 values in the code (see below).

Arduino

  1. 1 Arduino Uno Rev3 Microcontroller Board A000066
  2. 1 USB Cable Type A/B (generic)

ProtoBoard

  1. 1 Solderless Breadboard, SparkFun Electronics PRT-12002
  2. 1 Pkg Jumper Wires, Various Lengths, M to M, Adafruit Industries
  3. 2 Test Leads, Gator to Gator, BLK, RED (generic)

Components

  1. 2 (SW1,2) SSR RELAY SPST-NO 500MA 0-60V "TLP592A(F)
  2. 1 (R1) RES 825K OHM 0.1%, ≥1/10W
  3. 1 (R2) RES 200K OHM 0.1%, ≥1/10W
  4. 1 (RP1) RES 200K OHM ≤5%, ≥1/10W
  5. 1 (CP1) CAP CER 0.15UF ≥50V, ≤10% X7R
  6. 2 (R3,R4) RES 1K OHM ≤5%, ≥1/10W
  7. 1 (SW3) SparkFun Electronics COM-00097 Push Button

Build It - Arduino UNO + Protoboard

protoboard-assembly-connections.JPG

With the Solderless Protoboards, you can easily test, modify and experiment with your design. (Wiring diagram created at www.cirkitstudio.com/)

Please note, the diagram above is only a suggestion. Arrange parts and wire the circuit anyway you prefer!

Code 1 - Manual Range Select

DVM1_flowchart1.JPG

The Arduino's C Code follows the flow chart above.

/*
DVM1 Basic1

Read ADC and convert to voltage at R divider input.
Display Vinput at PC screen
Read Pushbuto change Vrange if pressed
*/

int Vrange = 0; // 0 - 4V Range, 1 - 20V Range
int ADCword = 0; // ADC value (counts)
float Vinput = 0; // input voltage (V)

// resistor divider
float R1 = 825000; // Rdivider
float R2 = 200000; // Rdivider
float Kdiv = 1; // gain

// digital bits
const int SW1 = 12; // sw1 control
const int SW2 = 13; // sw2 control
const int buttonPin = 11; // pushbutton input

// pushbutton press
int buttonState = 1; // current state of the button
int lastButtonState = 1; // previous state of the button

// the setup routine runs once *****************************
void setup() {
// initialize serial communication at 9600 bits per second:
Serial.begin(9600);

// configure digital pins
pinMode(SW1, OUTPUT); // set as output
pinMode(SW2, OUTPUT); // set as output
pinMode(buttonPin, INPUT_PULLUP); // set as input

// set voltage range manually with variable
Vrange = 0; // 0 - 4V Range, 1 - 20V Range

// set Vrange switches at R Divider
if (Vrange == 0) { // 4V Range
// sw to straight in
digitalWrite(SW1, LOW); // sw1 ON
digitalWrite(SW2, HIGH); // sw2 OFF
}
else { // 20V Range
// sw to divider tap
digitalWrite(SW1, HIGH); // sw1 OFF
digitalWrite(SW2, LOW); // sw2 ON
}

}



// the loop routine runs continuously **********************
void loop() {

// read analog input from ADC at pin A0
ADCword = analogRead(A0);

// calc input divider scaling
if (Vrange == 0) { // 4V Range
Kdiv = 1;
}
else { // 20V Range
Kdiv = R2/(R1+R2);
}

// convert ADC counts to input voltage
// Vinput = ADCvalue * Vref/2^N * 1/Kdiv
Vinput = ADCword * (5.0 / 1024.0) * 1/Kdiv ;

// read Vrange Pushbutton
buttonState = digitalRead(buttonPin);

// check for Hi to Lo transition
if (lastButtonState == 1 && buttonState == 0) {
// if transition, then toggle voltage range
if (Vrange == 0) {
Vrange = 1;
}
else {
Vrange = 0;
}

}

// update last state
lastButtonState = buttonState;

// set Vrange switches at R Divider
if (Vrange == 0) { // 4V Range
// sw to straight in
digitalWrite(SW1, LOW); // sw1 ON
digitalWrite(SW2, HIGH); // sw2 OFF
}
else { // 20V Range
// sw to divider tap
digitalWrite(SW1, HIGH); // sw1 OFF
digitalWrite(SW2, LOW); // sw2 ON
}

// time delay (ms) for approx 4 readings per second
delay(250);

// print out the values to computer as ASCII

Serial.print("Vinput,ADCword,buttonState,Vrange:\t");
Serial.print(Vinput,3);
Serial.print("\t");
Serial.print(ADCword);
Serial.print("\t");
Serial.print(buttonState);
Serial.print("\t");
Serial.println(Vrange);
}


Convert ADC Word to Input Voltage

For best accuracy, the resistor divider values are entered into the R1 and R2 variables.

float R1 = 825000; // Rdivider

float R2 = 200000; // Rdivider

The input divider gain is calculated according to voltage range

If Vrange=0 (LOW Range), then Kdiv = 1.0

If Vrange=1 (HIGH Range), then Kdiv = R2/(R1+R2)

Lastly, the Digital ADCword is converted to an Analog value by the factor Vref/2^10 and scaled to the equivalent input voltage by 1/Kdiv.

// Vinput = ADCword * Vref/2^N * 1/Kdiv

Vinput = ADCword * (5.0 / 1024.0) * 1/Kdiv ;


Solid-State Relay Control

Driving D12 or D13 low, turns ON SW1 or SW2.

If Vrange=0, then SW1=ON, SW2=OFF for direct connection.

If Vrange=1, then SW1=OFF, SW2=ON for connection to R Divider.


Manual Range Selection (Pushbutton)

You can configure D11's digital input to enable an internal pull-up resistor. This handy feature saves you a resistor and a couple of wires.

pinMode(buttonPin, INPUT_PULLUP);

The code checks for a 1 to 0 state transition of the pushbutton to toggle the Voltage Range between LOW and HIGH.

if (lastButtonState == 1 && buttonState == 0) {

TOGGLE RANGE code

}


Sample / Display Time

A delay of 0.25s is added at the end of the Main Loop for an approximate sample / display rate of 4 per second.

delay(250);

This means that the pushbutton must be held down for 0.25s max to read the 1 to 0 transition.

Code 2 - Auto Range Select

The Auto Range code is identical to the Manual Select except the PushButton code is replaced with a Range Select based on input level.

/*
DVM1 Basic with Auto Range

Read ADC and convert to voltage at R divider input.
Display Vinput at PC screen
Auto Vrange Select depending on input level
*/

int Vrange = 0; // 0 - 4V Range, 1 - 20V Range
int ADCword = 0; // ADC value (counts)
float Vinput = 0; // input voltage (V)

// resistor divider
float R1 = 825000; // Rdivider
float R2 = 200000; // Rdivider
float Kdiv = 1; // gain

// digital bits
const int SW1 = 12; // sw1 control
const int SW2 = 13; // sw2 control
const int buttonPin = 11; // pushbutton input

// the setup routine runs once *****************************
void setup() {
// initialize serial communication at 9600 bits per second:
Serial.begin(9600);

// configure digital pins
pinMode(SW1, OUTPUT); // set as output
pinMode(SW2, OUTPUT); // set as output
pinMode(buttonPin, INPUT_PULLUP); // set as input

// set voltage range manually with variable
Vrange = 0; // 0 - 4V Range, 1 - 20V Range

// set Vrange switches at R Divider
if (Vrange == 0) { // 4V Range
// sw to straight in
digitalWrite(SW1, LOW); // sw1 ON
digitalWrite(SW2, HIGH); // sw2 OFF
}
else { // 25V Range
// sw to divider tap
digitalWrite(SW1, HIGH); // sw1 OFF
digitalWrite(SW2, LOW); // sw2 ON
}

}


// the loop routine runs continuously **********************
void loop() {

// read analog input from ADC at pin A0
ADCword = analogRead(A0);

// calc input divider scaling
if (Vrange == 0) { // 4V Range
Kdiv = 1;
}
else { // 20V Range
Kdiv = R2/(R1+R2);
}

// convert ADC counts to input voltage
// Vinput = ADCvalue * Vref/2^N * 1/Kdiv
Vinput = ADCword * (5.0 / 1024.0) * 1/Kdiv ;


// Auto Range - check input level and set range
if (Vinput > 4.90) { // if above thresh, set to HIGH Range
Vrange = 1;
}
if (Vinput < 4.85) { // if below thresh, set to LO Range
Vrange = 0;
}

// set Vrange switches at R Divider
if (Vrange == 0) { // 4V Range
// sw to straight in
digitalWrite(SW1, LOW); // sw1 ON
digitalWrite(SW2, HIGH); // sw2 OFF
}
else { // 20V Range
// sw to divider tap
digitalWrite(SW1, HIGH); // sw1 OFF
digitalWrite(SW2, LOW); // sw2 ON
}

// time delay (ms) for approx 4 readings per second
delay(250);

// print out the values to computer as ASCII

Serial.print("Vinput,ADCword,Vrange:\t");
Serial.print(Vinput,3);
Serial.print("\t");
Serial.print(ADCword);
Serial.print("\t");
Serial.println(Vrange);
}

During each program loop, the code simply checks the input level and sets the Voltage Range accordingly.

If Vinput > 4.90V, then set HIGH Range

If Vinput < 4.85V, then set LOW Range

Notice the LOW threshold is 50mV less than the HIGH threshold! This difference (hysteresis) avoids continuously toggling between the HIGH/LOW ranges if both thresholds are at 4.9V and Vinput is at 4.9V with some noise fluctuations.

Try It!

It's easy to try, just grab a couple of household batteries!

  1. 1.5V Battery Health: 1.1 to 1.6V typical
  2. 9V Battery Health: 7.0 to 9.0V typical

Manual Range Select

Run the code: DVM1_basic_voltmeter1.ino

  1. The Serial Monitor will display: Vinput, ADCword, pushButton, Vrange
  2. Default values: pushButton = 1 (open), Vrange = 0 (LO Range)

Press the Push Button

  1. Does the pushButton transition from 1 to 0?
  2. Does the Vrange change from 0 (LOW RANGE) to 1 (HIGH RANGE)?
  3. Push the button again, does Vrange toggle back LOW?

Battery Health Check

  1. Connect a 1.5V Battery on the LOW Vrange and check the voltage.
  2. Connect a 9V Battery on the LOW Vrange. You should see a max reading near 5V.
  3. Press the pushbutton to change to the HIGH Range.


Auto Range Select

Run the code: DVM1_basic_voltmeter_auto1.ino

  1. The Serial Monitor will display: Vinput, ADCword, Vrange
  2. Default values: Vrange = 0 (LO Range)

Battery Health Check

  1. Connect a 1.5V Battery. The Range should auto switch to Vrange = 0.
  2. Connect a 9V Battery. The Range should auto switch to Vrange = 1.

Explore More!

If you've made it this far - high fives for you! The DVM showcases many key concepts of electronic measurement circuits and software, even for a basic instrument as this.

You can dive deeper into the circuit theory or customize the design for a different HIGH voltage range. Check out the following design files and links.

Design Equations

  1. DVM1_Design_Eqns.pdf (See attached)

Design Calculator

  1. Excel Calculator Link

SPICE Simulation

  1. SPICE Topic Link


Take a walk through the DVM Design from Specification development to Circuit Design, Coding and Final Test.

  1. DVM Design Series at ecircuitcenter.com