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.
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.
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.
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.
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;
}
}
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;
}
}
}