Bench supply monitor on an Arduino Uno
An INA219 reads a 12 V supply’s volts, amps, watts and watt-hours on an LCD; an over-current knob trips a shutoff relay that latches, with a buzzer, until RESET (a low-voltage teaching model). The whole build, an Arduino Uno and 14 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.
Advanced About 45 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
- Displays
- Choosing and reading a resistor
- Reading a button
- How an LED lights and why it needs a resistor
- Difficulty: Advanced
- Time: About 45 minutes
- Board: Arduino Uno R3
- Parts: 16
How it works
An INA219 reads a 12 V supply’s volts, amps, watts and watt-hours on an LCD; an over-current knob trips a shutoff relay that latches, with a buzzer, until RESET (a low-voltage teaching model).
Arduino Uno R3 runs the sketch below. The circuit around it: Current monitor, INA219, DC voltage source, Relay module, Resistor, 33 Ω, Slide switch, Resistor, 15 Ω. pinMode sets PIN_RESET as an input with its pull-up on. digitalWrite drives PIN_LED_TRIP LOW. digitalRead reads whether PIN_RESET is HIGH or LOW. analogRead measures the voltage on PIN_LIMIT as a number. Serial.begin opens the serial port at 115200 baud.
Parts list
16 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.
- 1 × Full-size breadboard
- 1 × Current monitor, INA219
- 1 × DC voltage source
- 1 × Relay moduleIn Halloween Prop Kit.
- 1 × Resistor, 33 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × Slide switchIn Logic Lab Kit, Spaceship Panel Kit.
- 1 × Resistor, 15 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × Character LCD with I2C backpackIn Claw Machine Kit, Vending Machine Kit, Rhythm Game Kit.
- 1 × PushbuttonIn Starter Kit, Christmas Lights Kit, Logic Lab Kit.
- 2 × Resistor, 220 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × LED, redIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 1 × LED, greenIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 1 × BuzzerIn Starter Kit, Rocket Flight Computer Kit.
- 1 × PotentiometerIn Halloween Prop Kit, Starter Projects 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 |
|---|---|
| Current monitor, INA219 pin SCL | Arduino Uno R3 pin SCL |
| Current monitor, INA219 pin SDA | Arduino Uno R3 pin SDA |
| DC voltage source pin V- | Arduino Uno R3 pin GND |
| Buzzer pin 1 | Arduino Uno R3 pin 9 |
| Relay module pin IN | Arduino Uno R3 pin 7 |
| Resistor, 220 Ω (2) pin 1 | Arduino Uno R3 pin 6 |
| Resistor, 220 Ω (1) pin 1 | Arduino Uno R3 pin 5 |
| Pushbutton pin 1a | Arduino Uno R3 pin 2 |
| Current monitor, INA219 pin VCC | Arduino Uno R3 pin 5V |
| Relay module pin VCC | Arduino Uno R3 pin 5V |
| Character LCD with I2C backpack pin VCC | Arduino Uno R3 pin 5V |
| Potentiometer pin 3 | Arduino Uno R3 pin 5V |
| Current monitor, INA219 pin GND | Arduino Uno R3 pin GND |
| Relay module pin GND | Arduino Uno R3 pin GND |
| Resistor, 33 Ω pin 1 | Arduino Uno R3 pin GND |
| Resistor, 15 Ω pin 2 | Arduino Uno R3 pin GND |
| Character LCD with I2C backpack pin GND | Arduino Uno R3 pin GND |
| Pushbutton pin 2a | Arduino Uno R3 pin GND |
| LED, red pin C | Arduino Uno R3 pin GND |
| LED, green pin C | Arduino Uno R3 pin GND |
| Buzzer pin 2 | Arduino Uno R3 pin GND |
| Potentiometer pin 1 | Arduino Uno R3 pin GND |
| Potentiometer pin 2 | Arduino Uno R3 pin A0 |
| Character LCD with I2C backpack pin SDA | Arduino Uno R3 pin A4 |
| Character LCD with I2C backpack pin SCL | Arduino Uno R3 pin A5 |
| Relay module pin COM | Current monitor, INA219 pin VIN- |
| DC voltage source pin V+ | Current monitor, INA219 pin VIN+ |
| Resistor, 33 Ω pin 2 | Relay module pin NO |
| Slide switch pin 2 | Relay module pin NO |
| Resistor, 15 Ω pin 1 | Slide switch pin 3 |
| LED, red pin A | Resistor, 220 Ω (1) pin 2 |
| LED, green pin A | Resistor, 220 Ω (2) pin 2 |
The notes on the canvas
Word for word, as it sits beside the circuit in the editor.
Try it
1. Press Run. The output is OFF until you press RESET.
2. Press RESET: the relay closes and the LCD shows 12 V, about 0.36 A, 4.4 W, and the watt-hours climb.
3. Flip the load switch to add the second resistor: the current jumps to about 1.16 A.
4. Turn the limit knob down to 1 A with the heavy load on: after 100 ms the relay opens, the red LED lights and the buzzer beeps, latched until RESET.
5. RESET with the output on clears the watt-hours, min and max.
A teaching model at 12 V. The reverse-polarity and over-voltage notes (a series diode or a P-MOSFET, a TVS, a crowbar) are text in the sketch; this circuit has none of them.
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Bench supply monitor: an INA219 between a bench supply and its load reads
// the volts, amps and watts, adds up the watt-hours, and keeps the min and
// max. A knob sets an over-current limit; if the load draws more than that for
// a short time, a shutoff relay opens and STAYS open (latched), a buzzer
// sounds, and only the RESET button closes it again.
//
// THIS IS A TEACHING MODEL at 12 V with small resistors for a load. It is not
// a protection device. Never put it between a mains-powered supply, a car or
// a large battery and anything you care about.
//
// INA219 VCC 5V GND SDA -> SDA SCL -> SCL (address 0x40)
// VIN+ from the supply's plus, VIN- to the relay's COM
// LCD 16x2 I2C backpack at 0x27 on the same SDA and SCL
// relay IN -> pin 7 (HIGH = energized = load connected)
// COM from the INA219 VIN-, NO to the load; NC is not used
// pin 2 RESET button to GND (the chip's pull-up)
// A0 over-current limit knob, 0.10 to 3.00 A
// pin 6 green LED: output on
// pin 5 red LED: tripped
// pin 9 buzzer
// Every ground joins: the supply's minus, the load, the Uno, the modules.
//
// HOW THE READINGS WORK
// The INA219 module has a 0.1 ohm shunt resistor in series with the load.
// The chip reads the few millivolts across it for the current (10 uV a step
// in the shunt register) and the voltage on VIN- against ground for the bus
// voltage (4 mV a step). With the 32 V, 2 A calibration the library uses, a
// current step is 0.1 mA and the chip tops out near 3.2 A. The sketch
// multiplies volts by amps for the watts and adds watts times the time since
// the last sample into watt-hours.
// The relay sits on the load side of the shunt, so with the output off the
// display still shows the supply voltage and 0 A.
//
// THE LIMIT, THE LATCH AND RESET
// Over the limit for TRIP_MS (100 ms) trips it: a quick inrush spike shorter
// than that is ignored. A trip opens the relay and holds it open until RESET
// is pressed, even if the load goes away by itself. Power-up is the same:
// the output stays OFF until RESET is pressed.
// RESET rules:
// output OFF at power-up RESET turns the output on.
// TRIPPED RESET turns the output back on (the latch
// clears). If the fault is still there it trips
// again after TRIP_MS. Watt-hours and min/max
// keep counting from before.
// output ON RESET clears the watt-hours, min and max.
// Turning the knob below the present load's current while the output is on
// trips it after TRIP_MS like any other overload.
//
// REVERSE POLARITY AND OVER-VOLTAGE (text only: this circuit has neither)
// Reverse polarity: swapping the supply leads would push current backwards
// through the INA219 and everything else. A diode in series with the plus
// lead blocks that but drops 0.7 V (silicon) or about 0.3 V (Schottky) and
// wastes that voltage times the current as heat. A P-channel MOSFET in the
// plus lead does the same job with almost no loss: its gate goes to ground
// and its body diode is the first to conduct with the right polarity,
// after which the gate, well below the source, turns the channel fully
// on, so the drop is only the current times the milliohms of RDS(on). With
// the leads swapped the gate is not below the source, the channel stays off
// and the body diode is reverse biased, so nothing flows.
// Over-voltage: the INA219's bus input is good to 26 V, so a bench supply
// turned up past that damages it. A TVS diode across the input clamps
// spikes, and a crowbar (a thyristor that shorts the rail when it passes a
// set voltage) forces a fuse or the supply's own current limit to act. This
// sketch's relay is a series shutoff, not a crowbar: a crowbar shorts the
// supply, a shutoff disconnects the load.
#include <Wire.h>
#include <Adafruit_INA219.h>
#include <LiquidCrystal_I2C.h>
const int PIN_RESET = 2;
const int PIN_RELAY = 7;
const int PIN_LED_ON = 6;
const int PIN_LED_TRIP = 5;
const int PIN_BUZZER = 9;
const int PIN_LIMIT = A0;
const float MIN_LIMIT_A = 0.10;
const float MAX_LIMIT_A = 3.00;
const unsigned long TRIP_MS = 100;
const unsigned long SAMPLE_MS = 20;
const unsigned long LCD_MS = 250;
const unsigned long PAGE_MS = 3000;
const unsigned long BLANK_MS = 150; // the relay's contact settles before min and max count
enum State { OUTPUT_OFF, OUTPUT_ON, TRIPPED };
const char *const STATE_NAME[] = {"OFF", "ON", "TRIPPED"};
Adafruit_INA219 ina;
LiquidCrystal_I2C lcd(0x27, 16, 2);
State state = OUTPUT_OFF;
float volts = 0, milliamps = 0, limitA = 1.0;
float milliWattHours = 0;
float minV = 0, maxV = 0, minMa = 0, maxMa = 0;
bool haveRange = false;
float peakMa = 0;
bool over = false;
unsigned long overSince = 0;
unsigned long onSince = 0;
unsigned long lastSample = 0, lastLcd = 0, lastStatus = 0;
bool buttonDown = false;
unsigned long buttonChange = 0;
void clearCounters() {
milliWattHours = 0;
haveRange = false;
}
void setOutput(bool on) {
digitalWrite(PIN_RELAY, on ? HIGH : LOW);
digitalWrite(PIN_LED_ON, on ? HIGH : LOW);
}
void pressReset() {
if (state == OUTPUT_ON) {
clearCounters();
onSince = millis();
Serial.println("RESET: watt-hours, min and max cleared");
return;
}
bool wasTripped = state == TRIPPED;
state = OUTPUT_ON;
over = false;
haveRange = wasTripped ? haveRange : false;
noTone(PIN_BUZZER);
digitalWrite(PIN_LED_TRIP, LOW);
setOutput(true);
onSince = millis();
Serial.println(wasTripped ? "RESET: latch cleared, output ON" : "RESET: output ON");
}
void checkButton() {
bool down = digitalRead(PIN_RESET) == LOW;
if (down != buttonDown && millis() - buttonChange >= 30) {
buttonDown = down;
buttonChange = millis();
if (down) pressReset();
}
}
void sample() {
unsigned long now = millis();
unsigned long dt = now - lastSample;
lastSample = now;
volts = ina.getBusVoltage_V();
milliamps = ina.getCurrent_mA();
if (state != OUTPUT_ON) return;
milliWattHours += volts * milliamps * dt / 3600000.0;
if (now - onSince >= BLANK_MS) {
if (!haveRange) {
minV = maxV = volts;
minMa = maxMa = milliamps;
haveRange = true;
}
if (volts < minV) minV = volts;
if (volts > maxV) maxV = volts;
if (milliamps < minMa) minMa = milliamps;
if (milliamps > maxMa) maxMa = milliamps;
}
if (milliamps > limitA * 1000.0) {
if (!over) {
over = true;
overSince = now;
} else if (now - overSince >= TRIP_MS) {
state = TRIPPED;
peakMa = milliamps;
setOutput(false);
digitalWrite(PIN_LED_TRIP, HIGH);
Serial.print("TRIP: ");
Serial.print((long)(milliamps + 0.5));
Serial.print(" mA over the ");
Serial.print((long)(limitA * 1000.0 + 0.5));
Serial.println(" mA limit, output OFF, latched until RESET");
}
} else {
over = false;
}
}
void showLcd() {
char top[17];
char bottom[17];
int v100 = (int)(volts * 100.0 + 0.5);
int mA = (int)(milliamps + (milliamps < 0 ? -0.5 : 0.5));
if (state == OUTPUT_OFF) {
snprintf(top, sizeof top, "OFF %2d.%02dV ", v100 / 100, v100 % 100);
snprintf(bottom, sizeof bottom, "press RESET ");
} else if (state == TRIPPED) {
snprintf(top, sizeof top, "OVER-CURRENT! ");
snprintf(bottom, sizeof bottom, "peak %d.%02dA RESET", (int)peakMa / 1000, ((int)peakMa % 1000) / 10);
} else if ((millis() / PAGE_MS) % 2 == 0) {
int mw = (int)(volts * milliamps / 10.0);
int mwh = (int)milliWattHours;
snprintf(top, sizeof top, "%2d.%02dV %d.%03dA ", v100 / 100, v100 % 100, mA / 1000, mA % 1000);
snprintf(bottom, sizeof bottom, "%d.%02dW %d.%03dWh ", mw / 100, mw % 100, mwh / 1000, mwh % 1000);
} else {
int lo = (int)minMa, hi = (int)maxMa;
int v1 = (int)(minV * 100.0), v2 = (int)(maxV * 100.0);
snprintf(top, sizeof top, "I %d.%03d-%d.%03dA ", lo / 1000, lo % 1000, hi / 1000, hi % 1000);
snprintf(bottom, sizeof bottom, "V %d.%02d-%d.%02dV ", v1 / 100, v1 % 100, v2 / 100, v2 % 100);
}
lcd.setCursor(0, 0);
lcd.print(top);
lcd.setCursor(0, 1);
lcd.print(bottom);
}
void setup() {
Serial.begin(115200);
pinMode(PIN_RESET, INPUT_PULLUP);
pinMode(PIN_RELAY, OUTPUT);
pinMode(PIN_LED_ON, OUTPUT);
pinMode(PIN_LED_TRIP, OUTPUT);
pinMode(PIN_BUZZER, OUTPUT);
setOutput(false);
Serial.println("Mokxi Arduino Uno: bench supply monitor (teaching model, 12 V)");
lcd.init();
lcd.backlight();
if (!ina.begin()) {
Serial.println("FAULT: no INA219 at 0x40, check SDA, SCL and power");
lcd.print("No INA219 0x40");
while (1);
}
Serial.println("output OFF until RESET is pressed");
}
void loop() {
unsigned long now = millis();
checkButton();
limitA = MIN_LIMIT_A + (MAX_LIMIT_A - MIN_LIMIT_A) * analogRead(PIN_LIMIT) / 1023.0;
limitA = (int)(limitA * 100.0 + 0.5) / 100.0;
if (now - lastSample >= SAMPLE_MS) sample();
if (state == TRIPPED) {
// A beep every third of a second.
if ((now / 150) % 2 == 0) tone(PIN_BUZZER, 2400);
else noTone(PIN_BUZZER);
}
if (now - lastLcd >= LCD_MS) {
lastLcd = now;
showLcd();
}
if (now - lastStatus >= 1000) {
lastStatus = now;
Serial.print("V ");
Serial.print(volts, 2);
Serial.print(" A ");
Serial.print(milliamps / 1000.0, 3);
Serial.print(" W ");
Serial.print(volts * milliamps / 1000.0, 2);
Serial.print(" Wh ");
Serial.print(milliWattHours / 1000.0, 3);
Serial.print(" limit ");
Serial.print(limitA, 2);
Serial.print(" ");
Serial.print(STATE_NAME[state]);
if (haveRange && state != OUTPUT_OFF) {
Serial.print(" min ");
Serial.print(minMa / 1000.0, 3);
Serial.print(" max ");
Serial.print(maxMa / 1000.0, 3);
Serial.print(" Vmin ");
Serial.print(minV, 2);
Serial.print(" Vmax ");
Serial.print(maxV, 2);
}
Serial.println();
}
}
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);
pinMode(PIN_RESET, INPUT_PULLUP);
pinMode(PIN_RELAY, OUTPUT);
pinMode(PIN_LED_ON, OUTPUT);
pinMode(PIN_LED_TRIP, OUTPUT);
pinMode(PIN_BUZZER, OUTPUT);
setOutput(false);
Serial.println("Mokxi Arduino Uno: bench supply monitor (teaching model, 12 V)");
lcd.init();
lcd.backlight();
if (!ina.begin()) {
Serial.println("FAULT: no INA219 at 0x40, check SDA, SCL and power");
lcd.print("No INA219 0x40");
while (1);
}
Serial.println("output OFF until RESET is pressed");
}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() {
unsigned long now = millis();
checkButton();
limitA = MIN_LIMIT_A + (MAX_LIMIT_A - MIN_LIMIT_A) * analogRead(PIN_LIMIT) / 1023.0;
limitA = (int)(limitA * 100.0 + 0.5) / 100.0;
if (now - lastSample >= SAMPLE_MS) sample();
if (state == TRIPPED) {
// A beep every third of a second.
if ((now / 150) % 2 == 0) tone(PIN_BUZZER, 2400);
else noTone(PIN_BUZZER);
}
if (now - lastLcd >= LCD_MS) {
lastLcd = now;
showLcd();
}
if (now - lastStatus >= 1000) {
lastStatus = now;
Serial.print("V ");
Serial.print(volts, 2);
Serial.print(" A ");
Serial.print(milliamps / 1000.0, 3);
Serial.print(" W ");
Serial.print(volts * milliamps / 1000.0, 2);
Serial.print(" Wh ");
// ... 18 more lines in the editorTroubleshooting
The usual mistakes with this circuit, and the fix for each.
- 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 circuit sees no voltage or the wrong amount. DC voltage source
Make sure V+ and V- go to the right nodes. The source floats, so tie one side to your circuit ground. Check the set voltage matches what your parts allow. - A part gets too much voltage. DC voltage source
Check the rating of every part before raising the supply. Use series resistors with LEDs, and do not connect the source straight across a short. - The relay does not click, or the board resets when it switches. Relay module
A relay coil needs more current than a pin can give. Use a module with a transistor driver, and power VCC from 5 V. Share ground between the module and the Arduino. - The relay works backwards: on when you expect off. Relay module
Many modules are active LOW: the input must go LOW to turn the relay on. Set the pin HIGH at startup so the load does not switch on during boot. - 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.