Introduction: Arduino Precise & Accurate Volt Meter (0-90V DC)

In this instructable, I've built a voltmeter to measure high voltages DC (0-90v) with relative precision and accuracy using an Arduino Nano.

The test measurements I took were accurate enough, mostly within 0.3v of the actual voltage measured with a standard voltmeter (I used an Astro AI DM6000AR). This is close enough for my intended use of the device.

To archive this I used a voltage reference (4.096v) and voltage divider.

On the code side, I used, of course, the "external reference" option for the Arduino Nano and the "Smoothing" example in the Arduino tutorials.


1 x Arduino Nano - Link

1 x Oled Display (SSD 1306) - Link

1 x 1/4W 1% Resistors - 1k ohm - Link

1 x 1/4W 1% Resistors - 220k ohm - Link

1 x 1/4W 1% Resistors - 10k ohm - Link

1 x 4.096v LM4040DIZ-4.1 Voltage Reference - Link

Breadboard and wires - Link

Astro AI DM6000AR - Link

USB Power Bank - Link

9V Batteries - Link

CanadianWinters is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn fees by linking to and affiliated sites. By using these links, as an Amazon Associate I earn from qualifying purchases, even if you buy something else--and it won't cost you anything.

Step 1: The Schematics

I connected all the parts as per the schematics above. In particular I chose the 4.096 voltage reference to stay as close as possible to the 5v mark to avoid loosing resolution.

Following the datasheet, I chose a 1K ohm resistor for the voltage reference even though a different value could be used. The voltage for the reference is supplied from the Nano 5v pin.

The idea of the circuit is that the DC voltage to be measured goes through a voltage resistor. The scaled voltage and then gets into the analog pin of the Arduino to be sampled, smoothed, re-scaled and displayed on the OLed display.

I tried to keep things simple :)

Step 2: The Code and Resistor Calculations

The resistors values were chosen as it is advisable (if I am not mistaken this is on the Arduino/Atmega datasheet) to keep the impedance below 10k ohm.

To simplify things, I made a spreadsheet that automates the calculations in case you want to use different resistor values: Link to Google Sheet

Here is the code I used for this project:

#include <Arduino.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0);// (rotation, [reset])

float voltage =0; // used to store voltage value
float Radjust = 0.043459459; //Voltage divider factor ( R2 / R1+R2 )
float vbat =0; //final voltage after calcs- voltage of the battery
float Vref = 4.113; //Voltage reference - real value measured. Nominal value 4.096v 
const int numReadings = 50; // number of reading samples - increase for more smoothing. Decrease for faster reading.
int readings[numReadings];      // the readings from the analog input
int readIndex = 0;              // the index of the current reading
unsigned long total = 0;                  // the running total
int average = 0;

//variables for refreshing the screen without using delay
unsigned long previousMillis = 0;        // will store last time the screen was updated

// constants won't change:
const long interval = 50;           // interval at which to refresh the screen (milliseconds)


void setup(void) {

   analogReference(EXTERNAL); // use AREF for reference voltage 4.096. My reference real voltage is 4.113v 

    for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    readings[thisReading] = 0;

void loop(void) {
 unsigned long currentMillis = millis();
 //voltage calculations with smoothing average

// subtract the last reading:
  total = total - readings[readIndex];
  // read from the sensor:
  readings[readIndex] = analogRead(A0);
  // add the reading to the total:
  total = total + readings[readIndex];
  // advance to the next position in the array:
  readIndex = readIndex + 1;

  // if we're at the end of the array...
  if (readIndex >= numReadings) {
    // ...wrap around to the beginning:
    readIndex = 0;

  // calculate the average:
  average = (total / numReadings); 
  voltage = average * (Vref / 1023.0); //4.113 is the Vref
  vbat = voltage/Radjust;
// Setting the delay for the screen refresh using Millis 
  if (currentMillis - previousMillis >= interval) {
    // save the last time the screen was updated 
    previousMillis = currentMillis;
  u8g2.clearBuffer();          // clear the internal menory
//Pack Voltage display
  u8g2.setFont(u8g2_font_fub20_tr);  // 20px font
  u8g2.setCursor (1, 20);
  u8g2.setFont(u8g2_font_8x13B_mr);  // 10 px font
  u8g2.setCursor (76, 20);
  u8g2.setCursor (1,40);
  u8g2.setCursor (1,60);
  u8g2.print("Precise Voltage");
  u8g2.sendBuffer();          // transfer internal memory to the display

Please note I am a bit rusty with Arduino coding, so if you find any mistake or a way to improve the code, I am open to suggestions :)

Step 3: Let's Test It Out!

To test this voltmeter I used 8x 9v batteries that I got at a local store. I am planning to use this voltmeter to measure the voltage on my electric bicycles battery packs (they have voltages ranging from 24-60v with the occasional 72v ones).

Once the electronics are packaged into a pcb and a little box, this will make a nice and portable battery pack meter. The graphics and fonts on the OLED could be customized to fit your needs (eg. bigger font for easy reading).

My goal was to have a voltage reading on the Oled/Arduino meter not too far from my Digital Multi Meter. I was aiming for +/-0,3v max delta. As you can see from the video I was able to archive this except at the top end of the measurements.

I hope you enjoyed this Instructable and let me know your thoughts!