When searching for electrical engineering design project ideas that actually test your grasp of both circuit theory and embedded firmware, skip the blinking LEDs and basic weather stations. Build a True RMS AC Power Analyzer. This project forces you to confront AC phase shifts, ADC non-linearity, discrete sampling math, and mains isolation. It is the ultimate bridge between textbook AC theory and bench-level embedded debugging.

In this guide, we will build a 120V/240V True RMS voltage, current, and power factor meter. We will cover the exact hardware variants, the math behind discrete RMS sampling, and the specific ESP32 ADC quirks that will break your code if you ignore them.

Choosing Your Sensor: Decision Path for AC Measurement

Before ordering parts, you must match your sensor topology to your measurement goals. Average-responding sensors will lie to you when measuring non-linear loads (like LED drivers or PC power supplies). Use this decision table to select the right hardware.

Measurement Goal Load Type Required Topology Concrete Part Pick
DC Power / Efficiency DC Motors, Batteries High-side shunt + I2C ADC INA219 Breakout
AC Apparent Power (VA) Resistive Heaters Current Transformer (CT) SCT-013-000 (100A)
AC True RMS & Power Factor Switch-mode supplies, Motors Active Voltage Module + Hall Effect ZMPT101B + ACS712-30A
Default Recommendation: For a comprehensive electrical engineering design project, we terminate on the third path. The ZMPT101B + ACS712-30A combination allows you to measure both voltage and current waveforms simultaneously, enabling True RMS and Power Factor calculations that a simple CT clamp cannot provide.

Hardware BOM & Spec Sheet

Do not substitute the ESP32 variant. The code below relies on the specific ADC1 channels available on the standard 30-pin DevKit. Pricing reflects typical 2026 market rates for authentic modules.

Component Exact Variant / Model Est. Price Critical Spec / Note
Microcontroller ESP32-DevKitC V4 (ESP32-WROOM-32E) $6.50 Must have ADC1 exposed (GPIO 32-39)
Voltage Sensor ZMPT101B Active AC Voltage Module $4.00 Includes onboard op-amp and trimpot
Current Sensor ACS712-30A Hall Effect Module $3.50 66mV/A sensitivity, 5V VCC required
Calibration 10kΩ Multi-turn Trimpot (Bourns 3296W) $1.50 For fine-tuning ZMPT101B offset if needed

Pin Mapping & AC Theory Integration

The fundamental theory here is discrete sampling. True RMS is not an average; it is the square root of the mean of the squares of the instantaneous values. Mathematically: \( V_{RMS} = \sqrt{\frac{1}{T} \int_0^T v(t)^2 dt} \). In firmware, we approximate this integral by sampling the ADC thousands of times per AC cycle.

CRITICAL ESP32 ADC RULE: You must use ADC1 (GPIO 32, 33, 34, 35, 36, 39). Never use ADC2 (GPIO 0, 2, 4, 12-15, 25-27) for analog reads while WiFi is active. The WiFi driver hijacks ADC2, causing silent read failures. For a deep dive into ESP32 ADC architecture, refer to the official Espressif ADC Oneshot documentation.

Sensor Pin ESP32-DevKitC V4 Pin Function / Theory Note
ZMPT101B OUT GPIO 36 (ADC1_CH0) Input only. No internal pull-up.
ACS712 OUT GPIO 39 (ADC1_CH3) Input only. Measures bidirectional current.
Both VCC 5V (VIN pin on DevKit) Op-amps and Hall sensors need 5V, not 3.3V.
Both GND GND Establish common reference plane.
Safety Warning: The ZMPT101B provides galvanic isolation via its internal transformer, but the primary side connects directly to mains voltage. Terminate your mains wires with proper ferrules and heat shrink. Never touch the primary screw terminals while the circuit is energized. Local electrical codes may require this to be housed in an insulated enclosure.

Complete ESP32 Firmware (Arduino IDE)

This firmware targets the ESP32-DevKitC V4 (ESP32-WROOM-32E). It uses raw ADC sampling to calculate True RMS and Apparent Power without heavy external libraries, giving you full control over the sampling window. It includes a strict timeout handler to prevent the MCU from hanging if the AC waveform disappears.


// Target Board: ESP32-DevKitC V4 (ESP32-WROOM-32E)
// IDE: Arduino IDE 2.x with 'esp32' board package v2.0.14+

#include <Arduino.h>
#include <math.h>

// --- PIN DEFINITIONS ---
#define PIN_V_SENSOR  36  // ADC1_CH0 (ZMPT101B)
#define PIN_I_SENSOR  39  // ADC1_CH3 (ACS712-30A)

// --- CALIBRATION CONSTANTS ---
// Adjust these based on your multimeter readings during calibration
#define V_CAL_FACTOR  0.0985  // Maps ADC raw to Volts
#define I_CAL_FACTOR  0.0441  // Maps ADC raw to Amps (ACS712 30A = 66mV/A)
#define V_OFFSET      2048    // Theoretical mid-point for 12-bit ADC (3.3V ref)
#define I_OFFSET      2048    

// --- SAMPLING PARAMETERS ---
#define SAMPLES_PER_CYCLE 1000
#define TIMEOUT_MS        1000 // Max time to wait for a zero-crossing

void setup() {
  Serial.begin(115200);
  delay(1000);
  
  // Configure ADC1 for 12-bit resolution
  analogReadResolution(12);
  analogSetAttenuation(ADC_11db); // Full 3.3V scale
  
  Serial.println("ESP32 True RMS Power Analyzer Initialized.");
}

void loop() {
  unsigned long startTime = millis();
  
  // 1. Wait for Voltage Zero-Crossing (Rising edge past midpoint)
  int vRaw = analogRead(PIN_V_SENSOR);
  while (vRaw >= V_OFFSET) {
    vRaw = analogRead(PIN_V_SENSOR);
    if (millis() - startTime > TIMEOUT_MS) {
      Serial.println("E: ADC Read Timeout - Zero Crossing Not Detected");
      return; // Abort this loop iteration
    }
  }
  while (vRaw < V_OFFSET) {
    vRaw = analogRead(PIN_V_SENSOR);
    if (millis() - startTime > TIMEOUT_MS) {
      Serial.println("E: ADC Read Timeout - Zero Crossing Not Detected");
      return;
    }
  }
  
  // 2. Sample one full cycle
  unsigned long sampleStart = micros();
  double sumV_sq = 0;
  double sumI_sq = 0;
  double sumP = 0;
  
  for (int i = 0; i < SAMPLES_PER_CYCLE; i++) {
    int rawV = analogRead(PIN_V_SENSOR);
    int rawI = analogRead(PIN_I_SENSOR);
    
    double vInst = (rawV - V_OFFSET) * V_CAL_FACTOR;
    double iInst = (rawI - I_OFFSET) * I_CAL_FACTOR;
    
    sumV_sq += vInst * vInst;
    sumI_sq += iInst * iInst;
    sumP += vInst * iInst;
    
    // Small delay to space samples across the ~16.6ms (60Hz) cycle
    // 16666us / 1000 = ~16us per sample. analogRead takes ~10us.
    delayMicroseconds(4); 
  }
  unsigned long sampleEnd = micros();
  
  // 3. Calculate RMS and Power
  double Vrms = sqrt(sumV_sq / SAMPLES_PER_CYCLE);
  double Irms = sqrt(sumI_sq / SAMPLES_PER_CYCLE);
  double P_real = sumP / SAMPLES_PER_CYCLE;
  double P_apparent = Vrms * Irms;
  double powerFactor = (P_apparent > 0.1) ? (P_real / P_apparent) : 0.0;
  
  // Calculate actual frequency based on sampling time
  double cycleTimeSec = (sampleEnd - sampleStart) / 1000000.0;
  double freq = 1.0 / cycleTimeSec;
  
  // 4. Output Data
  Serial.printf("Vrms: %5.1f V | Irms: %4.2f A | P: %5.1f W | PF: %.2f | Freq: %.1f Hz\n", 
                Vrms, Irms, P_real, powerFactor, freq);
                
  delay(500); // Update rate limit
}

Debugging: Exact Error Strings & First 3 Checks

When working with mains-referenced analog signals, the ESP32 will inevitably hang or output garbage if the hardware isn't biased correctly. If your serial monitor stalls or prints errors, follow this decision path.

The First Three Things to Check

  1. VCC Voltage: Measure the 5V pin on the ESP32 with a multimeter. The ZMPT101B and ACS712 op-amps require a stable 5.0V. If your USB port is sagging to 4.6V, the sensor output will clip, destroying the zero-crossing detection.
  2. DC Offset Bias: Disconnect the AC mains from the ZMPT101B. Read the DC voltage between the ZMPT101B OUT pin and GND. It must read exactly 2.5V (or half of your VCC). If it reads 0V or 5V, the onboard trimpot is misadjusted or the op-amp is dead.
  3. ADC Pin Selection: Verify you are not using GPIO 25, 26, or 27. Those are ADC2 pins. If WiFi initializes (even in the background), ADC2 reads will return 0 or 4095, breaking the math.

Error: "E: ADC Read Timeout - Zero Crossing Not Detected"

This exact string triggers when the firmware waits longer than 1000ms for the AC voltage waveform to cross the 1.65V midpoint. The ESP32's watchdog hasn't tripped, but the math loop is aborted to prevent division-by-zero or infinite hangs.

Ranked Causes:

  • Cause 1 (80%): AC Source is Dead or Disconnected. The ZMPT101B primary side isn't receiving mains voltage. Verify the outlet with a standard digital multimeter. For more on True RMS measurement theory, see this All About Circuits primer on True RMS.
  • Cause 2 (15%): ZMPT101B Trimpot is Zeroed. If the blue trimpot on the ZMPT101B is turned all the way down, the secondary waveform amplitude is 0V, meaning it never crosses the digital midpoint threshold. Turn it counter-clockwise 5 full turns.
  • Cause 3 (5%): V_OFFSET Constant Mismatch. If your ESP32's internal Vref is unusually low (e.g., 3.1V), the theoretical midpoint of 2048 is wrong. Measure the 3.3V pin, calculate the exact midpoint (e.g., 3.1V / 2 = 1.55V -> 1922 ADC raw), and update V_OFFSET in the code.

Error: "PF: 0.00" or Negative Power Factor on Resistive Loads

If you plug in a toaster (purely resistive, PF should be 1.0) and the serial monitor shows a PF of 0.4 or negative, your voltage and current waveforms are out of phase in software.

The Fix: The ACS712 and ZMPT101B have different internal low-pass filter delays. You must add a software phase-shift compensation. In the for loop, sample the current sensor before the voltage sensor, or introduce a 2-sample delay to align the peaks physically.

Extending and Simplifying the Build

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

How to Simplify (For Quick Prototyping)

If you only need to monitor a purely resistive load (like a water heater or incandescent lighting), drop the ZMPT101B entirely. Assume a fixed nominal voltage (e.g., 120V). Use only the ACS712 to measure True RMS current, and multiply by 120 in firmware. This eliminates the dangerous mains-voltage wiring and the zero-crossing detection logic, reducing the code complexity by half.

How to Extend (For Production / High Accuracy)

The ESP32's internal 12-bit SAR ADC is notoriously non-linear at the extremes (near 0 and 4095) and suffers from noise. To elevate this from a bench toy to a reliable engineering tool:

  • Upgrade the ADC: Bypass the internal ADC and use an external ADS1115 (16-bit I2C ADC). It provides differential inputs and a stable internal voltage reference, eliminating VCC sag errors.
  • Add MQTT Telemetry: Integrate the PubSubClient library to push Vrms, Irms, and PF to a local Mosquitto broker every 5 seconds. This allows you to graph power consumption over time using Grafana and InfluxDB.
  • Isolate the I2C Bus: If using an external ADC, place an ISO1540 I2C isolator between the ESP32 and the ADC to maintain strict galvanic isolation between your low-voltage logic and the mains-referenced sensors.

Building a True RMS analyzer forces you to respect the physics of alternating current. By mastering the discrete sampling math and debugging the ESP32's specific ADC quirks, you move beyond copying library code and actually engineer a solution from the silicon up.