Energy Monitor Advanced

Monitor voltage, current, and power consumption with the INA219 sensor and ESP32

🔗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:

ComponentQtyNotesBuy
ESP32 dev board1AliExpress | Amazon.de .co.uk .com
INA219 current sensor module1Breakout board with I2C interface and onboard shunt resistorAliExpress | Amazon.de .co.uk .com
SSD1306 OLED display (0.96")1Optional but recommendedAliExpress | Amazon.de .co.uk .com
Breadboard1AliExpress | Amazon.de .co.uk .com
Jumper wires~10Male-to-male and male-to-femaleAliExpress | 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):

LibraryAuthorPurpose
Adafruit INA219AdafruitDriver for the INA219 sensor
Adafruit SSD1306AdafruitOLED display driver
Adafruit GFX LibraryAdafruitGraphics primitives (dependency)
Adafruit BusIOAdafruitI2C 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

ComponentPinESP32 PinNotes
INA219VCC3.3VPowers the sensor module
INA219GNDGNDShared ground with ESP32 and the monitored circuit
INA219SDAGPIO 21I2C data (shared bus with OLED)
INA219SCLGPIO 22I2C clock (shared bus with OLED)
INA219VIN+Power source (+)Positive terminal of the power source
INA219VIN-Load (+)Positive terminal of the load
OLEDVCC3.3V
OLEDGNDGND
OLEDSDAGPIO 21I2C data (shared bus with INA219)
OLEDSCLGPIO 22I2C 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.1754

Saving 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.csv

You 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:

MeasurementUnitDescription
Bus voltageVVoltage at the VIN- pin (load side)
Shunt voltagemVVoltage drop across the shunt resistor
CurrentmACurrent flowing through the shunt, calculated as $I = \frac{V_{shunt}}{R_{shunt}}$
PowermWPower 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 ModeMax VoltageMax CurrentBest For
Default (32V_2A)32 V3.2 AGeneral purpose
32V_1A32 V1 AMedium loads, better precision
16V_400mA16 V400 mASmall 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

ProblemCauseFix
"INA219 not found" error on bootWiring issue or wrong I2C addressCheck SDA→GPIO 21, SCL→GPIO 22. Verify power to the module. Run an I2C scanner to confirm the address (default 0x40)
Current reads 0 mALoad is not connected through VIN+/VIN-, or load is offEnsure current flows from VIN+ to VIN-. Check that the load is powered on
Voltage reads 0 VGND not shared between the monitored circuit and ESP32Connect the negative terminal of the monitored power source to ESP32 GND
Readings are negativeVIN+ and VIN- swappedSwap the VIN+ and VIN- connections so current flows in the correct direction
Current reading seems inaccurateWrong calibration mode for your load rangeUse setCalibration_16V_400mA() for small loads or setCalibration_32V_1A() for medium loads
OLED does not turn onWrong I2C address or OLED not poweredTry address 0x3D instead of 0x3C. Check 3.3V and GND connections
Energy (mWh) value resets to 0ESP32 rebootedThe 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 garbledWrong baud rate in Serial MonitorSet the Serial Monitor baud rate to 115200
INA219 reports maximum current (3.2 A) constantlyCurrent exceeds sensor range or shunt resistor damagedCheck that your load does not exceed the sensor's maximum rated current. Inspect the shunt resistor on the module