If you want to bridge the gap between high-voltage AC theory and low-voltage embedded DSP, building a true RMS and power factor meter is where it’s at. While many cool electrical engineering projects stop at blinking LEDs or reading basic DC sensors, measuring AC phase angles forces you to deal with real-world signal conditioning, discrete integration, and interrupt-driven sampling. In this guide, we are building a benchtop AC Power Factor Meter targeting the ESP32-DevKitC V4 (ESP32-WROOM-32U variant). You will measure Voltage (RMS), Current (RMS), Real Power (Watts), Apparent Power (VA), and the Power Factor (PF) of any AC load up to 100A.

Project Spec Sheet & Parts List

To get accurate zero-crossing detection and RMS calculations, your analog front-end must be stable. Do not substitute the ZMPT101B with a simple resistor divider; the isolation and op-amp buffering are mandatory for safety and signal integrity.

Component Exact Variant / Model Purpose Est. Cost (2026)
Microcontroller ESP32-DevKitC V4 (WROOM-32U, 38-pin) Dual-core DSP, 12-bit ADC, WiFi $6.50
Voltage Sensor ZMPT101B Module (with LM393 comparator) Isolated AC step-down & biasing $3.20
Current Sensor SCT-013-000 (100A / 50mA output) Split-core non-invasive CT $8.00
Burden Resistor 33Ω 1/4W Metal Film (1% tolerance) Converts CT current to voltage $0.10
DC Bias Network 2x 10kΩ resistors + 10µF electrolytic cap Biases AC signal to 1.65V for ESP32 ADC $0.25
Display SSD1306 128x64 I2C OLED (0x3C address) Real-time telemetry readout $4.50

Pin Mapping & Mains Safety Wiring

⚠️ HIGH VOLTAGE WARNING: This project interfaces with mains AC (120V/240V). De-energize the circuit, lock out the breaker, and verify dead with a CAT III rated multimeter before clipping the SCT-013 onto the live wire or wiring the ZMPT101B primary terminals. The ZMPT101B provides galvanic isolation, but a wiring fault on the primary side can be lethal. NEC-style guidance dictates that all mains connections must be housed in a rated junction box; never leave bare mains terminals exposed on a breadboard.
Sensor / Module Sensor Pin ESP32-WROOM-32U Pin Notes
ZMPT101B VCC 5V (VIN) Requires 5V for internal op-amp headroom
ZMPT101B GND GND Common ground with ESP32
ZMPT101B AO (Analog Out) GPIO 34 (ADC1_CH6) Input only pin, no internal pull-up
SCT-013-000 Tip (via 33Ω burden) GPIO 35 (ADC1_CH7) Must be biased to 1.65V via 10k/10k divider
SCT-013-000 Sleeve GND Connect to the bottom of the bias divider
SSD1306 OLED SDA GPIO 21 I2C Data (add 4.7k pull-up if missing on module)
SSD1306 OLED SCL GPIO 22 I2C Clock

Complete ESP32 Power Factor Code

The code below targets the Arduino framework for the ESP32. It uses a discrete integration method over 10 AC cycles (166.6ms for 60Hz) to calculate True RMS and Real Power. Apparent power is simply V_rms * I_rms, and Power Factor is Real Power / Apparent Power. For a deeper dive into the math behind this sampling, refer to the OpenEnergyMonitor CT/AC Theory documentation.

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

// --- PIN DEFINITIONS ---
#define PIN_VOLTAGE 34
#define PIN_CURRENT 35
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C

// --- CALIBRATION CONSTANTS ---
// Adjust these based on your multimeter readings
const float V_CAL = 138.5;   // Maps ADC raw to Volts
const float I_CAL = 28.4;    // Maps ADC raw to Amps
const float PHASE_CAL = 1.7; // Phase shift compensation (microseconds)

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

unsigned long lastCalcTime = 0;
const int SAMPLES_PER_CYCLE = 100;
const int NUM_CYCLES = 10; // 10 cycles at 60Hz = 166.6ms

void setup() {
  Serial.begin(115200);
  analogReadResolution(12); // ESP32 native 12-bit ADC
  
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Halt execution
  }
  
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(1);
  display.setCursor(0,0);
  display.println("AC PF Meter Ready");
  display.display();
  delay(1000);
}

void loop() {
  unsigned long startMicros = micros();
  
  double sumV_sq = 0, sumI_sq = 0, sumP = 0;
  int lastV = 0, lastI = 0;
  
  // Sample over 10 full AC cycles
  for(int i = 0; i < SAMPLES_PER_CYCLE * NUM_CYCLES; i++) {
    int rawV = analogRead(PIN_VOLTAGE);
    int rawI = analogRead(PIN_CURRENT);
    
    // Remove DC bias (assuming 1.65V bias = 2048 on 12-bit scale)
    int offsetV = rawV - 2048;
    int offsetI = rawI - 2048;
    
    // Phase shift compensation (simple linear extrapolation)
    int shiftedI = offsetI + PHASE_CAL * (offsetI - lastI);
    
    sumV_sq += (double)offsetV * offsetV;
    sumI_sq += (double)shiftedI * shiftedI;
    sumP += (double)offsetV * shiftedI;
    
    lastV = offsetV;
    lastI = offsetI;
    
    // Delay to space samples evenly across the 166.6ms window
    delayMicroseconds(15); 
  }
  
  unsigned long endMicros = micros();
  
  // Calculate RMS and Power
  double V_rms = V_CAL * sqrt(sumV_sq / (SAMPLES_PER_CYCLE * NUM_CYCLES));
  double I_rms = I_CAL * sqrt(sumI_sq / (SAMPLES_PER_CYCLE * NUM_CYCLES));
  double RealPower = V_CAL * I_CAL * (sumP / (SAMPLES_PER_CYCLE * NUM_CYCLES));
  double ApparentPower = V_rms * I_rms;
  
  double PowerFactor = 0.0;
  if(ApparentPower > 5.0) { // Avoid division by zero / noise at no load
    PowerFactor = abs(RealPower / ApparentPower);
  }
  
  // Update Display
  display.clearDisplay();
  display.setCursor(0,0);
  display.printf("V: %5.1f V\n", V_rms);
  display.printf("I: %5.2f A\n", I_rms);
  display.printf("P: %5.1f W\n", RealPower);
  display.printf("S: %5.1f VA\n", ApparentPower);
  display.printf("PF: %5.3f\n", PowerFactor);
  display.display();
  
  // Serial output for logging
  Serial.printf("V:%.1f I:%.2f W:%.1f PF:%.3f\n", V_rms, I_rms, RealPower, PowerFactor);
}

Debugging: First Three Checks & Common Errors

When working with high-impedance analog sensors and the ESP32’s notoriously noisy ADC, things will go wrong. If your serial monitor spits out garbage or the screen stays black, run through these first three checks:

  1. Verify the DC Bias Voltage: Unplug the SCT-013 and ZMPT101B signal wires. Use your multimeter to measure the voltage at the ESP32 GPIO 35 and GPIO 34 pins. You must read exactly 1.65V (half of 3.3V). If it reads 0V or 3.3V, your 10kΩ voltage divider is wired backward or the op-amp on the ZMPT101B isn't getting 5V on its VCC pin.
  2. Check I2C Pull-up Resistors: Many cheap SSD1306 modules lack the required 4.7kΩ pull-up resistors on the SDA/SCL lines. The ESP32’s internal pull-ups are too weak for reliable I2C at 400kHz.
  3. Isolate the Ground Loop: If your current readings fluctuate wildly when a compressor kicks on, your sensor ground is sharing a return path with a noisy digital load. Star-ground your analog sensors directly to the ESP32 GND pin, not through a breadboard power rail.
Exact Error String: [E][Wire.cpp:499] requestFrom(): i2cWriteReadNonStop returned Error 263 (ESP_ERR_TIMEOUT)
Ranked Causes:
1. Missing or blown I2C pull-up resistors on the OLED module.
2. SDA/SCL jumper wire has an internal break (very common with cheap dupont cables).
3. The OLED module is actually addressed at 0x3D instead of 0x3C. Run an I2C scanner sketch to verify.

Extending and Simplifying the Build

Depending on your bench space and end goal, you can easily modify this architecture:

  • To Simplify: Drop the SSD1306 OLED entirely. Remove the Wire.h and Adafruit includes, delete the display update block in the loop, and rely purely on the Serial Plotter. This frees up CPU cycles and eliminates I2C bus lockups, allowing you to increase the sampling rate for higher resolution zero-crossing detection.
  • To Extend (IoT Integration): Add the PubSubClient library to push the Real Power and PF data to an MQTT broker (like Mosquitto or Home Assistant) every 5 seconds. This turns the project into a whole-home energy monitor. Just ensure you use the ESP32's core 0 for WiFi tasks and core 1 for ADC sampling to prevent the FreeRTOS Watchdog from triggering during WiFi transmission delays.
  • To Extend (Power Factor Correction): Wire a 5V relay module to GPIO 26. If the PF drops below 0.85 (indicating a heavy inductive load like an unwound motor), trigger the relay to switch in a parallel run capacitor bank, actively correcting the phase angle in real-time.

FAQ: Cool Electrical Engineering Projects

What are some cool electrical engineering projects for beginners that teach AC theory?

Before tackling true RMS power meters, beginners should start with an AC zero-crossing detector using an H11AA1 optocoupler. It safely steps down mains voltage and outputs a clean 3.3V logic pulse every time the AC sine wave crosses 0V. This teaches the fundamental concept of phase timing and isolation without requiring complex analog biasing or high-speed ADC sampling. Once you understand zero-crossing, stepping up to a full power factor meter becomes much more intuitive.

How do cool electrical engineering projects like this handle AC safety and isolation?

Professional and educational projects rely on galvanic isolation to protect the low-voltage microcontroller (and the user) from lethal mains faults. In this build, the ZMPT101B uses a miniature transformer to magnetically couple the voltage signal, meaning there is no direct electrical path between the 120V/240V mains and the ESP32. The SCT-013 current transformer operates on the same principle; it clamps around the insulated wire, never making bare-metal contact with the live conductor. Never use direct-connected resistor dividers for mains voltage measurement on a hobbyist bench.

Can I use an Arduino Uno instead of an ESP32 for these cool electrical engineering projects?

You can, but you will hit severe hardware limitations. The Arduino Uno’s ATmega328P features a 10-bit ADC (compared to the ESP32’s 12-bit), which drastically reduces your current measurement resolution at low loads. More importantly, the Uno only has one core. If you try to write data to an SD card or transmit over WiFi via an ESP-01 shield while simultaneously sampling the AC waveform at 6kHz, the sampling loop will stall, destroying your phase angle calculations and resulting in wildly inaccurate Power Factor readings. The ESP32’s dual-core architecture and native 12-bit SAR ADC make it the undisputed choice for embedded AC DSP.