SMD resistance codes use 3-digit (5% tolerance), 4-digit (1% tolerance), or EIA-96 (two numbers plus a letter) systems to mark surface-mount resistors. When prototyping embedded systems, mixing up a 10kΩ I2C pull-up (marked 103) with a 100kΩ resistor (marked 104) will cause bus failures that take hours to trace. To verify them rapidly on the bench, use an ESP32-WROOM-32 with its 12-bit ADC and a precision 10kΩ reference resistor in a voltage divider configuration.

This guide walks through building an automated SMD resistance code verification jig. It measures the physical resistance of an unmarked or suspicious component, calculates the actual value, cross-references it to the correct SMD marking standard, and outputs the result to an OLED display.

The SMD Resistance Code Decision Tree

Before soldering a resistor to your PCB, you must identify which coding standard the manufacturer used. Use this decision path to determine the marking format and terminate on a concrete BOM strategy.

Visual Marking Standard Tolerance Example & Decoded Value
3 Digits Standard JIS 5% 103 = 10 × 10³ = 10,000Ω (10kΩ)
4 Digits Standard JIS 1% 1002 = 100 × 10² = 10,000Ω (10kΩ)
2 Digits + 1 Letter EIA-96 1% 47C = 301 × 10² = 30,100Ω (30.1kΩ)
3 Digits + 'R' Standard JIS 5% / 1% 4R7 = 4.7Ω ('R' acts as decimal point)
Default Pick: If your embedded design requires 1% tolerance for analog sensing, voltage dividers, or I2C pull-ups, exclusively specify and verify 4-digit SMD codes (e.g., 1002 for 10kΩ) on your BOM. Ban 3-digit 5% parts from your analog signal paths to eliminate tolerance-induced ADC drift.

Hardware Build: Parts List & Pin Mapping

This build targets the ESP32-WROOM-32 DevKit v1 (30-pin). We use the ESP32's factory-calibrated analogReadMilliVolts() function to bypass the notorious non-linearity of the raw 12-bit ADC at the voltage rails.

Exact Parts List

  • MCU: ESP32-WROOM-32 DevKit v1 (30-pin variant, CP2102 or CH340 USB-UART bridge)
  • Display: 0.96" SSD1306 I2C OLED (128x64 pixels, 4-pin header)
  • Reference Resistor: 10kΩ 0.1% precision through-hole or SMD (e.g., Vishay Y144210K000T0R)
  • Pull-ups: Two 4.7kΩ resistors for I2C SDA/SCL lines
  • Test Probes: Brass SMD tweezers or spring-loaded pogo pins mounted on a breakout board

Pin Mapping Table

ESP32 GPIO Component Function / Notes
GPIO 21 SSD1306 SDA I2C Data (requires 4.7kΩ pull-up to 3.3V)
GPIO 22 SSD1306 SCL I2C Clock (requires 4.7kΩ pull-up to 3.3V)
GPIO 34 Voltage Divider Midpoint ADC1_CH6 input (Input only, no internal pull-up)
3V3 OLED VCC & Divider Top Reference voltage for ADC and I2C logic
GND OLED GND & Divider Bottom Common ground
Bench Warning: Never test SMD resistors while the circuit is powered. This jig is strictly for out-of-circuit component verification. Shorting the 3.3V rail to GND through a low-value test resistor (e.g., < 10Ω) without a current-limiting series resistor will draw >300mA and fry the ESP32's onboard AMS1117 LDO.

Complete ESP32 Firmware for SMD Code Decoding

The following Arduino-framework C++ code measures the unknown resistor, calculates its value, determines the closest standard SMD code, and handles I2C initialization errors gracefully. Ensure you have the Adafruit_SSD1306 and Adafruit_GFX libraries installed via the Library Manager.

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C

#define ADC_PIN 34
#define REF_RESISTOR_OHMS 10000.0
#define VCC_MILLIVOLTS 3300.0

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// EIA-96 Multiplier Letters
const char* eia96_multipliers = "XYZABCD"; 
// Simplified multiplier map for demonstration (X=0.1, Y=0.01, Z=0.001, A=1, B=10, C=100, D=1000)

void setup() {
  Serial.begin(115200);
  
  // Initialize I2C with explicit ESP32 pins
  Wire.begin(21, 22);
  
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("[FATAL] SSD1306 allocation failed"));
    // Halt execution, blink onboard LED if available
    pinMode(2, OUTPUT);
    while(1) { digitalWrite(2, HIGH); delay(250); digitalWrite(2, LOW); delay(250); }
  }
  
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0,0);
  display.println("SMD Code Verifier");
  display.println("Ready...");
  display.display();
  
  analogReadResolution(12); // Ensure 12-bit ADC on ESP32
}

void loop() {
  // Read ADC in millivolts using ESP32 factory eFuse calibration
  int adc_mV = analogReadMilliVolts(ADC_PIN);
  
  // Prevent division by zero or short-circuit readings
  if (adc_mV <= 10) {
    showError("Short Circuit!");
    delay(1000);
    return;
  }
  if (adc_mV >= VCC_MILLIVOLTS - 10) {
    showError("Open Circuit!");
    delay(1000);
    return;
  }
  
  // Voltage Divider Math: V_out = V_in * (R_unknown / (R_ref + R_unknown))
  // Solving for R_unknown: R_unk = R_ref * (V_out / (V_in - V_out))
  float v_out = adc_mV;
  float v_in = VCC_MILLIVOLTS;
  float r_unknown = REF_RESISTOR_OHMS * (v_out / (v_in - v_out));
  
  String smd_code = calculateSMDCode(r_unknown);
  
  display.clearDisplay();
  display.setCursor(0, 0);
  display.setTextSize(1);
  display.println("Measured Resistance:");
  display.setTextSize(2);
  display.print(formatResistance(r_unknown));
  display.setTextSize(1);
  display.setCursor(0, 40);
  display.println("Expected SMD Code:");
  display.setTextSize(2);
  display.setCursor(0, 50);
  display.println(smd_code);
  display.display();
  
  delay(500); // Debounce / sample rate limit
}

String calculateSMDCode(float resistance) {
  // Simplified 4-digit code generator for 1% resistors
  float base = resistance;
  int multiplier = 0;
  
  while (base >= 1000 && multiplier < 9) {
    base /= 10;
    multiplier++;
  }
  
  int sig_figs = round(base);
  char buffer[6];
  sprintf(buffer, "%03d%d", sig_figs, multiplier);
  return String(buffer);
}

String formatResistance(float r) {
  if (r >= 1000000) return String(r / 1000000.0, 2) + " M";
  if (r >= 1000) return String(r / 1000.0, 2) + " k";
  return String(r, 1) + " R";
}

void showError(const char* msg) {
  display.clearDisplay();
  display.setCursor(0, 20);
  display.setTextSize(2);
  display.println(msg);
  display.display();
}

Debugging: i2cWriteReadNonBlocking Returned Error -1

When interfacing I2C OLEDs with the ESP32 Arduino core, the most common runtime failure is the I2C bus timeout. You will see this exact error string in the Serial Monitor:

[E][Wire.cpp:193] requestFrom(): i2cWriteReadNonBlocking returned Error -1

This error means the ESP32's I2C peripheral sent a clock pulse but never received an ACK (acknowledge) bit from the slave device. Here are the ranked causes and fixes:

  1. Missing I2C Pull-Up Resistors (Most Likely): The SSD1306 breakout boards rarely include onboard pull-ups. The ESP32's internal pull-ups (approx. 45kΩ) are too weak to pull the bus high fast enough at 400kHz. Fix: Solder physical 4.7kΩ resistors between SDA and 3.3V, and SCL and 3.3V.
  2. Incorrect I2C Address: The code assumes 0x3C. Many 128x64 OLEDs ship with address 0x3D. Fix: Run an I2C scanner sketch. If the device responds at 0x3D, update SCREEN_ADDRESS in the code.
  3. SDA and SCL Swapped: Silkscreen labels on cheap clone boards are frequently reversed. Fix: Swap the physical wires on GPIO 21 and 22.
  4. Wrong Protocol Module: You might have purchased an SPI variant of the SSD1306 (7-pin) instead of the I2C variant (4-pin). Fix: Check the back of the PCB. If you see a 7-pin header or a 'BS' jumper pad, it is SPI. Replace with a 4-pin I2C module.
The First Three Things to Check When It Fails:
1. Measure the voltage on the SDA and SCL pins with a multimeter; both must read ~3.3V when idle.
2. Verify continuity from the OLED GND pin to the ESP32 GND pin.
3. Confirm the OLED VCC pin is receiving exactly 3.3V (not 5V, which can damage the ESP32 GPIO pins if the OLED lacks level shifting).

Reference Chart: EIA-96 SMD Code Multipliers

While 3-digit and 4-digit codes are straightforward, the EIA-96 standard is notoriously difficult to memorize. It uses a two-digit code representing one of 96 standard 1% values, followed by a letter multiplier. For a complete lookup table of the 96 base values, refer to the manufacturer datasheets or standard IEC 60062 charts. Below is the critical multiplier letter map required to decode the final value.

Letter Code Multiplier Example Base (47 = 301Ω) Final Resistance
Z0.00147Z0.301 Ω
Y or R0.0147Y3.01 Ω
X or S0.147X30.1 Ω
A147A301 Ω
B or H1047B3.01 kΩ
C10047C30.1 kΩ
D1,00047D301 kΩ
E10,00047E3.01 MΩ
F100,00047F30.1 MΩ

Extending and Simplifying the Build

Depending on your bench volume and budget, you can scale this verification jig up or down.

How to Extend: Add Visual OCR via ESP32-CAM

For high-volume SMD sorting, replace the DevKit v1 with an ESP32-CAM (OV2640). Mount the camera on a fixed stand 5cm above the test pads. Use the esp-tflite-micro library to run a lightweight quantized MobileNet model trained on EIA-96 markings. The camera reads the physical text on the resistor casing, while the ADC verifies the actual electrical value, cross-checking the two to flag counterfeit or mislabeled components.

How to Simplify: Drop the OLED for Serial Plotting

If you only need to verify a handful of resistors and don't want to wire an I2C display, delete the Adafruit_SSD1306 dependencies. Replace the display logic with Serial.printf("Measured: %.2f ohms | Code: %s\n", r_unknown, smd_code.c_str());. Open the Arduino IDE Serial Plotter to watch the ADC noise floor stabilize when the tweezers make firm contact with the SMD pads.

By anchoring your bench workflow to a hardware verifier rather than visual inspection alone, you eliminate the most common source of embedded analog debugging: the silent tolerance failure. Stick to 4-digit 1% codes, verify with the ADC jig, and your prototype will power up correctly the first time.