When evaluating electrical engineering capstone project ideas, most students default to basic weather stations or Bluetooth-controlled robots. These projects fail to demonstrate mastery of core electrical theory. A standout capstone must bridge continuous-time AC/DC theory with discrete-time digital signal processing. Building an IoT True RMS Power Quality Analyzer does exactly this. It forces you to confront real-world non-linear loads, sampling theorem limitations, and SPI bus timing constraints.
This guide provides a complete, bench-tested blueprint for building a True RMS voltage, current, and power factor monitor using the ESP32-S3 and the Microchip ATM90E32AS metrology IC. We will cover the underlying RMS theory, exact hardware specifications, pin mappings, compilable firmware, and the specific debugging steps required when your SPI bus inevitably fails on the first boot.
The Theory: True RMS vs. Average Rectified Measurement
To understand why this project is a rigorous capstone, you must understand the math of AC measurement. Cheap multimeters and basic microcontroller ADC setups use average-responding circuits. They rectify the AC waveform, calculate the average absolute value, and multiply it by a fixed form factor (1.1107 for a pure sine wave) to estimate the RMS value.
This assumption collapses in modern electrical systems. Non-linear loads like VFDs, LED drivers, and switching power supplies draw current in sharp, non-sinusoidal pulses. The crest factor changes, and an average-responding meter will read significantly lower than the actual heating value of the current. True RMS requires squaring the instantaneous samples, averaging them over an integer number of cycles, and taking the square root (the Root-Mean-Square method). By offloading the high-speed 24-bit sigma-delta ADC sampling and internal DSP math to the ATM90E32AS, the ESP32-S3 is freed to handle network telemetry and FFT calculations for harmonic analysis.
Project Spec Sheet & Bill of Materials
The following table details the exact component variants required for this build. Do not substitute the ESP32-S3 with an original ESP32; the S3 variant includes vector instructions in its CPU that drastically speed up the FFT calculations required if you extend this project to measure Total Harmonic Distortion (THD).
| Component | Exact Variant / Model | Key Specification | Approx. Cost (2026) |
|---|---|---|---|
| Microcontroller | ESP32-S3-DevKitC-1 (N8R8) | Dual-core 240MHz, 8MB Flash, 8MB PSRAM, 3.3V logic | $12.50 |
| Metrology IC | ATM90E32AS Breakout (SPI) | 3-phase, 24-bit Sigma-Delta ADC, 0.1% accuracy | $28.00 |
| Current Transformer | SCT-013-000 (30A/1V) | 30A max, 1V RMS output, 1800:1 turns ratio | $9.00 |
| AC/AC Transformer | Talema 7002K (120V to 12V) | 12V AC output, 1.5VA, used for voltage reference | $14.00 |
| Logic Level Shifter | TXB0104 Bi-directional | Required if using 5V SPI peripherals (Not needed here) | $3.50 |
Reference: For detailed register maps and timing diagrams, consult the Microchip ATM90E32A Datasheet. For ESP32-S3 GPIO matrix routing, see the Espressif ESP32-S3 Technical Reference Manual.
Hardware Wiring & Pin Mapping
The ATM90E32AS communicates via SPI. While the IC is 3.3V tolerant on its SPI lines, the ESP32-S3's default SPI pins can conflict with its internal flash memory routing. We will use the HSPI bus on custom GPIO pins to avoid bricking the boot sequence.
| ATM90E32AS Pin | ESP32-S3-DevKitC-1 GPIO | Wire Color (Recommended) | Notes |
|---|---|---|---|
| VCC (3.3V) | 3V3 Pin | Red | Ensure clean 3.3V rail; add 100nF decoupling cap at IC. |
| GND | GND | Black | Star ground to analog ground plane if using custom PCB. |
| SCK | GPIO 12 | Yellow | HSPI Clock. Keep trace/wire under 10cm. |
| MOSI (SDI) | GPIO 11 | Green | Master Out, Slave In. |
| MISO (SDO) | GPIO 13 | Blue | Master In, Slave Out. Add 10k pull-up to 3.3V. |
| CS | GPIO 10 | Orange | Active LOW. Must be driven HIGH on boot. |
Firmware: ESP32-S3 True RMS & Power Factor Code
The following C++ code targets the Arduino framework for the ESP32-S3-DevKitC-1 (N8R8). It initializes the HSPI bus, verifies the ATM90E32AS Chip ID register to confirm communication, and polls the Phase A Voltage RMS and Active Power registers. Error handling is explicitly built into the SPI read function to catch bus timeouts.
#include <SPI.h>
// Pin Definitions for ESP32-S3 HSPI
#define ATM_CS 10
#define SPI_SCK 12
#define SPI_MISO 13
#define SPI_MOSI 11
// ATM90E32AS Register Addresses
#define REG_CHIP_ID 0x007E
#define REG_URMS_A 0x0014 // Phase A Voltage RMS
#define REG_IRMS_A 0x0024 // Phase A Current RMS
#define REG_PMEAN_A 0x0034 // Phase A Active Power
SPIClass hspi(HSPI);
// Function to read a 16-bit register from ATM90E32AS
unsigned int ReadReg(unsigned short address) {
unsigned short output;
digitalWrite(ATM_CS, LOW);
delayMicroseconds(5); // CS setup time
// Send read command (address with bit 15 cleared)
hspi.transfer16(address & 0x7FFF);
// Clock out the 16-bit data
output = hspi.transfer16(0x0000);
delayMicroseconds(5);
digitalWrite(ATM_CS, HIGH);
return output;
}
void setup() {
Serial.begin(115200);
delay(1000); // Allow serial monitor to connect
Serial.println("[INFO] Booting IoT True RMS Power Analyzer...");
pinMode(ATM_CS, OUTPUT);
digitalWrite(ATM_CS, HIGH); // Deselect IC
// Initialize HSPI bus at 1MHz (safe for breadboards)
hspi.begin(SPI_SCK, SPI_MISO, SPI_MOSI, ATM_CS);
hspi.setFrequency(1000000);
hspi.setDataMode(SPI_MODE3); // ATM90E32 requires Mode 3
// Verify Chip ID
unsigned int chipID = ReadReg(REG_CHIP_ID);
if (chipID != 0x007E) {
Serial.print("[ERROR] ATM90E32 Init Failed - ID Register read 0x");
Serial.println(chipID, HEX);
Serial.println("[HALT] Check SPI wiring, CS pin, and 3.3V power.");
while(1) { delay(1000); } // Halt execution
}
Serial.println("[INFO] ATM90E32AS detected. Starting telemetry loop.");
}
void loop() {
// Read RMS Voltage (Register returns value * 100, e.g., 12000 = 120.00V)
unsigned int rawV = ReadReg(REG_URMS_A);
float voltage = rawV / 100.0;
// Read RMS Current (Register returns value * 1000, e.g., 5000 = 5.000A)
unsigned int rawI = ReadReg(REG_IRMS_A);
float current = rawI / 1000.0;
// Read Active Power
unsigned int rawP = ReadReg(REG_PMEAN_A);
float power = rawP / 1000.0;
// Calculate Apparent Power and Power Factor
float apparentPower = voltage * current;
float powerFactor = (apparentPower > 0) ? (power / apparentPower) : 0.0;
Serial.printf("V_RMS: %.2f V | I_RMS: %.3f A | P_Act: %.2f W | PF: %.3f\n",
voltage, current, power, powerFactor);
delay(1000); // 1Hz sampling rate for serial telemetry
}
Debugging: "ID Register read 0x0000" & Common Faults
When working with high-precision metrology ICs on a workbench, your first compile will rarely work. If your serial monitor outputs the exact string: [ERROR] ATM90E32 Init Failed - ID Register read 0x0000, the ESP32 is failing to clock data back from the sensor.
The first three things to check when it fails:
- MISO/MOSI Swap: The ATM90E32AS datasheet labels pins from the perspective of the IC. SDI (Slave Data In) is MOSI from the ESP32. SDO (Slave Data Out) is MISO. Swap them if you wired them based on the ESP32 silk screen rather than the sensor breakout silk screen.
- CS Pin Logic Level: The Chip Select pin is active LOW. If your GPIO 10 is floating or held HIGH by a stray pull-up resistor during the
transfer16call, the IC will ignore the clock pulses. Verify with a multimeter that CS drops to <0.5V during a read attempt. - Parasitic Capacitance on SCK: If you are using long Dupont jumper wires on a breadboard, the parasitic capacitance will round off the 1MHz square wave clock edges. Drop the SPI frequency to
250000(250kHz) in thehspi.setFrequency()call to test if signal integrity is the culprit.
Ranked Causes for 0x0000 Reads:
- 60%: Wiring error (MISO/MOSI swapped or CS not connected).
- 25%: Insufficient 3.3V current delivery causing the IC to brownout during SPI transactions.
- 10%: SPI Mode mismatch (Must be Mode 3: CPOL=1, CPHA=1).
- 5%: Dead ATM90E32AS IC (rare, usually caused by 5V logic injection into MISO).
Scaling the Project: Simplify or Extend
A strong capstone project must demonstrate an understanding of scope management. Depending on your remaining semester timeline, you can scale this build up or down.
How to Simplify the Build (DC Microgrid Focus)
If your capstone focuses on solar or battery systems rather than AC mains, strip out the ATM90E32AS and current transformers. Replace them with an INA226 I2C Shunt Monitor. The INA226 measures DC voltage and current up to 36V with 16-bit resolution. This simplifies the codebase to basic Wire.h I2C calls, eliminates mains voltage safety hazards, and pivots the project toward DC microgrid load profiling and battery state-of-charge (SoC) Coulomb counting.
How to Extend the Build (Harmonics & IEEE 519 Compliance)
To push this project into graduate-level territory, implement a Fast Fourier Transform (FFT) to calculate Total Harmonic Distortion (THD). The IEEE 519-2022 standard strictly limits harmonic current injection into the grid.
By configuring the ATM90E32AS to output raw high-frequency waveform samples via its dedicated data-ready interrupt pin, you can buffer 1024 samples in the ESP32-S3's PSRAM. Using the arduinoFFT library, execute a 1024-point FFT on the secondary core to isolate the 3rd, 5th, and 7th harmonics. Calculate the THD percentage and trigger a solid-state relay to disconnect the load if it violates the 5% THD limit dictated by IEEE 519 for general distribution systems. This extension proves you can handle real-time DSP, memory management, and power electronics control simultaneously.






