Difficulty Rating: Intermediate (Requires basic AC mains awareness and C++ firmware flashing)
Estimated Build Time: 2.5 Hours (Hardware: 45m | Firmware & Debugging: 1.5h)
Target Board: ESP32-DevKitC-V4 (ESP32-WROOM-32)

When searching for electrical engineering projects for students, most tutorials default to simple DC LED blinkers or 5V Arduino sensors. But real-world electrical engineering happens at 120V/230V AC, involving complex impedance, power factor, and isolation requirements. This project bridges that gap. We are building a True RMS AC Power Meter that measures voltage, current, real power, and power factor, displaying it locally on an OLED and outputting structured data via Serial for IoT logging.

More importantly, this guide focuses on the debugging and theory that professors actually test on: understanding why your sensor reads NaN, how UART timing fails on the ESP32, and the physics of apparent versus real power.

Project Spec Sheet & Parts List

To ensure reproducibility, this build relies on exact module variants. Substituting a generic ESP32 clone with an unregulated LDO will cause brownouts when the OLED and UART transmit simultaneously.

Component Exact Variant / Model Why This Variant? Est. Cost
Microcontroller ESP32-DevKitC-V4 (ESP32-WROOM-32) Dual UART hardware support; 520KB SRAM prevents buffer overflows during MQTT logging. $6.00
Power Sensor Peacefair PZEM-004T V3.0 (with 100A CT) V3.0 uses isolated Current Transformer (CT) and internal DSP for True RMS, unlike V1.0 shunt versions. $14.50
Display 0.96" I2C OLED (SSD1306 Driver, 128x64) Low current draw (~15mA) compared to 16x2 LCDs with backlights (~60mA). $4.00
Power Supply 5V 2A USB-C Wall Adapter ESP32 WiFi transmit spikes hit 450mA; 2A headroom prevents brownout resets. $5.00

The Theory: Real vs. Apparent Power and Sensor Isolation

A common mistake in student projects is assuming Power = Voltage × Current. In DC circuits, this is true. In AC circuits with reactive loads (motors, transformers, switching power supplies), it is false.

The Water Analogy for Power Factor: Imagine pulling a sled with a rope at an angle. You are exerting effort (Apparent Power, VA), but only the horizontal component of your pull actually moves the sled forward (Real Power, W). The vertical component just lifts the sled uselessly (Reactive Power, VAR). Power Factor (PF) is the cosine of that angle. A PF of 0.7 means only 70% of the grid's current is doing real work.

The PZEM-004T V3.0 calculates this internally using a dedicated DSP chip that samples the AC waveform at high frequency, computing the phase shift between the voltage zero-crossing and the current transformer's induced waveform. It then outputs the finalized True RMS and PF values over a 9600-baud Modbus-RTU UART link.

Isolation Note: Never use ACS712 Hall-effect sensors for student mains projects. The ACS712 places the microcontroller ground directly in series with the mains voltage path via its internal copper trace. If the trace arcs, your 5V USB cable becomes energized at 120V/230V. The PZEM-004T uses a split-core Current Transformer (CT). The secondary winding is magnetically coupled, providing galvanic isolation between the lethal mains conductor and your 3.3V ESP32 logic.

Wiring & Pin Mapping

The ESP32-WROOM-32 features three hardware UARTs. We will use UART2 (Pins 16 and 17) for the PZEM sensor to avoid conflicting with UART0 (the USB Serial debug port).

⚠️ MAINS VOLTAGE WARNING: De-energize the circuit at the breaker before wiring the PZEM-004T screw terminals. Verify dead with a CAT-III multimeter. The CT clamp is safe to handle, but the blue screw terminals connect directly to the AC Line. Local electrical codes (NEC/IEC) require mains connections to be housed in a grounded, fire-rated junction box. Do not leave this circuit exposed on a breadboard.
PZEM-004T V3.0 Pin ESP32-DevKitC-V4 Pin Notes
VCC (5V)VIN (5V)Do NOT use 3V3; the optocouplers require 4.5V minimum.
GNDGNDCommon ground required for UART reference.
TXGPIO 16 (RX2)Cross-wire: Sensor TX to ESP32 RX.
RXGPIO 17 (TX2)Cross-wire: Sensor RX to ESP32 TX.

OLED I2C Wiring: SDA to GPIO 21, SCL to GPIO 22, VCC to 3V3, GND to GND.

Firmware: Complete ESP32 Code with Error Handling

This firmware targets the ESP32-DevKitC-V4. It utilizes the PZEM004Tv30 library by mandulaj and the Adafruit SSD1306 library. Notice the explicit error handling: if the UART times out, the code catches the NaN (Not a Number) float return and prevents the OLED from crashing.

#include <PZEM004Tv30.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// --- PIN DEFINITIONS ---
#define PZEM_RX_PIN 16
#define PZEM_TX_PIN 17
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C

// --- HARDWARE INITIALIZATION ---
HardwareSerial pzemSerial(2); // Use ESP32 UART2
PZEM004Tv30 pzem(pzemSerial, PZEM_RX_PIN, PZEM_TX_PIN);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup() {
  Serial.begin(115200);
  pzemSerial.begin(9600, SERIAL_8N1, PZEM_RX_PIN, PZEM_TX_PIN);
  
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("[FATAL] SSD1306 allocation failed"));
    for(;;); // Halt execution
  }
  
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(1);
  display.println("Booting PZEM...");
  display.display();
  delay(1000);
}

void loop() {
  float voltage = pzem.voltage();
  float current = pzem.current();
  float power = pzem.power();
  float pf = pzem.pf();

  // --- ERROR HANDLING & TIMEOUT CHECK ---
  if (isnan(voltage) || isnan(current)) {
    Serial.println("[ERROR] PZEM read timeout: NaN received. Check UART wiring.");
    display.clearDisplay();
    display.setCursor(0,0);
    display.println("SENSOR ERROR");
    display.println("UART Timeout");
    display.display();
    delay(2000);
    return;
  }

  // --- SERIAL OUTPUT ---
  Serial.printf("V: %.1f | A: %.3f | W: %.1f | PF: %.2f\n", voltage, current, power, pf);

  // --- OLED RENDERING ---
  display.clearDisplay();
  display.setCursor(0,0);
  display.printf("Voltage: %5.1f V\n", voltage);
  display.printf("Current: %5.3f A\n", current);
  display.printf("Power:   %5.1f W\n", power);
  display.printf("PF:      %5.2f\n", pf);
  display.display();

  delay(1000); // PZEM internal update rate is ~1s
}

Debugging: "NaN" Timeouts and UART Failures

When working with embedded AC sensors, the most common failure mode is a silent timeout. The PZEM004Tv30 library does not throw C++ exceptions; instead, it returns NaN (Not a Number) when the CRC check fails or the 200ms Modbus timeout expires.

Symptom: Your Serial monitor prints [ERROR] PZEM read timeout: NaN received or the raw output shows V: NaN | A: NaN.

The First Three Things to Check:

  1. UART Pin Swap (Most Common): Hardware serial requires crossing the lines. If you wired Sensor TX to ESP32 TX, you will get a timeout. Verify Sensor TX is on GPIO 16 (RX2) and Sensor RX is on GPIO 17 (TX2).
  2. VCC Sag and Brownouts: The PZEM-004T optocouplers draw roughly 40mA during transmission. If you are powering the ESP32 from a weak laptop USB port (limited to 500mA), the simultaneous WiFi stack and sensor polling will drop the 3.3V rail below 2.8V, triggering an ESP32 brownout reset. Check your serial monitor for brownout detector was triggered. Use a dedicated 5V 2A wall adapter.
  3. Baud Rate Mismatch: The V3.0 module is hardcoded to 9600 baud. If you initialized pzemSerial.begin(115200), the CRC checks will fail silently, resulting in NaN. Ensure the code explicitly sets 9600.

For deeper diagnostics on AC measurement theory and Modbus polling, refer to the Electronics Tutorials guide on AC Power or the Espressif ESP32 Datasheet for UART electrical characteristics.

Extending and Simplifying the Build

Depending on your lab requirements, you may need to scale this project up or down.

Simplify (Cost & Time Reduction):
  • Remove the I2C OLED entirely.
  • Strip the Adafruit_SSD1306 library dependencies.
  • Rely solely on the Serial Plotter in the Arduino IDE to graph the Real Power over time. This cuts BOM cost by $4 and eliminates I2C bus lockup debugging.
Extend (Capstone / IoT Level):
  • Add the PubSubClient library to publish JSON payloads to an MQTT broker (e.g., Mosquitto or AWS IoT Core).
  • Implement Coulomb counting in the firmware by integrating the current over time (Ah += current * (dt/3600)) to track total energy consumption (kWh) across power cycles using ESP32 NVS (Non-Volatile Storage).

FAQ: Electrical Engineering Projects for Students

What are the safest electrical engineering projects for students involving mains voltage?

The safest projects utilize galvanic isolation between the high-voltage AC side and the low-voltage DC logic side. Using a split-core Current Transformer (like the one included with the PZEM-004T) or an isolated Hall-effect sensor (like the Allegro ACS715, though less common for DIY) ensures that a component failure on the mains side cannot send 120V/230V into your USB port. Avoid projects that require you to build your own shunt resistors on the AC line unless you are using a fully isolated differential amplifier circuit with a verified isolation rating.

How do I calculate power factor in my student project manually?

If you are using an oscilloscope instead of a digital sensor module, you calculate Power Factor (PF) by measuring the time delay ($\Delta t$) between the zero-crossing of the voltage sine wave and the zero-crossing of the current sine wave. First, find the phase angle in degrees: $\theta = (\Delta t / T) \times 360^\circ$, where $T$ is the period of the AC wave (16.67ms for 60Hz, 20ms for 50Hz). The Power Factor is simply $\cos(\theta)$. The PZEM-004T handles this math internally via its onboard DSP, but understanding the oscilloscope method is a common lab exam requirement.

Why does my ESP32 brownout when switching AC loads via a relay?

When an ESP32 triggers a mechanical relay to switch an inductive AC load (like a motor or transformer), the collapsing magnetic field generates a massive Electromagnetic Interference (EMI) spike. This EMI couples into the ESP32's 3.3V power rail or the EN (Enable) pin, tricking the internal brownout detector into thinking the voltage has dropped, causing an immediate reboot. To fix this, you must add a flyback diode across the relay coil, use an optocoupler to drive the relay, and physically separate the AC load wiring from the ESP32 DC wiring by at least 2 inches.

Can I use the PZEM-004T to measure DC solar panel output?

No. The PZEM-004T relies on the alternating nature of AC voltage to calculate RMS and utilizes a current transformer, which only works with changing magnetic fields (AC). If you pass DC through the CT clamp, it will read 0A, and the voltage measurement circuit is designed for AC sine waves. For student solar projects, look into the INA219 or INA226 I2C shunt-based sensors, which are designed specifically for high-precision DC voltage and current measurement up to 26V or 36V.