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
Difficulty Rating: Intermediate. Requires basic I2C wiring, interrupt handling for the encoder, and understanding of NEC ampacity derating rules.

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
3V3SH1106 OLEDVCCDo not use 5V; SH1106 logic is 3.3V
GNDSH1106 OLEDGNDCommon ground
GPIO 21SH1106 OLEDSDAHardware I2C Data
GPIO 22SH1106 OLEDSCLHardware I2C Clock
5V (VIN)KY-040 Encoder+ (VCC)KY-040 module has onboard pull-ups
GNDKY-040 EncoderGNDCommon ground
GPIO 25KY-040 EncoderCLKInterrupt pin for rotation
GPIO 26KY-040 EncoderDTDirection pin
GPIO 27KY-040 EncoderSWPush-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.

Exact Error String:
Guru Meditation Error: Core 1 panic'ed (LoadProhibited). Exception was unhandled.

Ranked Causes & Fixes:

  1. Out-of-Bounds Array Access (Most Likely): If reqAmpacity exceeds 55A, the for loop checking nec_ampacity[] finishes without breaking, leaving wireIdx at 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.
  2. Stack Overflow from Local Arrays: If you move nec_ampacity or std_breakers inside the loop() function, you consume stack memory on every iteration. Fix: Always declare lookup tables globally with the const keyword so they reside in flash memory.
  3. I2C Bus Lockup: A missing pull-up resistor on the SDA/SCL lines can cause the Wire library 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 wireIdx variable is initialized to a safe fallback (e.g., 4 for 6 AWG) before the loop.
  • Check that loadAmps is 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 as 0x3D).

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.