Project Overview & Difficulty Rating
When browsing DIY electrical engineering projects, most builders stop at blinking LEDs or reading simple DC sensors. But to truly understand alternating current, you need to measure it the way the power grid does: using True Root Mean Square (RMS). This project bridges fundamental AC circuit theory with embedded systems design, tasking you with building a high-speed sampling True RMS AC voltmeter using an ESP32 and a ZMPT101B isolation module.
The Theory: Why Average Voltage Fails and True RMS Wins
If you measure a standard AC sine wave with a basic multimeter set to DC, it reads zero. If you rectify it and read the average, you get roughly 0.637 of the peak voltage. But neither of these tells you how much actual work (heat/power) the voltage can do. That requires the Root Mean Square value.
Mathematically, RMS is the square root of the mean of the squares of the instantaneous voltages over one complete cycle:
$V_{RMS} = \sqrt{\frac{1}{T} \int_0^T v(t)^2 dt}$
In the discrete digital domain of a microcontroller, we replace the integral with a summation. According to the electronics tutorials on RMS voltage, a pure sine wave has a form factor of 1.11, meaning $V_{RMS} = V_{peak} / \sqrt{2}$. However, modern mains power is rarely a perfect sine wave due to switching power supplies and variable frequency drives. A True RMS meter must sample the raw waveform, square every sample, average those squares, and take the square root. This is the only way to get an accurate reading on distorted waveforms.
To capture a 60Hz wave accurately, the Nyquist-Shannon sampling theorem dictates a minimum sampling rate of 120Hz. In practice, to reconstruct the wave and calculate RMS without massive aliasing errors, we need at least 1,000 to 2,000 samples per second. The ESP32's 12-bit SAR ADC is more than capable of this, provided we manage its notorious non-linearity at the voltage rails.
Hardware Spec Sheet & Pin Mapping
For this build, we are using the standard 30-pin DevKit V1. Do not use the ESP32-C3 or ESP32-S3 variants for this specific code without adjusting the ADC channel definitions, as their pinouts and ADC architectures differ.
| Component | Exact Variant | Notes |
|---|---|---|
| Microcontroller | ESP32-WROOM-32 (DevKit V1, 30-pin) | ~$6.50. Must have 12-bit ADC. |
| Voltage Sensor | ZMPT101B AC Voltage Transformer Module | ~$4.00. Provides galvanic isolation. |
| Wiring | 22 AWG Silicone Dupont Cables | Female-to-Female and Male-to-Female. |
| Power Supply | 5V 2A USB-C Bench Supply | Do not power from a noisy PC USB port. |
| ZMPT101B Pin | ESP32 Pin | Function |
|---|---|---|
| VCC | 3V3 | Power (Critical: Must be 3.3V, not 5V) |
| GND | GND | Common Ground |
| OUT | GPIO 34 (ADC1_CH6) | Analog Signal Output |
Step-by-Step Build & Wiring Procedure
- Prepare the Sensor: Locate the small blue trimmer potentiometer on the ZMPT101B board. Turn it fully counter-clockwise to start with the minimum gain.
- Wire the Low Voltage Side: Connect the ZMPT101B VCC to the ESP32 3V3 pin. Connect GND to GND. Connect the OUT pin to ESP32 GPIO 34.
- Wire the Mains Side: Connect your AC Line (L) and Neutral (N) to the primary screw terminals on the ZMPT101B. Polarity does not matter for the transformer.
- Power the ESP32: Plug the ESP32 into your clean bench power supply. Do not power the ESP32 from the same noisy mains circuit you are measuring without a high-quality isolation transformer.
- Upload and Calibrate: Upload the firmware below. Open the Serial Monitor at 115200 baud. Slowly turn the ZMPT101B potentiometer clockwise until the serial output matches your trusted multimeter's AC voltage reading.
Complete ESP32 Firmware with Error Handling
This code targets the ESP32 DevKit V1 (ESP32-WROOM-32). It uses a tight sampling loop to capture exactly one full cycle of a 60Hz waveform (approx 16.67ms) or 50Hz (20ms), calculating the discrete RMS value.
/*
* True RMS AC Voltmeter for ESP32-WROOM-32
* Target Board: ESP32 DevKit V1 (30-pin)
* Sensor: ZMPT101B on GPIO 34
*/
#define ADC_PIN 34
#define ADC_MAX 4095.0
#define V_REF 3.3
#define SAMPLES_PER_CYCLE 1000
#define MAINS_FREQ_HZ 60.0
// Calibration factor: Adjust this based on your ZMPT101B pot setting
// Start at 1.0 and tune against a known good multimeter
float calibrationFactor = 1.0;
// Offset voltage (VCC/2). For 3.3V VCC, offset is 1.65V.
// In ADC units: 1.65V / 3.3V * 4095 = 2048
const int ADC_OFFSET = 2048;
void setup() {
Serial.begin(115200);
analogReadResolution(12);
analogSetAttenuation(ADC_11db); // Full scale ~3.3V
pinMode(ADC_PIN, INPUT);
// Allow ADC to stabilize
delay(1000);
for(int i=0; i<50; i++) analogRead(ADC_PIN);
Serial.println("ESP32 True RMS Voltmeter Initialized.");
}
void loop() {
unsigned long startTime = micros();
unsigned long cycleTime = 1000000 / MAINS_FREQ_HZ; // Microseconds per cycle
float sumSquares = 0;
int saturationCount = 0;
// Sample for exactly one AC cycle
while(micros() - startTime < cycleTime) {
int raw = analogRead(ADC_PIN);
// Error Handling: Detect ADC saturation (clipping at rails)
// The ESP32 ADC is highly non-linear above 3800 and below 100
if(raw > 3900 || raw < 100) {
saturationCount++;
}
// Convert to voltage, subtract DC offset
float voltage = ((raw / ADC_MAX) * V_REF) - (V_REF / 2.0);
sumSquares += (voltage * voltage);
}
// Trigger fatal error if ADC is saturated for > 5% of the cycle
if(saturationCount > (SAMPLES_PER_CYCLE * 0.05)) {
Serial.println("FATAL: ADC_SATURATION_CONTINUOUS - Adjust ZMPT101B gain potentiometer");
delay(2000); // Pause to prevent serial flood
return;
}
// Calculate RMS
float meanSquares = sumSquares / SAMPLES_PER_CYCLE;
float rmsVoltage = sqrt(meanSquares) * calibrationFactor;
// Scale to actual mains voltage (e.g., if sensor outputs 1V RMS for 120V AC)
// This multiplier depends on your specific ZMPT101B transformer ratio
float displayVoltage = rmsVoltage * 120.0;
Serial.print("True RMS Voltage: ");
Serial.print(displayVoltage, 1);
Serial.println(" V");
delay(500); // Update rate
}
Debugging: ADC Saturation and Calibration Drift
When working with high-speed analog sampling on the ESP32, hardware quirks will inevitably surface. If your serial monitor outputs the exact error string: "FATAL: ADC_SATURATION_CONTINUOUS - Adjust ZMPT101B gain potentiometer", your waveform is clipping against the 3.3V or 0V rails, rendering the RMS calculation useless.
The first three things to check when it fails:
- Verify ZMPT101B VCC: Use a multimeter to check the voltage between the ZMPT101B VCC and GND pins. It must read exactly 3.3V. If you accidentally wired it to the ESP32's VIN or 5V pin, the sensor's internal op-amp offset will shift to 2.5V, pushing the signal completely out of the ESP32's 0-3.3V ADC range.
- Check the Physical Gain Potentiometer: The blue trimmer pot on the ZMPT101B is extremely sensitive. Turn it counter-clockwise to reduce the gain until the error clears.
- Measure the DC Offset: Disconnect the AC mains. Measure the DC voltage between the ZMPT101B OUT pin and GND. It should read ~1.65V. If it reads 0V or 3.3V, the module's op-amp is damaged or unpowered.
Ranked Causes for Persistent Calibration Drift:
- Thermal Drift (Most Likely): The cheap resistors on clone ZMPT101B boards have high temperature coefficients. As the board warms up, the offset shifts. Solution: Let the circuit run for 15 minutes before final calibration.
- ESP32 ADC Non-Linearity: The WROOM-32 ADC is notoriously inaccurate near 0V and 3.3V. If your AC waveform peaks near these rails, your RMS reading will be artificially low. Keep your peak ADC readings between 500 and 3500.
- Mains Frequency Mismatch: If you are in a 50Hz region (UK/EU/AUS) but the code is set to
MAINS_FREQ_HZ 60.0, you will be sampling 5/6ths of a cycle, causing massive calculation errors. Change the constant to 50.0.
Extending and Simplifying the Build
Not every project requires raw ADC manipulation. Depending on your end goal, you can adapt this build.
How to Simplify:
If you just need the data and don't care about the embedded theory, ditch the ZMPT101B and raw ADC math. Use a PZEM-004T v3.0 module (~$12). It contains a dedicated metrology chip (ATT7022B) that handles isolation, True RMS calculation, and power factor internally, outputting clean data via an optocoupled UART interface. You lose the educational value of Nyquist sampling, but gain immense reliability.
How to Extend:
To turn this voltmeter into a full Real Power (Watts) and Energy (kWh) monitor, add a SCT-013-000 100A Current Transformer and a burden resistor circuit to a second ADC pin (GPIO 35). By sampling voltage and current simultaneously, you can calculate the phase angle difference between the two waveforms. Multiplying $V_{RMS} \times I_{RMS} \times \cos(\theta)$ gives you Real Power, allowing you to measure the true energy consumption of inductive loads like motors and compressors.
FAQ: Common Questions on DIY Electrical Engineering Projects
What are the safest DIY electrical engineering projects for beginners?
The safest projects operate entirely on the low-voltage DC side (under 50V DC). Building a programmable electronic load using an op-amp and a power MOSFET, or designing a multi-channel thermocouple datalogger with an ESP32, teaches core electrical engineering concepts like feedback loops and signal conditioning without the lethal risks of mains AC. If you must work with AC, always use pre-isolated, UL/CE-certified sensor modules rather than wiring raw transformers yourself.
How do I isolate mains voltage in DIY electrical engineering projects?
Galvanic isolation is non-negotiable when measuring mains. Never use a simple resistor divider to step down 120V/240V AC into a microcontroller; a single component failure will send lethal voltage directly into your low-voltage logic. Use dedicated isolation transformers (like the ZMPT101B), optocouplers (for digital signals), or Hall-effect sensors (for current). For communication, use isolated RS-485 transceivers or fiber optics to bridge the gap between the mains-side and the PC-side.
Why do my DIY electrical engineering projects show noisy ADC readings?
Noisy ADC readings usually stem from three issues: a noisy power supply (switching regulators inject high-frequency ripple into the 3.3V rail), improper grounding (creating ground loops between the sensor and the MCU), or missing bypass capacitors. To fix this, add a 100nF ceramic capacitor and a 10µF tantalum capacitor directly across the VCC and GND pins of your sensor. Furthermore, use the ESP32's internal oversampling functions or implement a software moving-average filter to smooth out high-frequency EMI.






