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. |
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 address0x3D. Run an I2C scanner sketch to verify. ChangeSCREEN_ADDRESSin 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.






