Difficulty: Intermediate | Time: 2 Hours | Target Board: ESP32-WROOM-32 DevKit v1 (30-pin variant)

A true project electrical engineering endeavor bridges the gap between abstract circuit theory and embedded firmware. If you want to understand how microcontrollers interact with the physical world, measuring power consumption is the ultimate rite of passage. In this guide, we are building a high-side DC voltage and current logger using an ESP32 and an INA219 shunt monitor.

Unlike low-side sensing, which disrupts the ground reference and can cause erratic behavior in sensitive loads, high-side measurement sits between the power supply and the load. This requires a dedicated differential amplifier to handle the common-mode voltage. The INA219 handles this internally, outputting clean digital data over I2C. By the end of this build, you will have a functional telemetry node and a deep understanding of I2C bus mechanics, shunt saturation, and embedded error handling.

Project Electrical Engineering: Hardware & Spec Sheet

Before stripping wires, verify you have the exact components listed below. Substituting the ESP32 variant or the sensor breakout will change the pin mappings and I2C pull-up requirements.

Component Exact Variant / Model Key Specification Role in Circuit
Microcontroller ESP32-WROOM-32 DevKit v1 (30-pin) Dual-core 240MHz, 3.3V logic Data processing and I2C master
Current Sensor Adafruit INA219 Breakout (PID: 904) 0.1Ω shunt, 12-bit ADC, I2C High-side voltage/current measurement
Wiring (Logic) 22 AWG solid core jumper wires Stranded/Solid, breadboard compatible I2C and 3.3V logic connections
Wiring (Load) 16 AWG silicone stranded wire Rated for ~10A continuous Carrying the measured load current
Pull-up Resistors 4.7kΩ (0.25W, 1% tolerance) For I2C SDA/SCL lines Bus biasing (if breakout lacks them)

Note: The Adafruit INA219 breakout (Product ID 904) includes 10kΩ pull-up resistors on the PCB. If you use a generic, unbranded INA219 module from a bulk marketplace, you must add external 4.7kΩ pull-ups to the 3.3V rail, as the ESP32's internal pull-ups are too weak (~45kΩ) to meet I2C rise-time specifications at 400kHz.

Pin Mapping & Wiring Steps

The ESP32-WROOM-32 DevKit v1 (30-pin) has specific default I2C pins. While the ESP32's GPIO matrix allows you to map I2C to almost any pin, sticking to the hardware defaults reduces firmware overhead and simplifies debugging.

INA219 Breakout Pin ESP32 DevKit v1 Pin Wire Color (Suggested) Function
VIN 3V3 Red Sensor logic power
GND GND Black Common ground reference
SDA GPIO 21 Yellow I2C Data Line
SCL GPIO 22 Blue I2C Clock Line

Load Path Wiring (High-Side):

  1. Connect your DC power supply positive terminal to the INA219 Vin+ screw terminal using 16 AWG wire.
  2. Connect the INA219 Vin- screw terminal to the positive input of your load (e.g., a DC motor or LED strip).
  3. Connect the negative terminal of your load back to the negative terminal of your DC power supply.
  4. Critical: Ensure the ESP32 GND and the DC power supply GND are tied together. Without a common ground, the I2C logic levels will float, resulting in bus lockups.
Bench Tip: When terminating the 16 AWG silicone wire into the INA219 screw terminals, tin the wire ends with a rosin-core flux solder first. Untinned stranded wire will fray under the screw head, increasing contact resistance and introducing measurement errors of up to 50mV at high currents.

Compilable Firmware with Error Handling

The following C++ code is written for the Arduino IDE (ensure you have the Espressif ESP32 board manager installed) and targets the ESP32-WROOM-32 DevKit v1. It requires the Adafruit_INA219 and Adafruit_BusIO libraries via the Library Manager.

Notice the explicit error handling in the setup() function. In a real-world project electrical engineering deployment, firmware must fail gracefully if a sensor is disconnected, rather than hanging in an infinite initialization loop.

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

// Pin definitions for ESP32-WROOM-32 DevKit v1 (30-pin)
#define I2C_SDA_PIN 21
#define I2C_SCL_PIN 22

// Initialize the INA219 object at default I2C address 0x40
Adafruit_INA219 ina219;
bool sensorFault = false;

void setup() {
  Serial.begin(115200);
  // Wait for serial monitor to connect (useful for ESP32 native USB/UART bridges)
  while (!Serial) {
    delay(10);
  }
  Serial.println("\n--- ESP32 INA219 Power Monitor ---");
  Serial.println("Initializing I2C bus...");

  // Explicitly pass the SDA and SCL pins to the Wire library
  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
  Wire.setClock(400000); // Set I2C clock to 400kHz (Fast Mode)

  // Attempt to initialize the sensor with error handling
  if (!ina219.begin(&Wire)) {
    Serial.println("ERROR: Failed to find INA219 chip");
    Serial.println("Halting telemetry. Check wiring and I2C address.");
    sensorFault = true;
  } else {
    // Set calibration to 16V and 400mA range for higher resolution
    ina219.setCalibration_16V_400mA();
    Serial.println("INA219 calibrated and ready.");
    Serial.println("Bus Voltage (V) \t Shunt Voltage (mV) \t Load Voltage (V) \t Current (mA) \t Power (mW)");
  }
}

void loop() {
  // Halt telemetry if the sensor failed to initialize
  if (sensorFault) {
    delay(5000);
    return;
  }

  float shuntvoltage = ina219.getShuntVoltage_mV();
  float busvoltage = ina219.getBusVoltage_V();
  float current_mA = ina219.getCurrent_mA();
  float power_mW = ina219.getPower_mW();
  float loadvoltage = busvoltage + (shuntvoltage / 1000);

  Serial.print(busvoltage, 3); Serial.print("\t\t");
  Serial.print(shuntvoltage, 3); Serial.print("\t\t\t");
  Serial.print(loadvoltage, 3); Serial.print("\t\t");
  Serial.print(current_mA, 2); Serial.print("\t\t");
  Serial.print(power_mW, 2);
  Serial.println();

  // Check for ADC overflow (shunt saturation)
  if (ina219.getOverflow()) {
    Serial.println("WARNING: ADC Overflow! Current exceeds 400mA calibration range.");
  }

  delay(1000); // 1Hz sampling rate
}

Debugging: First Three Things to Check When It Fails

When you open the Serial Monitor and see the exact error string "ERROR: Failed to find INA219 chip", do not immediately rewrite your code. Hardware and electrical interfaces are usually the culprit. Here are the first three things to check, ranked by probability:

  1. Missing Common Ground (Ground Loop): The most common mistake in high-side measurement builds is forgetting to tie the load power supply's ground to the ESP32's ground. The I2C protocol requires both devices to share a 0V reference. If they are floating relative to each other, the ESP32 will read garbage data on the SDA line and fail the initialization handshake. Fix: Run a 22 AWG wire from the load PSU GND to the ESP32 GND pin.
  2. I2C Address Collision or Mismatch: The Adafruit library defaults to I2C address 0x40. If you are using a generic INA219 module, the address pads on the back might be bridged differently, shifting the address to 0x41, 0x44, or 0x45. Fix: Run an I2C Scanner sketch (available in the Arduino IDE examples) to find the actual hex address of the sensor, then pass it to the constructor: Adafruit_INA219 ina219(0x41);
  3. Weak I2C Pull-Up Resistors: The ESP32's internal pull-ups are approximately 45kΩ. The I2C specification requires pull-ups between 2kΩ and 10kΩ for 400kHz operation to ensure the signal rise time is under 300ns. If your breakout board lacks physical resistors, the signal edges will slope, causing the ESP32 to miss clock pulses. Fix: Solder 4.7kΩ resistors between the SDA/SCL lines and the 3.3V rail. For deeper theory on I2C bus capacitance and rise times, refer to the Texas Instruments I2C App Note (SLVA704).

Extending and Simplifying the Build

Every project electrical engineering prototype eventually needs to be scaled up for production or scaled down for a specific use case. Here is how to adapt this build:

How to Simplify (Local Display Node):
If you do not need WiFi telemetry and want a standalone bench tool, swap the ESP32-WROOM-32 for an Arduino Nano v3 (ATmega328P). Replace the Serial Monitor output with an SSD1306 128x64 I2C OLED display. This removes the overhead of the ESP32's RTOS and WiFi stack, reducing the quiescent current draw of the monitor itself from ~80mA down to ~15mA.

How to Extend (IoT Telemetry):
To push this data to a home automation dashboard, integrate the PubSubClient library to publish the loadvoltage and current_mA variables to an MQTT broker (like Mosquitto) over WiFi. From there, Node-RED or Home Assistant can ingest the JSON payloads to graph power consumption over time and trigger automations if a device stalls (current drops to zero while voltage remains high).

FAQ: Project Electrical Engineering Questions

What foundational skills does a project electrical engineering build teach?

A build like this forces you to confront the difference between ideal circuit theory and physical reality. You learn about shunt saturation (what happens when current exceeds the ADC's measurable voltage drop), common-mode rejection (how the INA219 ignores the 12V bus to measure a 30mV drop across the shunt), and I2C bus capacitance. It bridges Ohm's Law calculations with embedded C++ error handling, which is the core competency of any practical electrical engineer.

How do I prevent I2C bus lockups in my project electrical engineering prototype?

I2C lockups usually occur when a master (ESP32) resets while a slave (INA219) is holding the SDA line low during a data transmission. When the ESP32 reboots, it sees SDA low and assumes the bus is busy, halting all communication. To prevent this, implement a software bus-clearing routine in your setup() that manually toggles the SCL pin 9 times as a standard GPIO output before calling Wire.begin(). This forces the slave to release the SDA line. Additionally, keep I2C traces under 30cm to minimize parasitic capacitance.

What is the maximum current I can measure in this project electrical engineering circuit?

The Adafruit INA219 breakout features a 0.1Ω shunt resistor. The INA219's internal ADC can measure a maximum shunt voltage of 320mV. Using Ohm's Law ($I = V / R$), the absolute maximum continuous current is $0.320V / 0.1\Omega = 3.2A$. However, the 0.1Ω resistor on the breakout board is rated for 1% tolerance and will begin to drift thermally above 1A. For continuous loads above 1A, you should desolder the 0.1Ω shunt and replace it with a 0.01Ω 2W Kelvin-connection shunt, then recalibrate the firmware multiplier accordingly. For the official Adafruit INA219 documentation, stick to the 3.2A hard limit and 1A practical limit.