What is Decimal Code in Embedded Systems?

When a software engineer hears 'decimal code,' they usually think of base-10 string parsing (like converting '123' to an integer). But when you are wiring up hardware, decimal code almost always refers to Binary-Coded Decimal (BCD).

BCD is a method of encoding decimal numbers (0-9) where each individual decimal digit is represented by its own 4-bit binary sequence. Instead of converting the entire number '95' into pure binary (0101 1111), BCD splits it into two nibbles: '9' becomes 1001 and '5' becomes 0101.

Why do we still use this seemingly inefficient format in 2026? Because hardware like 7-segment display decoders (e.g., the CD4511BE), digital thumbwheel switches, and legacy industrial PLCs are hardwired to expect 4-bit BCD inputs. It eliminates the need for complex binary-to-decimal math in the firmware, offloading the display logic to dedicated silicon.

Number Representation Formats Compared

Format Bit Width for '9' Binary Example ('9') Primary Hardware Use Case Trade-offs
Pure Binary 4 bits 1001 Internal MCU math, memory storage Dense and efficient, but requires math to drive human-readable displays.
BCD (8421 Code) 4 bits per digit 1001 Driving CD4511/74LS47 decoders, RTCs Direct hardware mapping; wastes 6 states per nibble (1010-1111).
ASCII Decimal 8 bits per digit 0011 1001 UART/Serial comms, LCD character screens Human-readable over serial; highly inefficient for memory and logic gates.
One-Hot (Ring) 10 bits 0000100000 State machines, rotary encoders Glitch-resistant state transitions; uses too many GPIO pins for general math.

Hardware Build: ESP32 Keypad to BCD 7-Segment Display

To see decimal code in action, we are going to build a circuit that reads a decimal input from a 4x4 matrix keypad, translates it in firmware, and outputs the 4-bit BCD code to a CD4511BE decoder driving a common-cathode 7-segment display.

Target Board Variant: This guide and code specifically target the ESP32-WROOM-32 DevKit v1 (30-pin variant). If you are using the 38-pin variant, the physical pin numbers shift, but the GPIO numbers in the code remain identical.

Parts List

  • MCU: ESP32-WROOM-32 DevKit v1 (30-pin)
  • Decoder: Texas Instruments CD4511BE (BCD-to-7-Segment Latch/Decoder)
  • Display: Single-digit Common Cathode 7-Segment (e.g., Lite-On LTS-5467HR)
  • Input: 4x4 Membrane Matrix Keypad (8-pin ribbon)
  • Passives: 4x 220Ω resistors (for display current limiting), 1x 10kΩ pull-down resistor (optional for noisy environments)

Pin Mapping Table

We intentionally avoid GPIO 12 for the keypad matrix. On the ESP32, GPIO 12 is a strapping pin; if it is pulled high during boot, the board will enter the wrong flash voltage mode and fail to boot.

Component / Function ESP32 GPIO CD4511BE / Keypad Pin Notes
Keypad Row 1 13 Keypad Pin 1 Internal pull-up enabled in code
Keypad Row 2 4 Keypad Pin 2 Avoided GPIO 12 (strapping pin)
Keypad Row 3 14 Keypad Pin 3
Keypad Row 4 27 Keypad Pin 4
Keypad Col 1-4 26, 25, 33, 32 Keypad Pins 5-8
BCD Input A (LSB) 16 Pin 7 (A) Weight = 1
BCD Input B 17 Pin 1 (B) Weight = 2
BCD Input C 18 Pin 2 (C) Weight = 4
BCD Input D (MSB) 19 Pin 6 (D) Weight = 8
Lamp Test (LT) 21 Pin 3 (LT) Active LOW. Set HIGH for normal op.
Blanking (BL) 22 Pin 4 (BL) Active LOW. Set HIGH for normal op.
Display Common Cathode GND Pins 3 & 8 (Display) Use 220Ω resistors on anodes (a-g)

Firmware: Parsing and Outputting Decimal Code

The following Arduino C++ code reads the keypad, validates that the input is a decimal digit (0-9), extracts the individual bits using bitwise operations, and writes the BCD code to the CD4511BE.

#include <Keypad.h>

// --- TARGET BOARD: ESP32-WROOM-32 DevKit v1 (30-pin) ---

// 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'}
};
byte rowPins[ROWS] = {13, 4, 14, 27}; 
byte colPins[COLS] = {26, 25, 33, 32}; 
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

// CD4511BE BCD & Control Pins
const int PIN_BCD_A = 16; // LSB (Weight 1)
const int PIN_BCD_B = 17; // Weight 2
const int PIN_BCD_C = 18; // Weight 4
const int PIN_BCD_D = 19; // MSB (Weight 8)
const int PIN_LT = 21;    // Lamp Test (Active LOW)
const int PIN_BL = 22;    // Blanking (Active LOW)

void setup() {
  Serial.begin(115200);
  while(!Serial) { delay(10); }
  
  // Initialize BCD Output Pins
  pinMode(PIN_BCD_A, OUTPUT);
  pinMode(PIN_BCD_B, OUTPUT);
  pinMode(PIN_BCD_C, OUTPUT);
  pinMode(PIN_BCD_D, OUTPUT);
  
  // Initialize Control Pins (Both must be HIGH for normal display operation)
  pinMode(PIN_LT, OUTPUT);
  pinMode(PIN_BL, OUTPUT);
  digitalWrite(PIN_LT, HIGH); 
  digitalWrite(PIN_BL, HIGH); 
  
  Serial.println('System Ready. Press 0-9 on keypad.');
}

void loop() {
  char key = keypad.getKey();
  
  if (key) {
    // Error Handling: Ensure the key is a valid decimal digit
    if (key >= '0' && key <= '9') {
      int decimalValue = key - '0'; // Convert ASCII char to integer (0-9)
      outputBCD(decimalValue);
      Serial.print('Decimal Code Output: '); Serial.println(decimalValue);
    } 
    else {
      // Handle non-decimal keys (A, B, C, D, *, #)
      Serial.print('Ignored non-decimal input: '); Serial.println(key);
      // Optional: Blank the display for non-numeric input
      digitalWrite(PIN_BL, LOW); 
      delay(500);
      digitalWrite(PIN_BL, HIGH);
    }
  }
}

// Function to write 4-bit BCD to hardware pins
void outputBCD(int num) {
  // Bitwise AND to isolate each bit of the decimal code
  digitalWrite(PIN_BCD_A, (num & 0x01) ? HIGH : LOW);
  digitalWrite(PIN_BCD_B, (num & 0x02) ? HIGH : LOW);
  digitalWrite(PIN_BCD_C, (num & 0x04) ? HIGH : LOW);
  digitalWrite(PIN_BCD_D, (num & 0x08) ? HIGH : LOW);
}

Debugging: When Your Decimal Output Fails

Hardware BCD circuits are notorious for silent failures or displaying the wrong segments. If your build isn't working, here is the exact decision path to fix it.

Compilation Error: fatal error: Keypad.h: No such file or directory

Ranked Causes & Fixes:

  1. Missing Library: You haven't installed the library. Go to Sketch > Include Library > Manage Libraries, search for 'Keypad' by Mark Stanley, and install it.
  2. Wrong Board Selected: If the IDE is trying to compile for an AVR board but pulling ESP32 paths, restart the IDE and ensure 'ESP32 Dev Module' is selected in the Boards Manager.

The First Three Things to Check (Hardware)

If the code compiles and uploads, but the 7-segment display is dark, stuck on '8', or showing garbage, check these three physical layer issues:

  1. Verify LT and BL Pin States: The CD4511BE has internal pull-ups on the Lamp Test (LT) and Blanking (BL) pins, but floating inputs in a noisy breadboard environment can cause erratic behavior. If LT is pulled LOW, all segments turn on (display reads '8' with decimal point). If BL is pulled LOW, the display goes completely dark. Ensure your ESP32 is actively driving these HIGH, or physically tie them to 3.3V via a 10kΩ resistor.
  2. Check for the 'Invalid BCD' Lockout: If your ESP32 accidentally outputs a binary value between 10 and 15 (e.g., 1010 to 1111), the CD4511BE will intentionally blank the display to indicate an invalid decimal code. Use a multimeter to verify GPIOs 16-19 are not stuck HIGH simultaneously.
  3. Common Cathode vs. Common Anode: The CD4511BE only sources current. It is designed exclusively for Common Cathode displays. If you are using a Common Anode display, the logic is inverted, and the decoder cannot sink the required current. Check your display datasheet; if it's Common Anode, you need a 74LS47 decoder instead.

Extending and Simplifying the Build

Once you have a single digit working, you will inevitably want to scale the project. Here is how to adapt the decimal code architecture for different use cases.

Simplifying: Direct Port Manipulation

If you are driving the BCD outputs in a high-speed interrupt routine (e.g., multiplexing four 7-segment displays), calling digitalWrite() four times per digit is too slow. On the ESP32, you can write the entire BCD nibble to a GPIO register in a single clock cycle. Assuming you map your BCD pins to consecutive GPIOs (like GPIO 16, 17, 18, 19), you can use direct port manipulation:

// Clear the 4 bits, then write the new decimal code
GPIO.out_w1tc = (0xF << 16); // Clear GPIO 16-19
GPIO.out_w1ts = ((num & 0xF) << 16); // Set GPIO 16-19 to BCD value

Extending: Multi-Digit BCD Multiplexing

To display '42', you don't use two CD4511BE chips and 8 GPIOs. Instead, you use a single CD4511BE and multiplex the displays using NPN transistors (like the 2N2222) on the common cathode pins.

The firmware rapidly switches the transistor for the 'Tens' digit ON, writes the BCD code for '4', waits 5ms, turns it OFF, switches the 'Ones' transistor ON, writes the BCD code for '2', and repeats. Because of persistence of vision, the human eye reads '42'. This is where understanding decimal code transitions from a simple hardware wiring exercise into a firmware timing challenge.

For deeper hardware specifications on the decoder logic and truth tables, refer to the Texas Instruments CD4511B Datasheet. For ESP32 GPIO strapping pin constraints and register mapping, consult the official Espressif GPIO API Reference.