The fastest way to size a branch circuit without flipping through NEC Article 210 and 310 is to automate it. This guide walks you through building an ESP32-WROOM-32 based residential electrical wiring code calculator. By inputting your continuous load and wire run length via a rotary encoder, the tool instantly outputs the required NM-B AWG size, breaker rating, and voltage drop percentage. Defaulting to the 60°C ampacity column for standard Romex, a 16A continuous load immediately flags a requirement for 10 AWG wire and a 25A (or next standard 30A) breaker. Here is the exact hardware, code, and debugging path to build it.
Parts List & Spec Sheet
This build prioritizes jobsite durability and bench-top prototyping. We are targeting the standard 30-pin ESP32 DevKit v1 because its 5V VIN pin can easily be fed from a standard USB power bank, while the 3.3V logic interfaces directly with the I2C display.
| Component | Exact Variant / Model | Role in Build | Approx. Cost (2026) |
|---|---|---|---|
| Microcontroller | ESP32-WROOM-32 DevKit v1 (30-pin) | Core logic, NEC lookup tables, voltage drop math | $6.50 |
| Display | 1.3" I2C OLED (SH1106 driver, 128x64) | High-contrast output for AWG and breaker sizing | $8.00 |
| Input | KY-040 Rotary Encoder Module | Scroll through load amps and wire distance | $3.50 |
| Power | 5V 2A USB Power Bank | Portable jobsite power | $15.00 |
Pin Mapping & Wiring the Jobsite Tool
Keep your I2C lines short and use the default hardware I2C pins on the ESP32 to avoid software bit-banging overhead, which can cause display flicker when the encoder interrupts fire.
| ESP32 Pin | Module | Module Pin | Notes |
|---|---|---|---|
| 3V3 | SH1106 OLED | VCC | Do not use 5V; SH1106 logic is 3.3V |
| GND | SH1106 OLED | GND | Common ground |
| GPIO 21 | SH1106 OLED | SDA | Hardware I2C Data |
| GPIO 22 | SH1106 OLED | SCL | Hardware I2C Clock |
| 5V (VIN) | KY-040 Encoder | + (VCC) | KY-040 module has onboard pull-ups |
| GND | KY-040 Encoder | GND | Common ground |
| GPIO 25 | KY-040 Encoder | CLK | Interrupt pin for rotation |
| GPIO 26 | KY-040 Encoder | DT | Direction pin |
| GPIO 27 | KY-040 Encoder | SW | Push-button to toggle Continuous/Non-Continuous |
The NEC Sizing Engine: Complete ESP32 Code
This code targets the ESP32-WROOM-32 DevKit v1 in the Arduino IDE (Board: "DOIT ESP32 DEVKIT V1"). It uses the Adafruit SH110X library. The engine enforces NEC 210.19(A)(1) for continuous loads (125% multiplier) and NEC 240.4(D) for small conductor overcurrent limits.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
// Pin Definitions
#define I2C_SDA 21
#define I2C_SCL 22
#define ENC_CLK 25
#define ENC_DT 26
#define ENC_SW 27
// Display Setup
Adafruit_SH1106G display(128, 64, &Wire, -1);
// State Variables
volatile int loadAmps = 15;
volatile int distanceFt = 50;
bool isContinuous = false;
int menuState = 0; // 0=Amps, 1=Distance
// NEC 310.16 60°C Column (NM-B Romex) Ampacities
// Index: 0=14AWG, 1=12AWG, 2=10AWG, 3=8AWG, 4=6AWG
const int nec_ampacity[] = {15, 20, 30, 40, 55};
const char* awg_labels[] = {"14 AWG", "12 AWG", "10 AWG", "8 AWG", "6 AWG"};
// Standard Breaker Sizes (NEC 240.6)
const int std_breakers[] = {15, 20, 25, 30, 35, 40, 45, 50, 60};
void IRAM_ATTR handleEncoder() {
static unsigned long lastInterruptTime = 0;
unsigned long interruptTime = millis();
if (interruptTime - lastInterruptTime > 5) { // Debounce
if (digitalRead(ENC_DT) != digitalRead(ENC_CLK)) {
if (menuState == 0) loadAmps = constrain(loadAmps + 1, 1, 60);
else distanceFt = constrain(distanceFt + 10, 10, 500);
} else {
if (menuState == 0) loadAmps = constrain(loadAmps - 1, 1, 60);
else distanceFt = constrain(distanceFt - 10, 10, 500);
}
}
lastInterruptTime = interruptTime;
}
void setup() {
Serial.begin(115200);
Wire.begin(I2C_SDA, I2C_SCL);
if (!display.begin(0x3C, true)) {
Serial.println(F("SH1106 allocation failed"));
for(;;); // Halt
}
display.clearDisplay();
display.setTextColor(SH110X_WHITE);
pinMode(ENC_CLK, INPUT_PULLUP);
pinMode(ENC_DT, INPUT_PULLUP);
pinMode(ENC_SW, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENC_CLK), handleEncoder, FALLING);
}
void loop() {
// Button press toggles menu or continuous state
if (digitalRead(ENC_SW) == LOW) {
delay(200); // Simple debounce
if (digitalRead(ENC_SW) == LOW) {
menuState = (menuState + 1) % 2;
if (menuState == 0) isContinuous = !isContinuous;
}
}
// Calculate Required Ampacity (NEC 210.19)
int reqAmpacity = isContinuous ? (loadAmps * 1.25) : loadAmps;
// Find Wire Size (NEC 310.16 & 240.4D)
int wireIdx = 4; // Default to 6 AWG if loop fails
for (int i = 0; i < 5; i++) {
if (nec_ampacity[i] >= reqAmpacity) {
wireIdx = i;
break;
}
}
// Find Breaker Size (NEC 240.4B - Next standard size up)
int breakerSize = 60;
for (int i = 0; i < 9; i++) {
if (std_breakers[i] >= reqAmpacity) {
breakerSize = std_breakers[i];
break;
}
}
// Enforce NEC 240.4(D) Small Conductor Rule
if (wireIdx == 0 && breakerSize > 15) breakerSize = 15;
if (wireIdx == 1 && breakerSize > 20) breakerSize = 20;
if (wireIdx == 2 && breakerSize > 30) breakerSize = 30;
// Voltage Drop Calc (120V, Copper, K=12.9)
float vDrop = (2 * 12.9 * loadAmps * distanceFt) / (1000.0 * pow(2, (3 - wireIdx)) * 10); // Simplified CM approx
float vDropPct = (vDrop / 120.0) * 100.0;
// Render UI
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0,0);
display.println(F("NEC RESIDENTIAL SIZER"));
display.drawLine(0, 10, 128, 10, SH110X_WHITE);
display.setCursor(0, 15);
display.print(F("Load: ")); display.print(loadAmps); display.println(F("A"));
display.print(F("Dist: ")); display.print(distanceFt); display.println(F(" ft"));
display.print(F("Type: ")); display.println(isContinuous ? F("Continuous") : F("Non-Cont"));
display.setCursor(0, 45);
display.setTextSize(2);
display.print(awg_labels[wireIdx]);
display.setCursor(80, 45);
display.print(breakerSize); display.print(F("A"));
display.setTextSize(1);
display.setCursor(0, 60);
display.print(F("VD: ")); display.print(vDropPct, 1); display.print(F("% "));
if (vDropPct > 3.0) display.print(F("[WARN]"));
display.display();
delay(50);
}
Debugging: Fixing the 'Guru Meditation' Array Panic
When adapting this code for longer wire runs or higher amperages, you may hit a fatal crash. If your serial monitor outputs the exact string below, your ESP32 is attempting to read memory it doesn't own.
Guru Meditation Error: Core 1 panic'ed (LoadProhibited). Exception was unhandled.
Ranked Causes & Fixes:
- Out-of-Bounds Array Access (Most Likely): If
reqAmpacityexceeds 55A, theforloop checkingnec_ampacity[]finishes without breaking, leavingwireIdxat an invalid state or causing a subsequent array lookup to fail. Fix: Add a hard cap or expand the array to include 4 AWG and 3 AWG. - Stack Overflow from Local Arrays: If you move
nec_ampacityorstd_breakersinside theloop()function, you consume stack memory on every iteration. Fix: Always declare lookup tables globally with theconstkeyword so they reside in flash memory. - I2C Bus Lockup: A missing pull-up resistor on the SDA/SCL lines can cause the
Wirelibrary to hang, eventually triggering a watchdog panic. Fix: Ensure your SH1106 module has onboard 4.7k pull-ups, or add them externally to 3.3V.
The First Three Things to Check When It Fails:
- Verify the
wireIdxvariable is initialized to a safe fallback (e.g.,4for 6 AWG) before the loop. - Check that
loadAmpsis constrained in the interrupt handler so it cannot exceed your maximum array index. - Confirm the SH1106 I2C address is actually
0x3C(some cheap clones ship as0x3D).
Decision Tree: Which AWG and Breaker Does the Code Demand?
The ESP32 logic strictly follows the NFPA 70 (National Electrical Code) residential branch circuit rules. Use this decision matrix to verify the tool's output against your manual calculations.
| Calculated Load | Continuous? (>3h) | NEC 210.19 Min Ampacity | Min NM-B AWG (60°C) | NEC 240.4 Breaker |
|---|---|---|---|---|
| 12A | No | 12A | 14 AWG | 15A |
| 12A | Yes (x1.25 = 15A) | 15A | 14 AWG | 15A |
| 16A | No | 16A | 12 AWG | 20A |
| 16A | Yes (x1.25 = 20A) | 20A | 12 AWG | 20A |
| 17A | Yes (x1.25 = 21.25A) | 21.25A | 10 AWG | 25A (Next Std: 30A) |
Concrete Default Pick: If you are wiring a standard 120V residential receptacle circuit and the load profile is unknown, the code and the NEC default to 12 AWG NM-B on a 20A breaker. This provides the safest margin for voltage drop and allows for future continuous load additions without rewiring.
Extending and Simplifying the Build
Once the core calculator is stable on your workbench, you have two distinct paths depending on your field needs.
To Simplify (The Pocket Tool):
Strip out the rotary encoder and I2C OLED. Replace them with a simple web server hosted on the ESP32's WiFi. You can then input the load and distance from your phone browser while standing at the panel. This eliminates interrupt debouncing bugs entirely and reduces the BOM cost to under $7. Refer to the Espressif ESP32 Web Server documentation for the AsyncTCP implementation.
To Extend (The Ambient Derating Monitor):
NEC 310.15(B) requires ampacity derating when ambient temperatures exceed 86°F (30°C), which is common in hot attics where NM-B is often routed. Add a DS18B20 waterproof temperature sensor to GPIO 13. Modify the code to read the attic temperature and automatically bump the wireIdx up one size if the ambient temp crosses the 104°F (40°C) threshold, applying the 0.88 derating factor to the 60°C column. This turns a simple calculator into a true code-compliance enforcement tool.






