🔗Goal
Build an energy monitor that measures voltage, current, and power consumption of any DC circuit using the INA219 sensor. The ESP32 reads the measurements, displays them on an OLED screen, and outputs CSV-formatted data over Serial for logging. This is useful for monitoring battery drain, solar panel output, or the power draw of any device.
Here is how the system works at a high level:
graph LR
A[Power Source] -->|V+| B[INA219]
B -->|V-| C[Load / Device]
B -->|I2C| D[ESP32]
D -->|I2C| E[OLED Display]
D -->|USB| F[Serial Monitor / CSV Log]The INA219 is a high-side current and power monitor with an I2C interface. It sits in series between the power source and the load, measuring both the voltage across the load and the current flowing through its internal shunt resistor. From these two values, it calculates power:
$$P = V \cdot I$$
where $P$ is power in watts, $V$ is voltage in volts, and $I$ is current in amperes.
Over time, you can also calculate total energy consumed:
$$E = P \cdot t$$
where $E$ is energy in watt-hours and $t$ is time in hours. Our code tracks this as milliwatt-hours ($\text{mWh}$) for smaller loads.
🔗Prerequisites
You will need the following components:
| Component | Qty | Notes | Buy |
|---|---|---|---|
| ESP32 dev board | 1 | AliExpress | Amazon.de .co.uk .com | |
| INA219 current sensor module | 1 | Breakout board with I2C interface and onboard shunt resistor | AliExpress | Amazon.de .co.uk .com |
| SSD1306 OLED display (0.96") | 1 | Optional but recommended | AliExpress | Amazon.de .co.uk .com |
| Breadboard | 1 | AliExpress | Amazon.de .co.uk .com | |
| Jumper wires | ~10 | Male-to-male and male-to-female | AliExpress | Amazon.de .co.uk .com |
Links marked Amazon/AliExpress are affiliate links. We may earn a small commission at no extra cost to you.
The INA219 module has a maximum bus voltage of $26\,\text{V}$ and can measure up to $3.2\,\text{A}$ of current (with the default $0.1\,\Omega$ shunt resistor). Do not exceed these limits.
Arduino IDE libraries (install via Library Manager):
| Library | Author | Purpose |
|---|---|---|
| Adafruit INA219 | Adafruit | Driver for the INA219 sensor |
| Adafruit SSD1306 | Adafruit | OLED display driver |
| Adafruit GFX Library | Adafruit | Graphics primitives (dependency) |
| Adafruit BusIO | Adafruit | I2C abstraction (dependency) |
🔗Tutorial
🔗Step 1: Understanding the INA219 wiring
The INA219 measures current by sensing the voltage drop across a small shunt resistor. It must be wired in series with the load — the current you want to measure flows through the sensor.
graph LR
A[Power Source +] --> B[INA219 VIN+]
B --> C[INA219 VIN-]
C --> D[Load +]
D --> E[Load -]
E --> F[Power Source -]The key concept: VIN+ connects to the power source's positive terminal, and VIN- connects to the load's positive terminal. Current flows from VIN+ through the shunt resistor to VIN-. The INA219 also measures the bus voltage at VIN- relative to GND.
🔗Step 2: Wiring
| Component | Pin | ESP32 Pin | Notes |
|---|---|---|---|
| INA219 | VCC | 3.3V | Powers the sensor module |
| INA219 | GND | GND | Shared ground with ESP32 and the monitored circuit |
| INA219 | SDA | GPIO 21 | I2C data (shared bus with OLED) |
| INA219 | SCL | GPIO 22 | I2C clock (shared bus with OLED) |
| INA219 | VIN+ | Power source (+) | Positive terminal of the power source |
| INA219 | VIN- | Load (+) | Positive terminal of the load |
| OLED | VCC | 3.3V | |
| OLED | GND | GND | |
| OLED | SDA | GPIO 21 | I2C data (shared bus with INA219) |
| OLED | SCL | GPIO 22 | I2C clock (shared bus with INA219) |
The INA219 (default address 0x40) and the SSD1306 OLED (default address 0x3C) both use I2C, and since they have different addresses, they can share the same SDA and SCL lines without conflict.
Pin labels and GPIO numbers vary between ESP32 boards. Always check your board's pinout diagram. GPIO 21 and GPIO 22 are the default I2C pins on most ESP32-WROOM-32 DevKits.
Important: The GND of the monitored circuit must be connected to the ESP32 GND for the INA219 to measure correctly. If you are monitoring a separate battery-powered circuit, connect the battery's negative terminal to the ESP32 GND.
🔗Step 3: Upload the code
This sketch reads voltage, current, and power from the INA219 every second, displays the values on the OLED, tracks cumulative energy consumption, and outputs CSV data over Serial for logging.
#include <Wire.h>
#include <Adafruit_INA219.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// ---- OLED configuration ----
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// ---- Sensor ----
Adafruit_INA219 ina219;
// ---- Energy tracking ----
float totalEnergy_mWh = 0.0; // Cumulative energy in milliwatt-hours
unsigned long lastReadTime = 0;
const unsigned long READ_INTERVAL = 1000; // Read every 1 second
// ---- CSV header flag ----
bool csvHeaderPrinted = false;
void displayReadings(float busVoltage, float current_mA, float power_mW, float energy_mWh) {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
// Title
display.setTextSize(1);
display.setCursor(20, 0);
display.print("Energy Monitor");
display.drawLine(0, 10, 127, 10, SSD1306_WHITE);
// Voltage
display.setCursor(0, 14);
display.print("Voltage: ");
display.print(busVoltage, 2);
display.print(" V");
// Current
display.setCursor(0, 26);
display.print("Current: ");
display.print(current_mA, 1);
display.print(" mA");
// Power
display.setCursor(0, 38);
display.print("Power: ");
display.print(power_mW, 1);
display.print(" mW");
// Cumulative energy
display.setCursor(0, 50);
display.print("Energy: ");
display.print(energy_mWh, 2);
display.print(" mWh");
display.display();
}
void setup() {
Serial.begin(115200);
delay(100);
// Initialize I2C
Wire.begin();
// Initialize INA219
if (!ina219.begin()) {
Serial.println("ERROR: INA219 not found. Check wiring.");
while (1) { delay(1000); }
}
Serial.println("INA219 initialized.");
// Optional: set calibration for higher precision on smaller currents.
// Uncomment ONE of these lines if needed:
// ina219.setCalibration_32V_1A(); // Max 32V, 1A — higher precision for small loads
// ina219.setCalibration_16V_400mA(); // Max 16V, 400mA — highest precision
// Initialize OLED
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("WARNING: SSD1306 OLED not found. Continuing with Serial only.");
} else {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(20, 28);
display.print("Starting...");
display.display();
}
lastReadTime = millis();
Serial.println("Energy Monitor ready.");
Serial.println();
}
void loop() {
if (millis() - lastReadTime >= READ_INTERVAL) {
unsigned long now = millis();
float elapsed_h = (now - lastReadTime) / 3600000.0; // Convert ms to hours
lastReadTime = now;
// Read sensor values
float busVoltage = ina219.getBusVoltage_V(); // Voltage in V
float shuntVoltage = ina219.getShuntVoltage_mV(); // Shunt voltage in mV
float current_mA = ina219.getCurrent_mA(); // Current in mA
float power_mW = ina219.getPower_mW(); // Power in mW
// Accumulate energy (power * time)
totalEnergy_mWh += power_mW * elapsed_h;
// The load voltage is the bus voltage plus the shunt voltage drop
float loadVoltage = busVoltage + (shuntVoltage / 1000.0);
// Print CSV header once
if (!csvHeaderPrinted) {
Serial.println("timestamp_ms,bus_voltage_V,shunt_voltage_mV,current_mA,power_mW,energy_mWh");
csvHeaderPrinted = true;
}
// Print CSV data line
Serial.print(now);
Serial.print(",");
Serial.print(busVoltage, 3);
Serial.print(",");
Serial.print(shuntVoltage, 3);
Serial.print(",");
Serial.print(current_mA, 2);
Serial.print(",");
Serial.print(power_mW, 2);
Serial.print(",");
Serial.println(totalEnergy_mWh, 4);
// Update OLED display
displayReadings(busVoltage, current_mA, power_mW, totalEnergy_mWh);
}
}🔗Step 4: Viewing and logging data
Serial Monitor:
Open the Serial Monitor at 115200 baud. You will see CSV-formatted output:
timestamp_ms,bus_voltage_V,shunt_voltage_mV,current_mA,power_mW,energy_mWh
1023,5.012,4.210,42.10,211.05,0.0586
2024,5.008,4.190,41.90,209.83,0.1169
3025,5.010,4.200,42.00,210.42,0.1754Saving to a file for analysis:
To log the data to a CSV file on your computer, you can use the Arduino IDE's Serial Monitor (copy and paste), or use a terminal tool like PuTTY, screen, or minicom with logging enabled. For example, on Linux or macOS:
# Log serial output to a file (replace /dev/ttyUSB0 with your port)
cat /dev/ttyUSB0 > energy_log.csvYou can then open the CSV in a spreadsheet application or plot it with Python/matplotlib to visualize voltage, current, and power over time.
🔗Step 5: Understanding the measurements
The INA219 provides four values:
| Measurement | Unit | Description |
|---|---|---|
| Bus voltage | V | Voltage at the VIN- pin (load side) |
| Shunt voltage | mV | Voltage drop across the shunt resistor |
| Current | mA | Current flowing through the shunt, calculated as $I = \frac{V_{shunt}}{R_{shunt}}$ |
| Power | mW | Power consumed by the load, calculated as $P = V_{bus} \cdot I$ |
The shunt resistor on most INA219 breakout boards is $R_{shunt} = 0.1\,\Omega$. The current is derived from the voltage drop across it using Ohm's law:
$$I = \frac{V_{shunt}}{R_{shunt}} = \frac{V_{shunt}}{0.1}\,\text{A}$$
For example, if the shunt voltage reads $4.2\,\text{mV}$:
$$I = \frac{0.0042}{0.1} = 0.042\,\text{A} = 42\,\text{mA}$$
The cumulative energy consumed is calculated by integrating power over time. Since we sample at discrete intervals, we approximate:
$$E_{total} = \sum_{i} P_i \cdot \Delta t_i$$
where $P_i$ is the power reading at sample $i$ and $\Delta t_i$ is the time since the last sample.
🔗Step 6: Choosing the right calibration mode
The INA219 library offers three calibration presets that trade off voltage/current range for measurement precision:
| Calibration Mode | Max Voltage | Max Current | Best For |
|---|---|---|---|
| Default (32V_2A) | 32 V | 3.2 A | General purpose |
| 32V_1A | 32 V | 1 A | Medium loads, better precision |
| 16V_400mA | 16 V | 400 mA | Small loads like sensors and LEDs |
If your load draws less than $400\,\text{mA}$ and runs at less than $16\,\text{V}$, uncomment the setCalibration_16V_400mA() line in the code for the most precise readings. For most hobby projects, the default mode works well.
🔗Common Issues and Solutions
| Problem | Cause | Fix |
|---|---|---|
| "INA219 not found" error on boot | Wiring issue or wrong I2C address | Check SDA→GPIO 21, SCL→GPIO 22. Verify power to the module. Run an I2C scanner to confirm the address (default 0x40) |
| Current reads 0 mA | Load is not connected through VIN+/VIN-, or load is off | Ensure current flows from VIN+ to VIN-. Check that the load is powered on |
| Voltage reads 0 V | GND not shared between the monitored circuit and ESP32 | Connect the negative terminal of the monitored power source to ESP32 GND |
| Readings are negative | VIN+ and VIN- swapped | Swap the VIN+ and VIN- connections so current flows in the correct direction |
| Current reading seems inaccurate | Wrong calibration mode for your load range | Use setCalibration_16V_400mA() for small loads or setCalibration_32V_1A() for medium loads |
| OLED does not turn on | Wrong I2C address or OLED not powered | Try address 0x3D instead of 0x3C. Check 3.3V and GND connections |
| Energy (mWh) value resets to 0 | ESP32 rebooted | The energy counter is stored in RAM and resets on power loss. For persistent logging, save data to an SD card or send it over WiFi |
| Serial CSV output looks garbled | Wrong baud rate in Serial Monitor | Set the Serial Monitor baud rate to 115200 |
| INA219 reports maximum current (3.2 A) constantly | Current exceeds sensor range or shunt resistor damaged | Check that your load does not exceed the sensor's maximum rated current. Inspect the shunt resistor on the module |