When you are probing the I2C lines of a DS3231 Real Time Clock (RTC) module or debugging a legacy PLC register, context-switching to a web-based binary converter on your phone breaks your flow. A dedicated, physical BCD code calculator on your workbench solves this. Binary Coded Decimal (BCD) represents each decimal digit with its own 4-bit binary sequence. For example, the decimal number 147 in pure binary is 10010011, but in BCD it is 0001 0100 0111. Mistaking one for the other is the root cause of countless embedded timing bugs.

This guide walks through building a tactile bench tool that takes decimal input from a keypad and instantly calculates and displays the pure binary, hexadecimal, and BCD representations. We will use an ESP32 for its fast I2C handling and ample GPIO, terminating in a specific, optimized hardware pick.

Component Selection and Decision Matrix

Before wiring, we need to make a concrete hardware decision. The right combination depends on what you are debugging.

If your primary use case is... Choose this Input/Display combo Why
Visualizing hardware logic levels (TTL) 8 DIP switches + 74LS47 decoder + 7-segment Pure hardware, no code, shows physical high/low states.
Quick decimal-to-BCD software debugging 4x4 Matrix Keypad + TM1637 4-digit display Low pin count, but limited to showing one format at a time.
Deep debugging (RTC, PLC, memory mapping) 4x4 Keypad + SSD1306 I2C OLED (Default Pick) Displays Dec, Hex, Bin, and BCD simultaneously. High information density.
The Verdict: For a modern embedded workbench, the ESP32-WROOM-32 DevKit V1 (30-pin) paired with a 0.96" SSD1306 I2C OLED and a 4x4 membrane keypad is the definitive pick. It costs under $15 total, requires only 12 GPIO pins, and provides the multi-line display real estate necessary to compare BCD against pure binary side-by-side.

Exact Parts List and Pin Mapping

Do not use random GPIO pins on the ESP32. The ESP32-WROOM-32 has specific strapping pins that will cause boot loops if pulled high or low during startup. We avoid GPIO 0, 2, 5, 12, and 15 for our keypad matrix.

Bill of Materials (BOM)

  • MCU: ESP32-WROOM-32 DevKit V1 (30-pin variant, not the 38-pin ESP32-S3)
  • Display: 0.96-inch SSD1306 OLED (I2C interface, 4 pins: GND, VCC, SCL, SDA)
  • Input: 4x4 Membrane Matrix Keypad (8-pin ribbon)
  • Wiring: 22 AWG solid core jumper wires, breadboard

Spec-Sheet-Table: Pin Mapping

Component Component Pin ESP32 GPIO Notes / Constraints
SSD1306 OLED VCC 3V3 Do not use 5V; the SSD1306 is strictly 3.3V logic.
SSD1306 OLED GND GND Common ground.
SSD1306 OLED SCL GPIO 22 Default ESP32 I2C clock. Internal pull-ups usually suffice.
SSD1306 OLED SDA GPIO 21 Default ESP32 I2C data.
4x4 Keypad Row 1 to 4 GPIO 13, 14, 27, 26 Rows driven LOW sequentially.
4x4 Keypad Col 1 to 4 GPIO 25, 33, 32, 23 Columns read with internal pull-ups enabled.

Complete Compilable Firmware

This firmware targets the Arduino IDE (ESP32 core by Espressif Systems). You must install the Keypad library by Mark Stanley and the Adafruit SSD1306 + Adafruit GFX libraries via the Library Manager before compiling.

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

// --- Display Configuration ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET   -1
#define SCREEN_ADDRESS 0x3C // Change to 0x3D if your specific OLED uses that address
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// --- Keypad Configuration ---
const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'}
};

// Pin mapping from our spec table
byte rowPins[ROWS] = {13, 14, 27, 26}; 
byte colPins[COLS] = {25, 33, 32, 23}; 

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

String currentInput = "";

// --- BCD Conversion Logic ---
// Converts an integer to a formatted BCD string (e.g., 147 -> "0001 0100 0111")
String getBCDString(unsigned int num) {
  if (num == 0) return "0000";
  String bcd = "";
  String sNum = String(num);
  for (int i = 0; i < sNum.length(); i++) {
    int digit = sNum.charAt(i) - '0';
    for (int j = 3; j >= 0; j--) {
      bcd += (bitRead(digit, j) ? "1" : "0");
    }
    if (i < sNum.length() - 1) bcd += " ";
  }
  return bcd;
}

void updateDisplay(unsigned int val) {
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("DEC: "); display.println(val);
  
  display.print("HEX: 0x"); display.println(val, HEX);
  
  display.print("BIN: ");
  // Pad binary to 16 bits for visual alignment
  String binStr = String(val, BIN);
  while(binStr.length() < 16) binStr = "0" + binStr;
  display.println(binStr);
  
  display.setCursor(0, 40);
  display.setTextSize(1);
  display.print("BCD: ");
  display.setTextSize(1); // BCD can be long, keep size 1
  display.println(getBCDString(val));
  
  display.display();
}

void setup() {
  Serial.begin(115200);
  
  // Initialize OLED with error handling
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    while(true) { delay(100); } // Halt execution
  }
  
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 20);
  display.println("BCD CALC");
  display.display();
  delay(1000);
  
  updateDisplay(0);
}

void loop() {
  char key = keypad.getKey();
  
  if (key) {
    if (key >= '0' && key <= '9') {
      if (currentInput.length() < 4) { // Max 9999
        currentInput += key;
      }
    } else if (key == '*') {
      currentInput = ""; // Clear
    } else if (key == '#') {
      // Enter/Calculate - do nothing, display updates live
    }
    
    unsigned int val = currentInput.length() > 0 ? currentInput.toInt() : 0;
    updateDisplay(val);
    Serial.printf("Input: %s | BCD: %s\n", currentInput.c_str(), getBCDString(val).c_str());
  }
}

Debugging: First Three Things to Check When It Fails

Hardware builds rarely work perfectly on the first power-up. If your BCD code calculator fails, follow this ranked troubleshooting path.

1. The Display Stays Black or Throws an Allocation Error

Exact Error String: SSD1306 allocation failed (printed to Serial Monitor) or the compiler throws fatal error: Adafruit_SSD1306.h: No such file or directory.

  • Cause A (I2C Address Mismatch): The code defaults to 0x3C. Many cheap clone OLEDs ship with the address 0x3D. Run an I2C scanner sketch to verify. Change SCREEN_ADDRESS in the code if needed.
  • Cause B (Missing Pull-ups): While the ESP32 enables internal pull-ups, some generic SSD1306 breakout boards lack the physical 4.7kΩ resistors on the SDA/SCL lines. If the screen flickers or fails to initialize, solder 4.7kΩ resistors between VCC and SDA/SCL.
  • Cause C (Library Missing): If it is a compilation error, open Tools > Manage Libraries and install the Adafruit SSD1306 and GFX libraries.

2. Keypad Ghosting or No Input Registering

Symptom: Pressing '5' registers as '2', or pressing a key does nothing.

  • Cause A (Strapping Pin Conflict): If you ignored the pin mapping table and used GPIO 12, the ESP32 will fail to boot or read the pin incorrectly because GPIO 12 dictates flash voltage selection. Move the wire to GPIO 25.
  • Cause B (Ribbon Cable Seating): Membrane keypads use fragile ZIF-style ribbon cables. Ensure the ribbon is pushed fully into the breadboard. If the contacts are bent, strip a 22 AWG wire, bend it into a U-shape, and use it to bridge the ribbon to the breadboard contacts securely.

3. BCD Output Looks Like Pure Binary

Symptom: You type 12, and the BCD line shows 1100 (which is 12 in pure binary) instead of 0001 0010.

  • Cause: You are looking at the "BIN" line instead of the "BCD" line, or the getBCDString() function was altered. Pure binary converts the whole number at once. BCD converts each individual decimal digit into a 4-bit nibble. Verify the OLED text labels and ensure the custom BCD function is intact.

How to Extend or Simplify the Build

Once the baseline calculator is working, you can adapt it to your specific bench constraints.

Simplify: The TM1637 Route

If you only need to see the BCD output for quick RTC debugging and want to eliminate the I2C OLED complexity, swap the SSD1306 for a TM1637 4-digit 7-segment display. Trade-off: You lose the ability to see Hex and Pure Binary simultaneously, and you must map the BCD nibbles to the TM1637's custom segment encoding (since standard 7-segment displays don't natively show '1010'). You will need to write a custom font array for the TM1637 library to render binary 1s and 0s on the segments.

Extend: Logic Analyzer Triggering

For advanced debugging, add a hardware output that pulses a physical wire whenever a BCD calculation occurs. Implementation: Wire an optocoupler (like the PC817) to GPIO 4. In the loop(), when the # key is pressed, pulse GPIO 4 HIGH for 10ms. Connect the optocoupler output to your oscilloscope or logic analyzer's external trigger input. This allows you to type a BCD value on the keypad and instantly trigger your scope to capture the exact moment your target microcontroller shifts that BCD data onto an SPI or I2C bus.

Building a dedicated hardware tool bridges the gap between abstract software math and physical wire-level debugging. Keep this BCD code calculator next to your multimeter; it will pay for itself the first time you catch a mismatched RTC register.