Project Overview & Difficulty Rating
Measuring alternating current (AC) accurately requires more than just reading a peak voltage and dividing by two. To truly understand AC power systems, you need to measure True Root Mean Square (RMS) voltage, True RMS current, and the phase angle between them to calculate Power Factor (PF). This electronics project bridges foundational AC circuit theory with high-speed embedded Analog-to-Digital Converter (ADC) sampling.
By building this dual-channel AC datalogger, you will move beyond average-responding multimeters and learn how digital signal processing (DSP) captures the actual heating potential of non-linear loads like LED drivers and switching power supplies.
Project Spec Card
- Difficulty: Intermediate (Requires mains voltage awareness and C++ pointer math)
- Time to Build: 3-4 hours (including calibration)
- Estimated Cost: $22 - $30 USD
- Target Board Variant: ESP32-DevKitC V4 featuring the ESP32-WROOM-32U module (U.FL antenna variant for stable Wi-Fi if extending later)
Component Spec Sheet & Pin Mapping
Before wiring, verify you have the exact component variants listed below. Using a 50A/1V SCT-013 instead of the 100A/50mA version will completely change the burden resistor math and saturate the ESP32 ADC.
| Component | Exact Variant / Value | Tolerance / Spec | Est. Price |
|---|---|---|---|
| Microcontroller | ESP32-DevKitC V4 (WROOM-32U) | 12-bit ADC, 240MHz Dual Core | $7.50 |
| Current Sensor | SCT-013-000 (100A / 50mA output) | ±3% linearity, 1800:1 turns | $6.00 |
| Voltage Sensor | ZMPT101B AC Voltage Module | 0-250VAC, onboard op-amp bias | $3.50 |
| Burden Resistor | 22Ω Metal Film (External) | 1% tolerance, 0.5W minimum | $0.10 |
| DC Bias Network | 2x 10kΩ, 1x 10μF Ceramic | Creates 1.65V virtual ground | $0.20 |
ESP32 Pin Mapping Table
We strictly use ADC1 pins. The ESP32's ADC2 pins are shared with the Wi-Fi radio and will return garbage data or cause crashes if Wi-Fi is initialized later in the project.
| ESP32 Pin | Module/Sensor Pin | Wire Color | Function |
|---|---|---|---|
| GPIO 34 (ADC1_CH6) | ZMPT101B OUT | Blue | Voltage Waveform Input |
| GPIO 35 (ADC1_CH7) | SCT-013 Burden Node | Green | Current Waveform Input |
| 3V3 | ZMPT101B VCC / Bias Div | Red | Power & 1.65V Reference |
| GND | ZMPT101B GND / Bias Div | Black | Common Ground Reference |
The Theory: True RMS vs. Average and Phase Angle
Standard digital multimeters often use 'average-responding' circuits. They rectify the AC waveform, find the average, and multiply by a fixed form factor (1.11 for a pure sine wave) to display the RMS value. According to Fluke's instrumentation guidelines, this method fails catastrophically on non-linear loads like variable frequency drives or switching power supplies, where the waveform is heavily distorted.
True RMS requires squaring every instantaneous sample, averaging those squares over a complete cycle, and taking the square root (Root-Mean-Square). Furthermore, to calculate Real Power (Watts) and Power Factor, we cannot just multiply V_rms by I_rms. That yields Apparent Power (Volt-Amps). Real Power requires integrating the instantaneous voltage and current products over time:
Power Factor (PF) = Real Power (W) / Apparent Power (VA). A PF of 1.0 means voltage and current cross zero at the exact same microsecond. Inductive loads (motors) cause current to lag; capacitive loads cause current to lead.
Wiring Steps & Mains Safety Protocols
⚠ CRITICAL MAINS VOLTAGE WARNING
This electronics project interfaces with 120V/240V AC mains. Before making any connections, de-energize the circuit at the breaker panel. Use a lockout/tagout device if possible, and verify the wires are dead using a CAT III or CAT IV rated multimeter. Never work on live panels. If you are not confident in identifying line, neutral, and ground conductors, hire a licensed electrician. Local electrical codes (NEC/IEC) dictate strict rules for tapping branch circuits.
- Calculate and Install the Burden Resistor: The SCT-013-000 outputs 50mA at 100A. The ESP32 ADC accepts 0-3.3V, meaning our peak AC voltage can be at most 1.65V (since we bias it at the midpoint). Using Ohm's Law: R = V_peak / I_peak = 1.65V / (0.05A * √2) = 23.3Ω. Solder a standard 22Ω metal film resistor directly across the two output wires of the SCT-013 plug. Do not leave a CT open-circuited while clamped around a live wire; it will generate lethal high voltages and destroy the core.
- Build the DC Bias Network: The ESP32 ADC cannot read negative voltages. We must shift the AC waveform up by 1.65V. Connect two 10kΩ resistors in series between the ESP32's 3V3 and GND pins. The midpoint of this divider provides exactly 1.65V. Connect a 10μF ceramic capacitor between this midpoint and GND to filter out high-frequency noise. Wire the midpoint to one side of your burden resistor, and the other side of the burden resistor to GPIO 35.
- Calibrate the ZMPT101B Voltage Module: The ZMPT101B has an onboard trimpot. Power the module with 3.3V and use your multimeter to measure the DC voltage at the 'OUT' pin. Carefully turn the blue trimpot with a ceramic screwdriver until the DC output reads exactly 1.65V. If this is off, your AC waveform will clip against the 0V or 3.3V ADC rails, ruining your RMS math.
- Clamp and Connect: Clamp the SCT-013 around only the Line (hot) conductor. Clamping around both Line and Neutral will result in a net magnetic field of zero and a reading of 0A. Wire the ZMPT101B AC input terminals across Line and Neutral.
Complete ESP32 Firmware for AC Sampling
The following C++ code targets the Arduino IDE framework for the ESP32. It uses a bounded sampling loop to capture one full 60Hz cycle (approx 16.6ms) at high speed. We include explicit error handling to detect disconnected sensors by checking for ADC variance.
#include <Arduino.h>
// --- PIN DEFINITIONS (ADC1 ONLY) ---
#define V_SENSOR_PIN 34 // ADC1_CH6
#define I_SENSOR_PIN 35 // ADC1_CH7
// --- CALIBRATION CONSTANTS ---
// Adjust these based on your exact burden resistor and ZMPT101B trimpot
const float V_CALIBRATION = 0.315; // Volts per ADC step
const float I_CALIBRATION = 0.042; // Amps per ADC step
const int DC_BIAS = 2048; // Theoretical midpoint of 12-bit ADC (4095/2)
void setup() {
Serial.begin(115200);
while(!Serial) { delay(10); }
analogReadResolution(12);
analogSetAttenuation(ADC_11db); // Full 0-3.3V range
Serial.println("AC True RMS & Power Factor Datalogger Initialized.");
}
void loop() {
const int SAMPLES = 1200; // ~1 cycle at 60Hz with ~14us/sample delay
long sumV_sq = 0, sumI_sq = 0, sumP = 0;
int rawV, rawI;
// Bounded sampling loop to prevent Watchdog timeouts
unsigned long start_time = micros();
for(int i = 0; i < SAMPLES; i++) {
rawV = analogRead(V_SENSOR_PIN) - DC_BIAS;
rawI = analogRead(I_SENSOR_PIN) - DC_BIAS;
sumV_sq += (long)rawV * rawV;
sumI_sq += (long)rawI * rawI;
sumP += (long)rawV * rawI;
// Minimal delay to set sampling rate, yielding to RTOS occasionally
delayMicroseconds(10);
if(i % 100 == 0) yield();
}
unsigned long elapsed = micros() - start_time;
// Calculate RMS and Real Power
float V_rms = sqrt((float)sumV_sq / SAMPLES) * V_CALIBRATION;
float I_rms = sqrt((float)sumI_sq / SAMPLES) * I_CALIBRATION;
float Real_Power = ((float)sumP / SAMPLES) * V_CALIBRATION * I_CALIBRATION;
float Apparent_Power = V_rms * I_rms;
// Error Handling: Check for disconnected sensors (variance near zero)
if(V_rms < 5.0) {
Serial.println("ERROR: Voltage sensor disconnected or reading below noise floor.");
} else {
float PF = (Apparent_Power > 0.1) ? (Real_Power / Apparent_Power) : 0.0;
Serial.printf("V_rms: %.1fV | I_rms: %.2fA | Real: %.1fW | PF: %.2f | Time: %luus\n",
V_rms, I_rms, Real_Power, PF, elapsed);
}
delay(500); // Update twice a second
}
Debugging: Watchdog Timeouts and Sensor Drift
When pushing the ESP32's ADC to its limits, you will inevitably encounter hardware abstraction layer (HAL) quirks. If your serial monitor suddenly halts and outputs the following exact string:
Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1)
This means your sampling loop monopolized the CPU for too long, starving the FreeRTOS idle task or Wi-Fi stack on Core 0, triggering the hardware Watchdog Timer (WDT). The Espressif ADC oneshot documentation warns against blocking tasks for more than a few milliseconds without yielding.
The First Three Things to Check When It Fails:
- Verify DC Bias Voltage: Grab your multimeter. Measure the DC voltage at GPIO 34 and GPIO 35 relative to GND. Both must read exactly 1.65V ± 0.05V. If it reads 0V or 3.3V, your bias network is broken, and the ADC is saturating, causing math overflow in the squaring operations.
- Check the Burden Resistor: Ensure the 22Ω resistor is physically soldered across the SCT-013 jack. If you are using the SCT-013-103 (which has an internal 62Ω resistor designed for 30A max), adding an external 22Ω resistor will create a parallel resistance of ~16Ω, skewing your I_CALIBRATION constant wildly.
- Confirm ADC1 Pin Usage: Verify you haven't accidentally wired the voltage sensor to GPIO 25, 26, or 27 (ADC2). If Wi-Fi initializes in the background, ADC2 reads will fail silently or throw exceptions, returning 0 and causing divide-by-zero errors when calculating Power Factor.
Ranked Causes for Sensor Drift Over Time
If your readings are accurate on day one but drift by 5-10% over a month:
- Cause 1 (Most Likely): Thermal drift in the ZMPT101B's cheap onboard operational amplifier. Fix: Move the ESP32 heat source away from the ZMPT101B module, or replace the module with a discrete precision op-amp circuit (e.g., OPA2277).
- Cause 2: ESP32 ADC non-linearity. The ESP32 ADC is notoriously non-linear near the 0V and 3.3V rails. Fix: Ensure your AC peaks never exceed 1.2V above or below the 1.65V bias point.
- Cause 3: Core saturation in the SCT-013. If you frequently measure loads above 80A, the ferrite core retains residual magnetism. Fix: Demagnetize the core by passing a decreasing AC current through it, or upgrade to a Hall-effect sensor like the ACS712-30A (though it introduces its own noise trade-offs).
Extending and Simplifying the Build
Depending on your end goal, you may want to alter the scope of this electronics project.
How to Simplify (The Off-The-Shelf Route)
If you need mains power monitoring for a home automation dashboard but don't want to wrestle with burden resistor math and DSP loops, swap the raw sensors for a PZEM-004T v3.0 module ($8 USD). This dedicated IC handles isolation, True RMS calculation, and phase-angle detection internally. You simply query it via the ESP32's hardware UART (RX/TX pins) using the Modbus-RTU protocol, entirely bypassing the ADC and eliminating the WDT timeout risk.
How to Extend (The IoT Route)
To turn this bench experiment into a permanent smart-home sensor:
- Add the
WiFi.handPubSubClient.hlibraries to the firmware. - Move the ADC sampling loop to Core 0 using a FreeRTOS task pinned to that core, leaving Core 1 exclusively for Wi-Fi and MQTT handling. This completely eliminates the Watchdog timeout error.
- Format the V_rms, I_rms, and PF floats into a JSON payload and publish to an MQTT broker (like Mosquitto or Home Assistant) every 5 seconds.
- For advanced AC theory analysis, log the raw waveform arrays to an SD card via SPI to perform Fast Fourier Transform (FFT) analysis later, allowing you to measure Total Harmonic Distortion (THD) of your household appliances.
By mastering the relationship between instantaneous sampling and AC theory, you move from simply observing circuits to deeply analyzing power quality, a critical skill in modern embedded systems design.






