Built-in project · Arduino Uno R3

Voltage drop calculator on an Arduino Uno

Pick a wire gauge, a run length and a load current: the Uno computes the round-trip voltage drop, flags 3% and 5% (NEC recommendations, not hard rules) and checks the math on a scaled 5 V stand-in for the wire. The whole build, an Arduino Uno and 9 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Intermediate About 35 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $41. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • Sensors
  • School
  • Reading a knob with an analog pin
  • Reading a button
  • Choosing and reading a resistor
  • Difficulty: Intermediate
  • Time: About 35 minutes
  • Board: Arduino Uno R3
  • Parts: 12

How it works

Pick a wire gauge, a run length and a load current: the Uno computes the round-trip voltage drop, flags 3% and 5% (NEC recommendations, not hard rules) and checks the math on a scaled 5 V stand-in for the wire.

Arduino Uno R3 runs the sketch below. The circuit around it: Potentiometer, Pushbutton, Current monitor, INA219, Resistor, 16.5 Ω, Resistor, 150 Ω, Character LCD with I2C backpack. pinMode sets UP_PIN as an input with its pull-up on. digitalRead reads whether UP_PIN is HIGH or LOW. analogRead measures the voltage on pin as a number. Serial.begin opens the serial port at 115200 baud. millis reads the time since power-up, so the sketch can time things without stopping.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Pushbutton (2) pin 1aArduino Uno R3 pin 3
Pushbutton (1) pin 1aArduino Uno R3 pin 2
Potentiometer (1) pin 3Arduino Uno R3 pin 5V
Potentiometer (2) pin 3Arduino Uno R3 pin 5V
Current monitor, INA219 pin VCCArduino Uno R3 pin 5V
Current monitor, INA219 pin VIN+Arduino Uno R3 pin 5V
Character LCD with I2C backpack pin VCCArduino Uno R3 pin 5V
Potentiometer (1) pin 1Arduino Uno R3 pin GND
Potentiometer (2) pin 1Arduino Uno R3 pin GND
Pushbutton (1) pin 2aArduino Uno R3 pin GND
Pushbutton (2) pin 2aArduino Uno R3 pin GND
Current monitor, INA219 pin GNDArduino Uno R3 pin GND
Potentiometer (3) pin 2Arduino Uno R3 pin GND
Potentiometer (3) pin 3Arduino Uno R3 pin GND
Character LCD with I2C backpack pin GNDArduino Uno R3 pin GND
Potentiometer (1) pin 2Arduino Uno R3 pin A0
Potentiometer (2) pin 2Arduino Uno R3 pin A1
Current monitor, INA219 pin VIN-Arduino Uno R3 pin A2
Resistor, 16.5 Ω pin 1Arduino Uno R3 pin A2
Resistor, 16.5 Ω pin 2Arduino Uno R3 pin A3
Resistor, 150 Ω pin 1Arduino Uno R3 pin A3
Current monitor, INA219 pin SDAArduino Uno R3 pin A4
Character LCD with I2C backpack pin SDAArduino Uno R3 pin A4
Current monitor, INA219 pin SCLArduino Uno R3 pin A5
Character LCD with I2C backpack pin SCLArduino Uno R3 pin A5
Potentiometer (3) pin 1Resistor, 150 Ω pin 2

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// Voltage drop calculator: how much voltage a long copper run loses, with a
// real measurement alongside to check the math. A teaching model with
// low-voltage stand-ins: the 120 V is only a number in the sketch, and the
// real circuit is 5 V on a breadboard. Never size or connect real wiring
// from this; use the code book and a qualified electrician.
//
//   A0      run length pot (10 to 300 ft, one way, in 5 ft steps)
//   A1      load current pot (0 to 30 A, in half amps)
//   pin 2   WIRE UP button to GND: next thicker gauge (14, 12, 10, 8, 6 AWG)
//   pin 3   WIRE DOWN button to GND: next thinner gauge
//   LCD     SDA -> A4    SCL -> A5    VCC -> 5 V    GND -> GND
//   INA219  SDA -> A4    SCL -> A5   (on the same two I2C wires, address 0x40)
//   A2      bench rig, the source end of the stand-in wire
//   A3      bench rig, the load end of the stand-in wire
//
// The math. Current goes out on one conductor and back on the other, so the
// wire is twice the run length:
//   drop = 2 x length x (ohms per 1000 ft / 1000) x current
//   percent = drop / source voltage x 100
// The ohms per 1000 ft are for copper at 75 C: 14 AWG 3.14, 12 AWG 1.98,
// 10 AWG 1.24, 8 AWG 0.778, 6 AWG 0.491. The NEC suggests (informational
// notes, not hard rules) about 3% drop on a branch circuit and 5% for the
// feeder and branch together. This sketch checks voltage drop only: the wire
// also has to carry the current (the usual small-wire limits are 15 A for
// 14 AWG, 20 A for 12 AWG and 30 A for 10 AWG).
// Example: 12 AWG, 100 ft, 15 A on 120 V is 1.98/1000 x 200 x 15 = 5.94 V,
// 4.95%: over 3%, under 5%, and 8 AWG is the smallest wire under 3%.
//
// The bench check. A 16.5 ohm resistor stands in for the 12 AWG, 100 ft run
// (0.396 ohm round trip) scaled so that 24 V of the model is 1 V on the bench
// and 1 A is 1 mA: 0.396 ohm x 1000 / 24 = 16.5 ohm. An INA219 reads the
// bench current, A2 and A3 read the volts at the two ends of the resistor,
// and the sketch prints the drop it computes from the current next to the
// drop it measures. The converter steps 4.9 mV, which is 0.12 V on the
// 120 V scale, so they agree to about that.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Adafruit_INA219.h>

const float SOURCE_VOLTS = 120.0;
const float PERCENT_BRANCH = 3.0;   // branch circuit recommendation
const float PERCENT_TOTAL = 5.0;    // feeder plus branch recommendation
const int GAUGES = 5;
const int AWG[GAUGES] = {14, 12, 10, 8, 6};
const float OHMS_PER_1000FT[GAUGES] = {3.14, 1.98, 1.24, 0.778, 0.491};

const float BENCH_FEET = 100.0;
const int BENCH_GAUGE = 1;          // 12 AWG
const float BENCH_VOLT_SCALE = 24.0;  // 120 V of the model is 5 V on the bench
const float BENCH_AMP_SCALE = 1000.0; // 1 A of the model is 1 mA on the bench

const int LENGTH_PIN = A0;
const int CURRENT_PIN = A1;
const int SOURCE_END_PIN = A2;
const int LOAD_END_PIN = A3;
const int UP_PIN = 2;
const int DOWN_PIN = 3;
const unsigned long REPORT_MS = 500;
const unsigned long DEBOUNCE_MS = 25;

LiquidCrystal_I2C lcd(0x27, 16, 2);
Adafruit_INA219 ina;
int gauge = 1;                      // starts on 12 AWG
bool lastUp = false;
bool lastDown = false;
unsigned long upAt = 0;
unsigned long downAt = 0;
unsigned long lastReport = 0;

// Round-trip drop in volts.
float dropVolts(int g, float feet, float amps) {
  return 2.0 * feet * (OHMS_PER_1000FT[g] / 1000.0) * amps;
}

// The index of the smallest wire whose drop is within the branch limit, or -1.
int smallestUnder(float feet, float amps) {
  for (int g = 0; g < GAUGES; g++) {
    if (dropVolts(g, feet, amps) / SOURCE_VOLTS * 100.0 <= PERCENT_BRANCH) return g;
  }
  return -1;
}

int readAverage(int pin, int count) {
  long sum = 0;
  for (int i = 0; i < count; i++) sum += analogRead(pin);
  return (int)(sum / count);
}

// Pad a field on the LCD to a width after printing n characters.
void pad(int n, int width) {
  while (n < width) {
    lcd.print(' ');
    n++;
  }
}

void showLcd(float feet, float amps, float drop, float percent) {
  lcd.setCursor(0, 0);
  int n = lcd.print("AWG");
  n += lcd.print(AWG[gauge]);
  n += lcd.print(' ');
  if (feet < 100) n += lcd.print(' ');
  n += lcd.print((int)feet);
  n += lcd.print("' ");
  if (amps < 10) n += lcd.print(' ');
  n += lcd.print(amps, 1);
  n += lcd.print('A');
  pad(n, 16);

  lcd.setCursor(0, 1);
  n = lcd.print(drop, drop < 10 ? 2 : 1);
  n += lcd.print("V ");
  n += lcd.print(percent, percent < 10 ? 2 : 1);
  n += lcd.print('%');
  pad(n, 12);
  n = 0;
  if (percent > PERCENT_TOTAL) n = lcd.print(">5%");
  else if (percent > PERCENT_BRANCH) n = lcd.print(">3%");
  else n = lcd.print("OK");
  pad(n, 4);
}

void checkButtons() {
  bool up = digitalRead(UP_PIN) == LOW;
  bool down = digitalRead(DOWN_PIN) == LOW;
  unsigned long now = millis();
  if (up && !lastUp && now - upAt > DEBOUNCE_MS) {
    if (gauge < GAUGES - 1) gauge++;
    upAt = now;
  }
  if (down && !lastDown && now - downAt > DEBOUNCE_MS) {
    if (gauge > 0) gauge--;
    downAt = now;
  }
  lastUp = up;
  lastDown = down;
}

void report() {
  // The inputs. Length: 10 to 300 ft in 5 ft steps. Current: 0 to 30 A in
  // half amps.
  int lengthSteps = (readAverage(LENGTH_PIN, 8) * 58L + 511) / 1023;
  float feet = 10.0 + 5.0 * lengthSteps;
  int halfAmps = (readAverage(CURRENT_PIN, 8) * 60L + 511) / 1023;
  float amps = halfAmps * 0.5;

  float drop = dropVolts(gauge, feet, amps);
  float percent = drop / SOURCE_VOLTS * 100.0;
  showLcd(feet, amps, drop, percent);

  Serial.print("AWG ");
  Serial.print(AWG[gauge]);
  Serial.print(", ");
  Serial.print((int)feet);
  Serial.print(" ft, ");
  Serial.print(amps, 1);
  Serial.print(" A: drop ");
  Serial.print(drop, 2);
  Serial.print(" V = ");
  Serial.print(percent, 2);
  Serial.print("% of 120 V, ");
  if (percent > PERCENT_TOTAL) Serial.print("OVER 5%");
  else if (percent > PERCENT_BRANCH) Serial.print("OVER 3%");
  else Serial.print("OK");
  int best = smallestUnder(feet, amps);
  Serial.print(", smallest wire under 3%: ");
  if (best < 0) {
    Serial.println("none up to 6 AWG");
  } else {
    Serial.print("AWG ");
    Serial.println(AWG[best]);
  }

  // The bench: the INA219's current against the volts across the resistor.
  float benchMa = ina.getCurrent_mA();
  float benchAmps = benchMa / 1000.0 * BENCH_AMP_SCALE;
  float computed = dropVolts(BENCH_GAUGE, BENCH_FEET, benchAmps);
  long sourceSum = 0;
  long loadSum = 0;
  for (int i = 0; i < 32; i++) {
    sourceSum += analogRead(SOURCE_END_PIN);
    loadSum += analogRead(LOAD_END_PIN);
  }
  float benchDrop = (sourceSum - loadSum) / 32.0 * 5.0 / 1023.0;
  float measured = benchDrop * BENCH_VOLT_SCALE;
  Serial.print("bench (12 AWG, 100 ft, scaled): load ");
  Serial.print(benchAmps, 1);
  Serial.print(" A, computed ");
  Serial.print(computed, 2);
  Serial.print(" V, measured ");
  Serial.print(measured, 2);
  Serial.println(" V");
}

void setup() {
  Serial.begin(115200);
  Serial.println("Mokxi Arduino Uno: voltage drop calculator");
  Serial.println("Drop = 2 x length x ohms per 1000 ft / 1000 x amps, copper at 75 C.");
  Serial.println("3% (branch) and 5% (feeder plus branch) are NEC informational-note recommendations, not hard rules.");
  Serial.println("Teaching model with a 5 V bench stand-in: never size real wiring from it.");
  pinMode(UP_PIN, INPUT_PULLUP);
  pinMode(DOWN_PIN, INPUT_PULLUP);
  lcd.init();
  lcd.backlight();
  ina.begin();
}

void loop() {
  checkButtons();
  if (millis() - lastReport >= REPORT_MS) {
    lastReport = millis();
    report();
  }
}

Code walkthrough

The key functions of the sketch, and what each one does.

setup sketch.ino

Runs once when the board starts. It gets the pins and libraries ready.

void setup() {
  Serial.begin(115200);
  Serial.println("Mokxi Arduino Uno: voltage drop calculator");
  Serial.println("Drop = 2 x length x ohms per 1000 ft / 1000 x amps, copper at 75 C.");
  Serial.println("3% (branch) and 5% (feeder plus branch) are NEC informational-note recommendations, not hard rules.");
  Serial.println("Teaching model with a 5 V bench stand-in: never size real wiring from it.");
  pinMode(UP_PIN, INPUT_PULLUP);
  pinMode(DOWN_PIN, INPUT_PULLUP);
  lcd.init();
  lcd.backlight();
  ina.begin();
}

loop sketch.ino

Runs over and over for as long as the board has power. This is where the project does its work.

void loop() {
  checkButtons();
  if (millis() - lastReport >= REPORT_MS) {
    lastReport = millis();
    report();
  }
}

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The reading jumps around or only covers part of the range. Potentiometer
    Wire the two outer legs to 5 V and ground and the middle leg (the wiper) to an analog pin. Using the wrong two legs gives a dead or partial range.
  • Turning the knob does nothing, or the value is stuck at 0 or 1023. Potentiometer
    Check that the wiper goes to an analog input (A0 to A5 on an Uno), not a digital pin. Make sure both ends are connected, since a floating end gives noise.
  • The input reads random HIGH and LOW when the button is not pressed. Pushbutton
    A floating pin picks up noise. Add a 10 k pull-down or pull-up resistor, or use INPUT_PULLUP and wire the button between the pin and GND. Then pressed reads LOW.
  • The button seems always pressed, or never changes. Pushbutton
    A 4-leg tactile switch has legs joined in pairs. Place it across the breadboard center gap so the pairs sit on different rows. Wired along one side, the legs are already connected.
  • The current reads zero or negative. Current monitor
    Put the shunt in series with the load, not across the supply. The load current must flow from VIN+ to VIN-. Swap them if the sign is reversed.
  • The reading is off by a scale factor, or the part is not found. Current monitor
    The address is 0x40 by default (A0 and A1 change it). Set the calibration for your shunt with the library. Keep bus voltage under 26 V.
  • The LED is dim, burns out, or the pin reads wrong because the resistor value is off. Resistor
    Read the color bands again. A 220 ohm to 330 ohm resistor suits a typical LED on 5 V. 10k is a common pull-up or pull-down value. Check with a multimeter if unsure.
  • A pull-up or pull-down resistor is missing, so the input floats and reads random values. Resistor
    Tie the input to 5 V or ground through about 10k. Or use INPUT_PULLUP and wire the switch to ground.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.