To measure AC current safely with a microcontroller, use a split-core SCT-013-000 current transformer paired with a 22Ω 1% burden resistor and a 1.65V DC bias network feeding an ESP32 ADC1 pin. This setup steps down dangerous mains current into an isolated, low-voltage AC signal that the ESP32 can sample without frying its GPIOs or clipping the waveform.
Most online tutorials copy-paste 5V Arduino burden math onto 3.3V ESP32 boards, resulting in clipped waveforms and destroyed ADC pins. This guide provides the exact 3.3V component sizing, a decision framework for part selection, and the compilable code to get your current transformer project running on the bench today.
Decision Tree: Sizing the CT and Burden Resistor
The burden resistor converts the CT's secondary current output into a measurable voltage. If the resistance is too high, the voltage exceeds the ESP32's 3.3V ADC limit. If it's too low, you lose resolution on small loads. Use this decision path to select your hardware:
| Application Scenario | CT Model | Secondary Output | Required Burden (for 3.3V ADC) |
|---|---|---|---|
| If measuring single appliances (<30A) | SCT-013-030 | 0-1V RMS (Internal 62Ω) | None (Internal) |
| If measuring sub-panels or feeders (<50A) | SCT-013-050 | 0-50mA RMS | 43Ω 1% Resistor |
| If measuring whole-home mains (up to 100A) | SCT-013-000 | 0-50mA RMS | 22Ω 1% Resistor |
Hardware BOM and Pin Mapping
This build targets the ESP32-WROOM-32 DevKit V1 (30-pin variant). Do not use 38-pin variants without verifying the pinout, as ADC channel assignments shift.
Parts List
- Microcontroller: ESP32-WROOM-32 DevKit V1 (30-pin)
- Sensor: SCT-013-000 (100A:50mA split-core CT)
- Burden Resistor: 22Ω 1% 1/4W metal film (Do not use 5% carbon; thermal drift will ruin calibration)
- Bias Resistors: 2x 100kΩ 1% metal film
- Bypass Capacitor: 10µF electrolytic or ceramic (rated 10V+)
- Protection (Optional but recommended): 3.3V Zener diode or TVS diode across ADC pin to GND
Pin Mapping Table
| Component | ESP32 Pin | ADC Channel | Notes |
|---|---|---|---|
| CT Signal (via Bias) | GPIO 36 (VP) | ADC1_CHANNEL_0 | Must use ADC1; ADC2 conflicts with WiFi |
| Bias Divider Top | 3V3 | N/A | Feeds first 100kΩ resistor |
| Bias Divider Bottom | GND | N/A | Feeds second 100kΩ resistor |
The DC Bias Network: Shifting AC to the ADC Window
The ESP32 ADC cannot read negative voltages. If you wire the CT directly to GPIO 36, the negative half of the AC sine wave will be clamped by the ESP32's internal protection diodes, distorting your reading and potentially damaging the silicon over time.
We solve this by creating a virtual ground at exactly half of VCC (1.65V). The 100kΩ resistor voltage divider provides this offset, and the 10µF capacitor acts as a low-impedance sink to prevent the bias voltage from sagging during high-current transients. The CT signal is superimposed onto this 1.65V baseline, meaning a 0A reading outputs a raw ADC value of ~2048 (half of 4095), and current flow causes the reading to oscillate above and below 2048.
Compilable ESP32 Code with EmonLib and Error Handling
This code uses the standard EmonLib library. It initializes WiFi (to prove ADC1 compatibility) and samples the current, handling basic ADC dropout errors.
#include <WiFi.h>
#include <EmonLib.h>
// --- PIN DEFINITIONS ---
const int CT_PIN = 36; // GPIO36 (ADC1_CHANNEL_0)
// --- CALIBRATION MATH ---
// SCT-013-000 has 2000 turns. Burden is 22 ohms.
// Calibration = Turns / Burden = 2000 / 22 = 90.90
const float CALIBRATION_FACTOR = 90.90;
// --- NETWORK CREDS ---
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
EnergyMonitor emon1;
void setup() {
Serial.begin(115200);
delay(500);
// Initialize WiFi to demonstrate ADC1 compatibility
Serial.print("Connecting to WiFi...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi Connected. ADC1 is safe to use.");
// Initialize EmonLib
// analogRead resolution for ESP32 is 12-bit (0-4095)
analogReadResolution(12);
emon1.current(CT_PIN, CALIBRATION_FACTOR);
Serial.println("Current Transformer Project Initialized.");
}
void loop() {
// Read 1480 half-wavelengths (approx 1 second at 60Hz)
double Irms = emon1.calcIrms(1480);
// Error handling: Check for ADC saturation or disconnect
// If bias is lost, raw ADC reads will rail at 0 or 4095
if (Irms < 0.05) {
Serial.println("Status: No load detected or CT disconnected.");
} else if (Irms > 105.0) {
Serial.println("ERROR: ADC Saturation. Check burden resistor and bias network.");
} else {
Serial.printf("Current: %.2f A | Power (approx @ 120V): %.2f W\n", Irms, Irms * 120.0);
}
delay(1000);
}
Debugging: First Three Things to Check When It Fails
When your serial monitor spits out garbage data or fails to compile, run through this ranked troubleshooting path.
1. The WiFi / ADC Conflict Error
Exact Error String: E (456) adc: adc2_get_raw(244): adc2 is in use by Wi-Fi
- Cause A (Most Likely): You wired the CT to GPIO 25, 26, or 27. These are ADC2 pins. The ESP32 hardware physically locks out ADC2 when the WiFi radio is active.
- Fix: Move the CT signal wire to GPIO 36, 39, 34, or 35 (ADC1 pins) and update
CT_PINin the code.
2. Readings Stuck at ~0.1A with No Load Attached
Symptom: The serial monitor reads 0.10A to 0.25A constantly, even when the CT is sitting on the desk.
- Cause A: Missing or failed 10µF bypass capacitor on the DC bias divider. The ESP32's ADC sampling capacitor is drawing charge from the high-impedance 100kΩ bias resistors, causing voltage sag and phantom readings.
- Fix: Solder the 10µF capacitor directly across the junction of the two 100kΩ resistors and GND.
- Cause B: The CT is clamped around a multi-conductor cable (like standard Romex/NM-B). The magnetic fields from the Line and Neutral cancel each other out, leaving only capacitive coupling noise.
- Fix: Clamp the CT around only one current-carrying conductor (Line or Neutral, never both, never the bare ground).
3. EmonLib Calibration is Wildly Off (e.g., Reads 40A on a 10A Load)
Symptom: Your multimeter clamp reads 10.0A, but the ESP32 serial output reads 16.5A.
- Cause: The ESP32's internal 3.3V regulator is actually outputting 3.1V or 3.4V, and EmonLib assumes a perfect 3.3V reference by default.
- Fix: Measure the actual voltage between the ESP32's 3V3 pin and GND with a calibrated multimeter. Open
EmonLib.cppin your Arduino libraries folder, find the linesupplyVoltage = 3.3;(or thereadVcc()function), and hardcode your measured voltage (e.g.,3.28).
Extending or Simplifying the Build
Depending on your accuracy requirements, you can scale this current transformer project up or down.
Simplify: Bypass the Internal ADC
The ESP32's internal ADC is notoriously noisy and non-linear above 3.1V. If you are measuring small loads (under 2A) and need precision, strip out the internal ADC calls and use an external ADS1115 16-bit I2C ADC. Wire the ADS1115 ALERT/RDY pin to an ESP32 GPIO for hardware interrupt-driven sampling, which eliminates the timing jitter inherent in analogRead() loops.
Extend: Three-Phase Monitoring
To monitor a 3-phase panel, you need three SCT-013 sensors. Because we strictly used ADC1 for the single-phase build, you have exactly four ADC1 pins available on the 30-pin DevKit (GPIO 36, 39, 34, 35). Wire the three CTs to GPIO 36, 39, and 34. Instantiate three EnergyMonitor objects in your code (emon1, emon2, emon3) and call calcIrms() sequentially. Note that sequential sampling introduces a slight phase-shift error between channels; if you need true simultaneous sampling for power factor calculations, upgrade to an ADS131E08 multi-channel delta-sigma ADC.
Final Recommendation: For 90% of home energy monitoring projects, stick to the SCT-013-000, 22Ω burden, and GPIO 36 configuration detailed above. It provides the best balance of safety, resolution, and code simplicity without requiring expensive external ADC modules.






