Starting Electronics needs your help! Please make a donation to help cover our hosting and other costs. Click the donate button to send a donation of any amount.

Software Voltmeter using Arduino

Created on: 29 May 2013

A four channel voltmeter that displays voltage readings in a software application running on a computer. An Arduino reads the voltages, and sends them to an application written in the Processing language.

A history of the voltage readings is displayed on a graph for each channel. Although the sample rate is slow, this is the beginnings of an oscilloscope or logic analyser. The code could also be adapted to make a voltage logger.

This video shows measurements being made with the software voltmeter. One of the channels takes readings across a slow flashing LED and the waveform can be seen on the graph.

Can't see the video? View on YouTube →

This video gives a better view of the software application.

Can't see the video? View on YouTube →

Software Voltmeter Circuit Diagram

The circuit uses a voltage divider on each channel to increase the readable range of the Arduino analog inputs from 5V to about 50V. For an explanation on how Arduino measures voltage, read the article on measuring DC voltage using Arduino.

Arduino software voltmeter circuit diagram
Arduino Software Voltmeter Circuit Diagram

How the Software Voltmeter Works

Code running on the Arduino reads the values on the four analog input channels and then calculates the voltages. The voltages are sent out of the serial / USB port as a string.

The string containing the voltages can be viewed by using the serial monitor window in the Arduino IDE for testing and debug purposes.

Instead of using the serial monitor window, an application written in the processing language is used to receive the voltage string from the USB port and display the values in a window.

Making the Project

To make the project, first build the hardware and connect it to an Arduino as shown in the circuit diagram.

Perform calibration of the voltmeter as described in the article on measuring DC voltage using Arduino and the Arduino LCD voltmeter.

Load the Arduino software (below) to the Arduino and connect it to the computer serial port.

Now run the processing application to connect to the Arduino and display the voltages on the computer. If you want to test that the Arduino code is working first, then connect to the Arduino using the serial monitor window to see if the voltages are being displayed.

Books that may interest you:

C Programming with Arduino Book Ultimate Arduino MEGA 2560 Hardware Manual Ultimage Arduino Uno Hardware Manual

Arduino Software Voltmeter Code

The Arduino code is a modified version of the sketch from the Arduino LCD voltmeter project.

Read the information from the LCD voltmeter project for an explanation on how the code works. The only difference between the two projects is that the software voltmeter sends the voltages as a string on the USB port instead of displaying the voltages on the LCD.

/*--------------------------------------------------------------
  Program:      sw_voltmeter_4ch

  Description:  4 channel software voltmeter that displays
                voltage readings in a Processing application
                running on a computer.
  
  Hardware:     Arduino Uno with voltage dividers on A2 to A5.
                
  Software:     Developed using Arduino 1.0.5 software
                Should be compatible with Arduino 1.0 +
                voltmeter_4ch Processing software runs on PC

  Date:         28 May 2013
 
  Author:       W.A. Smith, http://startingelectronics.org
--------------------------------------------------------------*/

// number of analog samples to take per reading, per channel
#define NUM_SAMPLES 10
// voltage divider calibration values
#define DIV_1    11.1346
#define DIV_2    11.1969
#define DIV_3    11.0718
#define DIV_4    11.0718
// ADC reference voltage / calibration value
#define V_REF    4.991

int sum[4] = {0};                // sums of samples taken
unsigned char sample_count = 0;  // current sample number
float voltage[4] = {0.0};        // calculated voltages
char l_cnt = 0;                  // used in 'for' loops

void setup()
{
    Serial.begin(9600);
}

void loop()
{
    // take a number of analog samples and add them up
    while (sample_count < NUM_SAMPLES) {
        // sample each channel A2 to A5
        for (l_cnt = 0; l_cnt < 4; l_cnt++) {
            sum[l_cnt] += analogRead(A2 + l_cnt);
        }
        sample_count++;
        delay(10);
    }
    // calculate the voltage for each channel
    for (l_cnt = 0; l_cnt < 4; l_cnt++) {
        voltage[l_cnt] = ((float)sum[l_cnt] / (float)NUM_SAMPLES * V_REF) / 1024.0;
    }
    
    // each voltage is multiplied by the resistor network
    // division factor to calculate the actual voltage
    voltage[0] = voltage[0] * DIV_1;
    voltage[1] = voltage[1] * DIV_2;
    voltage[2] = voltage[2] * DIV_3;
    voltage[3] = voltage[3] * DIV_4;
    
    // send voltages to Processing application via serial port / USB
    // voltage 1 - A (pin A2)
    Serial.print("A ");
    Serial.print(voltage[0], 1);
    Serial.print("V ");
    // voltage 2 - B (pin A3)
    Serial.print("B ");
    Serial.print(voltage[1], 1);
    Serial.print("V ");
    // voltge 3 - C (pin A4)
    Serial.print("C ");
    Serial.print(voltage[2], 1);
    Serial.print("V ");
    // voltage 4 - D (pin A5)
    Serial.print("D ");
    Serial.print(voltage[3], 1);
    Serial.print("V ");
    Serial.println("");
    delay(10);
    // reset count and sums
    sample_count = 0;
    for (l_cnt = 0; l_cnt < 4; l_cnt++) {
        sum[l_cnt] = 0;
    }
}

Software Voltmeter Processing Application Code

The listing for the Processing code below is long, so you may prefer to download it here rather than copy and paste.

You may need to modify a line of code in the setup() function to be able to connect to the USB port that the Arduino is attached to.

When run from the Processing IDE, a list of available ports will be displayed at the bottom of the Processing IDE window. Try changing this line of code to point to a valid USB port:

ser_port = new Serial(this, Serial.list()[0], 9600);

If a valid port is shown to be at [1] or [2], then change the value in the square brackets at Serial.list()[0] to this number to select a different serial port.

/*--------------------------------------------------------------
  Program:     voltmeter_4ch

  Description:  Gets a string containing voltages from an
                Arduino over the USB port and displays the
                voltages in a window.
                The sw_voltmeter_4ch sketch must be running
                on the Arduino

  Date:         28 May 2013
 
  Author:       W.A. Smith, http://startingelectronics.org
--------------------------------------------------------------*/
import processing.serial.*;

Serial ser_port;                  // for serial port
char rx_byte;                     // stores single byte received from serial port
String volts[] = {"", "", "", ""};// 4 voltages from the 4 channels
int volts_index = 0;              // current channel
boolean got_volts = false;        // flags received voltage
int graph[][];                    // graph values for each channel
int graph_x = 0;                  // current x position of graph
PFont fnt;                        // for font
// voltage scale for channels, default set to 30V
int ch1_scale = 30;
int ch2_scale = 30;
int ch3_scale = 30;
int ch4_scale = 30;

void setup()
{
    size(600, 450);
    background(0);
    fnt = createFont("Arial", 16, true);
    
    println(Serial.list());
    // modify Serial.list()[0] to select correct serial port
    ser_port = new Serial(this, Serial.list()[0], 9600);
    
    // initialize graph array
    graph = new int[4][400];
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 400; j++) {
            graph[i][j] = -1;  // fill graphs with invalid values
        }
    }
}

void draw()
{
    // update voltage in window if voltages received from USB port
    if (got_volts) {
        got_volts = false;
        background (0);
        DrawGraphAxis(160, 28, 400, 80);
        DrawGraphAxis(160, 128, 400, 80);
        DrawGraphAxis(160, 228, 400, 80);
        DrawGraphAxis(160, 328, 400, 80);
        // display voltages
        voltage(1, volts[0]);
        voltage(2, volts[1]);
        voltage(3, volts[2]);
        voltage(4, volts[3]);
        // draw the voltage graphs
        DrawGraph(volts[0], 0, 161, 107, ch1_scale);
        DrawGraph(volts[1], 1, 161, 207, ch2_scale);
        DrawGraph(volts[2], 2, 161, 307, ch3_scale);
        DrawGraph(volts[3], 3, 161, 407, ch4_scale);
        // draw the scale selection radio buttons
        DrawScaleSelect(10, 54, ch1_scale);
        DrawScaleSelect(10, 154, ch2_scale);
        DrawScaleSelect(10, 254, ch3_scale);
        DrawScaleSelect(10, 354, ch4_scale);
    }
}

// called when data has been received on the serial port
void serialEvent(Serial p)
{
    while (p.available() > 0) {
        // get a single character from the serial port
        rx_byte = ser_port.readChar();
        // determine which channel voltage is from
        if (rx_byte == 'A') {
            volts_index = 0;
            volts[volts_index] = "";
        }
        else if (rx_byte == 'B') {
            volts_index = 1;
            volts[volts_index] = "";
        }
        else if (rx_byte == 'C') {
            volts_index = 2;
            volts[volts_index] = "";
        }
        else if (rx_byte == 'D') {
            volts_index = 3;
            volts[volts_index] = "";
        }
        // only use characters 0 to 9 and .
        if ((rx_byte >= '0' && rx_byte <= '9') || (rx_byte == '.')) {
            volts[volts_index] += rx_byte;  // build the voltage string
        }
        // all 4 voltages received?
        if ((rx_byte == 'V') && (volts_index == 3) &&
            (volts[0].length() != 0) && (volts[1].length() != 0) &&
            (volts[2].length() != 0) && (volts[3].length() != 0)) {
            got_volts = true;    // flag received temperature
            //print ("Got voltage: ");    // debug
            //println (volts[volts_index]);      // debug
        }
    }
}

// display the channel number and voltage
void voltage (int channel, String volts)
{
    int y_pos = 40;
    int spacing = 100;
    String txt = "";
    
    textFont(fnt, 18);
    if (channel == 1) {
        fill (255, 0, 0);
        rect (15, y_pos - 20, 110, 25, 7);
        fill(255);
        txt = "CH1: " + volts + " V";
    }
    else if (channel == 2) {
        y_pos += spacing;
        fill (0, 255, 0);
        rect (15, y_pos - 20, 110, 25, 7);
        fill(255);
        txt = "CH2: " + volts + " V";
    }
    else if (channel == 3) {
        y_pos += (spacing * 2);
        fill (0, 0, 255);
        rect (15, y_pos - 20, 110, 25, 7);
        fill(255);
        txt = "CH3: " + volts + " V";
    }
    else if (channel == 4) {
        y_pos += (spacing * 3);
        fill (255, 0, 255);
        rect (15, y_pos - 20, 110, 25, 7);
        fill(255);
        txt = "CH4: " + volts + " V";
    }
    
    text (txt, 20, y_pos);
}

void DrawGraphAxis(int pos_x, int pos_y, int width, int height)
{
    stroke(255);
    line(pos_x, pos_y, pos_x, pos_y + height);      // vertical axis
    line(pos_x, pos_y + height, pos_x + width, pos_y + height);    // horizontal axis
}

void DrawGraph (String voltage, int channel, int pos_x, int pos_y, int scale)
{
    int y_start;  // temporary index into graph array when drawing graph
    
    graph[channel][graph_x] = int(voltage);
    // select colour, depending on channel number
    if (channel == 0) {
        stroke(255, 0, 0);
    }
    else if (channel == 1) {
        stroke(0, 255, 0);
    }
    else if (channel == 2) {
        stroke(0, 0, 255);
    }
    else if (channel == 3) {
        stroke(255, 0, 255);
    }
    y_start = graph_x;    // temporary copy of graph index
    // draw the graph
    for (int i = 0; i < 400; i++) {
        if (graph[channel][y_start] >= 0) {
            int y_val = (graph[channel][y_start] * 80 / scale);
            if (y_val > 80) {
                y_val = 80;
            }
            point(i + pos_x, pos_y - y_val);
        }
        y_start--;
        if (y_start < 0) {
            y_start = 399;
        }
    }
    if (channel == 3) {
        // only increment graph x position on last channel 
        graph_x++;
        if (graph_x >= 400) {
          graph_x = 0;
        }
    }
}

// draw the scale select box with radio buttons
void DrawScaleSelect (int x_pos, int y_pos, int scale)
{
    fill(0);
    stroke(255);
    rect (x_pos, y_pos, 120, 60, 7);  // box around scale selection
    
    fill(255);
    textFont(fnt, 16);
    text ("5V", x_pos + 25, y_pos + 25);
    text ("10V", x_pos + 80, y_pos + 25);
    text ("20V", x_pos + 25, y_pos + 50);
    text ("30V", x_pos + 80, y_pos + 50);
    noStroke();
    fill(255);
    // draw the radio buttons
    ellipse (x_pos + 15, y_pos + 20, 10, 10);
    ellipse (x_pos + 70, y_pos + 20, 10, 10);
    ellipse (x_pos + 15, y_pos + 45, 10, 10);
    ellipse (x_pos + 70, y_pos + 45, 10, 10);
    fill(0);
    // draw the dot in the radio button
    if (scale == 5) {
        ellipse (x_pos + 15, y_pos + 20, 6, 6);
    }
    else if (scale == 10) {
        ellipse (x_pos + 70, y_pos + 20, 6, 6);
    }
    else if (scale == 20) {
        ellipse (x_pos + 15, y_pos + 45, 6, 6);
    }
    else if (scale == 30) {
        ellipse (x_pos + 70, y_pos + 45, 6, 6);
    }
}

// called when mouse button is clicked
// determine which radio button was pressed and change to selected scale
void mousePressed()
{
    // first determine which box click was made in
    // channel 1
    if (mouseX >= 10 && mouseX <= 130 && mouseY >= 54 && mouseY <= 114) {
        // determine which radio button was clicked
        // 5V
        if (mouseX >= 20 && mouseX <= 30 && mouseY >= 69 && mouseY <= 79) {
            ch1_scale = 5;
        }
        // 10V
        else if (mouseX >= 75 && mouseX <= 85 && mouseY >= 69 && mouseY <= 79) {
            ch1_scale = 10;
        }
        // 20V
        else if (mouseX >= 20 && mouseX <= 30 && mouseY >= 94 && mouseY <= 104) {
            ch1_scale = 20;
        }
        // 30V
        else if (mouseX >= 75 && mouseX <= 85 && mouseY >= 94 && mouseY <= 104) {
            ch1_scale = 30;
        }
    }
    // channel 2
    else if (mouseX >= 10 && mouseX <= 130 && mouseY >= 154 && mouseY <= 214) {
        // 5V
        if (mouseX >= 20 && mouseX <= 30 && mouseY >= 169 && mouseY <= 179) {
            ch2_scale = 5;
        }
        // 10V
        else if (mouseX >= 75 && mouseX <= 85 && mouseY >= 169 && mouseY <= 179) {
            ch2_scale = 10;
        }
        // 20V
        else if (mouseX >= 20 && mouseX <= 30 && mouseY >= 194 && mouseY <= 204) {
            ch2_scale = 20;
        }
        // 30V
        else if (mouseX >= 75 && mouseX <= 85 && mouseY >= 194 && mouseY <= 204) {
            ch2_scale = 30;
        }
    }
    // channel 3
    else if (mouseX >= 10 && mouseX <= 130 && mouseY >= 254 && mouseY <= 314) {
        // 5V
        if (mouseX >= 20 && mouseX <= 30 && mouseY >= 269 && mouseY <= 279) {
            ch3_scale = 5;
        }
        // 10V
        else if (mouseX >= 75 && mouseX <= 85 && mouseY >= 269 && mouseY <= 279) {
            ch3_scale = 10;
        }
        // 20V
        else if (mouseX >= 20 && mouseX <= 30 && mouseY >= 294 && mouseY <= 304) {
            ch3_scale = 20;
        }
        // 30V
        else if (mouseX >= 75 && mouseX <= 85 && mouseY >= 294 && mouseY <= 304) {
            ch3_scale = 30;
        }
    }
    // channel 4
    else if (mouseX >= 10 && mouseX <= 130 && mouseY >= 354 && mouseY <= 414) {
        // 5V
        if (mouseX >= 20 && mouseX <= 30 && mouseY >= 369 && mouseY <= 379) {
            ch4_scale = 5;
        }
        // 10V
        else if (mouseX >= 75 && mouseX <= 85 && mouseY >= 369 && mouseY <= 379) {
            ch4_scale = 10;
        }
        // 20V
        else if (mouseX >= 20 && mouseX <= 30 && mouseY >= 394 && mouseY <= 404) {
            ch4_scale = 20;
        }
        // 30V
        else if (mouseX >= 75 && mouseX <= 85 && mouseY >= 394 && mouseY <= 404) {
            ch4_scale = 30;
        }
    }
}

New Book: Explore ATtiny Microcontrollers using C and Assembly Language

Explore ATtiny Microcontrollers using C and Assembly Language book Ultimate Arduino Uno Hardware Manual

As an Amazon Associate I earn from qualifying purchases: