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.
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.
Parts list
12 parts, plus the jumper wires. They retail for about $41; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 2 × Half breadboard
- 3 × PotentiometerIn Halloween Prop Kit, Starter Projects Kit.
- 2 × PushbuttonIn Starter Kit, Christmas Lights Kit, Logic Lab Kit.
- 1 × Current monitor, INA219
- 1 × Resistor, 16.5 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × Resistor, 150 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × Character LCD with I2C backpackIn Claw Machine Kit, Vending Machine Kit, Rhythm Game Kit.
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 pin | Connects to |
|---|---|
| Pushbutton (2) pin 1a | Arduino Uno R3 pin 3 |
| Pushbutton (1) pin 1a | Arduino Uno R3 pin 2 |
| Potentiometer (1) pin 3 | Arduino Uno R3 pin 5V |
| Potentiometer (2) pin 3 | Arduino Uno R3 pin 5V |
| Current monitor, INA219 pin VCC | Arduino Uno R3 pin 5V |
| Current monitor, INA219 pin VIN+ | Arduino Uno R3 pin 5V |
| Character LCD with I2C backpack pin VCC | Arduino Uno R3 pin 5V |
| Potentiometer (1) pin 1 | Arduino Uno R3 pin GND |
| Potentiometer (2) pin 1 | Arduino Uno R3 pin GND |
| Pushbutton (1) pin 2a | Arduino Uno R3 pin GND |
| Pushbutton (2) pin 2a | Arduino Uno R3 pin GND |
| Current monitor, INA219 pin GND | Arduino Uno R3 pin GND |
| Potentiometer (3) pin 2 | Arduino Uno R3 pin GND |
| Potentiometer (3) pin 3 | Arduino Uno R3 pin GND |
| Character LCD with I2C backpack pin GND | Arduino Uno R3 pin GND |
| Potentiometer (1) pin 2 | Arduino Uno R3 pin A0 |
| Potentiometer (2) pin 2 | Arduino Uno R3 pin A1 |
| Current monitor, INA219 pin VIN- | Arduino Uno R3 pin A2 |
| Resistor, 16.5 Ω pin 1 | Arduino Uno R3 pin A2 |
| Resistor, 16.5 Ω pin 2 | Arduino Uno R3 pin A3 |
| Resistor, 150 Ω pin 1 | Arduino Uno R3 pin A3 |
| Current monitor, INA219 pin SDA | Arduino Uno R3 pin A4 |
| Character LCD with I2C backpack pin SDA | Arduino Uno R3 pin A4 |
| Current monitor, INA219 pin SCL | Arduino Uno R3 pin A5 |
| Character LCD with I2C backpack pin SCL | Arduino Uno R3 pin A5 |
| Potentiometer (3) pin 1 | Resistor, 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.
// 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.