Binary Decimal Code (BCD) is a digital encoding scheme where each decimal digit (0 through 9) is represented by its own four-bit binary nibble. Unlike pure binary, which would encode the number 25 as 11001, BCD encodes it as two separate nibbles: 0010 (2) and 0101 (5). In embedded hardware, you will frequently encounter BCD when interfacing with legacy industrial equipment, DIP switches for I2C addressing, or panel-mount thumbwheel switches used for human-readable setpoints.

The direct answer to reading a 4-bit BCD switch on a microcontroller is to assign four GPIO pins with pull-up resistors, read the digital states, and combine them using bitwise left-shift operators: (bit3 << 3) | (bit2 << 2) | (bit1 << 1) | bit0. This guide walks through the exact hardware, C++ implementation, and debugging steps for reading BCD on the ESP32.

Parts List & Pin Mapping

This build targets the ESP32 DevKit V1 (30-pin variant). Do not use the 38-pin variant without adjusting the physical breadboard layout, as the pinout differs slightly. We are using a mechanical BCD thumbwheel switch, which requires external pull-up resistors for noise immunity in typical workshop environments.

ComponentExact Variant / SpecificationQty
MicrocontrollerESP32 DevKit V1 (30-pin, Type-C USB, ESP32-WROOM-32)1
BCD InputCTS Electrocomponents 157 Series BCD Thumbwheel (or C&K equivalent)1
Pull-up Resistors10kΩ 1/4W Through-Hole (Metal Film)4
Bypass Capacitor100nF (0.1µF) Ceramic Disc (for debounce)1
Prototyping830-point Solderless Breadboard & 22 AWG Jumper Wire Kit1

ESP32 Pin Mapping Table

ESP32 GPIOBCD Switch PinFunctionNotes
GPIO 16Pin 1 (Bit 0 / 1s)LSB InputExternal 10kΩ pull-up to 3.3V
GPIO 17Pin 2 (Bit 1 / 2s)Bit 1 InputExternal 10kΩ pull-up to 3.3V
GPIO 18Pin 4 (Bit 2 / 4s)Bit 2 InputExternal 10kΩ pull-up to 3.3V
GPIO 19Pin 8 (Bit 3 / 8s)MSB InputExternal 10kΩ pull-up to 3.3V
GNDPin C (Common)Switch GroundMust share ground with ESP32

Wiring the BCD Input Circuit

Mechanical BCD switches use a common ground architecture. When a specific bit is 'active' (logic 1 for the selected digit), the internal wiper connects that bit's output pin to the common ground pin. Therefore, the output pins sit floating or high when inactive, and are pulled low when active. We use a pull-up resistor configuration.

  1. Establish Power Rails: Connect the ESP32 3V3 pin to the breadboard's positive rail and GND to the negative rail.
  2. Install Pull-ups: Insert four 10kΩ resistors across the breadboard gap. Connect one leg of each to the 3V3 positive rail.
  3. Wire Signal Lines: Connect the free leg of the resistors to ESP32 GPIOs 16, 17, 18, and 19 respectively. Run jumper wires from these same junction points to the BCD switch pins 1, 2, 4, and 8.
  4. Ground the Switch: Connect the BCD switch Common (C) pin directly to the breadboard negative (GND) rail.
  5. Add Hardware Debounce: Solder or insert a 100nF ceramic capacitor between the Common pin and the Bit 0 pin. This creates a low-pass RC filter with the 10kΩ pull-up, smoothing out mechanical contact bounce without relying entirely on software delays.
Callout Tip: While the ESP32 has internal pull-up resistors (typically 45kΩ), they are often too weak for panel-mount switches running through long wires in electrically noisy environments. Always use external 10kΩ pull-ups for reliable BCD reading on the jobsite.

The C++ Code: Binary Decimal Code Conversion

The following code is fully compilable in the Arduino IDE (Core v2.x or v3.x) or PlatformIO. It targets the ESP32 DevKit V1 30-pin. It reads the pins, applies bitwise shifts to assemble the nibble, and includes critical error handling for invalid BCD states (values 10 through 15, which are mathematically possible in 4-bit binary but illegal in strict BCD).

// Target Board: ESP32 DevKit V1 (30-pin)
// Framework: Arduino / ESP32 Core

#include <Arduino.h>

// Pin Definitions (Must match wiring table)
const uint8_t PIN_BCD_BIT0 = 16;
const uint8_t PIN_BCD_BIT1 = 17;
const uint8_t PIN_BCD_BIT2 = 18;
const uint8_t PIN_BCD_BIT3 = 19;

// Timing for software debounce fallback
unsigned long lastReadTime = 0;
const unsigned long debounceDelay = 50; 

void setup() {
  Serial.begin(115200);
  delay(1000); // Allow serial monitor to connect
  
  // Configure pins as inputs. 
  // We use INPUT_PULLUP as a safety net, though external 10k resistors do the heavy lifting.
  pinMode(PIN_BCD_BIT0, INPUT_PULLUP);
  pinMode(PIN_BCD_BIT1, INPUT_PULLUP);
  pinMode(PIN_BCD_BIT2, INPUT_PULLUP);
  pinMode(PIN_BCD_BIT3, INPUT_PULLUP);
  
  Serial.println("BCD Reader Initialized. Awaiting switch input...");
}

void loop() {
  unsigned long currentMillis = millis();
  
  if (currentMillis - lastReadTime >= debounceDelay) {
    lastReadTime = currentMillis;
    
    // Read pins. Invert logic (!) because switch pulls LOW when active.
    uint8_t b0 = !digitalRead(PIN_BCD_BIT0);
    uint8_t b1 = !digitalRead(PIN_BCD_BIT1);
    uint8_t b2 = !digitalRead(PIN_BCD_BIT2);
    uint8_t b3 = !digitalRead(PIN_BCD_BIT3);
    
    // Assemble the 4-bit nibble using bitwise left-shift
    uint8_t bcd_value = (b3 << 3) | (b2 << 2) | (b1 << 1) | b0;
    
    // Error Handling: Strict BCD only allows 0-9. 
    // 10-15 indicates a wiring fault, broken switch wiper, or ESD glitch.
    if (bcd_value > 9) {
      Serial.print("[ERROR] Invalid BCD State Detected: ");
      Serial.print(bcd_value);
      Serial.println(" (Binary 1010-1111 are illegal in BCD). Check switch common ground.");
    } else {
      Serial.print("Valid BCD Decimal: ");
      Serial.println(bcd_value);
    }
  }
}

Debugging: Compiler Errors and Bitwise Fails

When migrating older Arduino BCD tutorials to the ESP32, or when the hardware simply refuses to read correctly, you will hit specific roadblocks. Here is the decision path for the most common failures.

The 'Not Declared in This Scope' Compiler Error

If you copied legacy code that defines BCD masks using the old Arduino binary prefix, you will see this exact error string during compilation:

error: 'B1001' was not declared in this scope

Ranked Causes:

  1. Deprecated Macro: The B1001 macro was a hack in early Arduino AVR cores. The ESP32 GCC compiler does not include it by default. Fix: Replace all instances of B1001 with the standard C++14 binary literal prefix: 0b1001.
  2. Missing Header (PlatformIO): If using PlatformIO, the Arduino macros aren't auto-included in the same way. Fix: Ensure #include <Arduino.h> is at the very top of your .cpp file.

First 3 Things to Check When Hardware Fails

If the code compiles but the Serial Monitor outputs random numbers or the [ERROR] Invalid BCD State warning, run this diagnostic sequence:

  1. Measure the Voltage Swing: Set your multimeter to DC Volts. Probe GPIO 16 while rotating the switch. You must see a clean swing from ~3.28V (High) to <0.1V (Low). If it only drops to 1.5V, your common ground wire is broken or you have a short.
  2. Verify the Common Ground: The BCD switch and the ESP32 must share the exact same ground plane. If the switch is powered by a separate bench supply, the grounds must be bonded. A floating ground will cause the ESP32's internal protection diodes to forward-bias, resulting in erratic ghost readings.
  3. Check for Contact Bounce: If you see the value flicker between the correct number and an 'Invalid State' (like 15) for a millisecond when turning the dial, the mechanical wipers are bouncing across multiple contacts simultaneously. Add a 100nF capacitor across the bit pins, or increase the debounceDelay in the code to 150ms.

Decision Tree: Selecting Your Multi-Position Input

BCD thumbwheel switches are excellent for specific use cases, but they consume a lot of GPIO pins. Use this decision matrix to finalize your hardware choice.

RequirementBCD ThumbwheelAnalog Resistor LadderQuadrature Rotary Encoder
User needs exact visual confirmation of digitYes (Printed numbers)No (Requires screen)No (Requires screen)
GPIO Pin Cost (for 0-9 input)4 Pins1 Pin (ADC)2 Pins
Immunity to EMI / Voltage DropHigh (Digital)Low (Analog drift)Medium (Digital)
Cost per unit (approx)$4.00 - $8.00$0.10$1.50 - $3.00
Default Pick: If your project requires a panel-mount setpoint where the operator must visually verify the exact integer (e.g., setting a kiln temperature multiplier or a timer preset) without looking at an LCD screen, buy the CTS 157 Series BCD Thumbwheel. If you just need a general-purpose dial for a menu system, abandon BCD and use a rotary encoder.

Extending and Simplifying the Build

Once you have a single BCD digit reading reliably, you will inevitably need to scale the system.

How to Extend (Multi-Digit BCD)

If you need a two-digit BCD input (00-99), wiring two switches directly will consume 8 GPIO pins. Instead, use a CD4051B 8-channel analog multiplexer. Wire both BCD switches to the multiplexer's inputs, and use 3 ESP32 GPIO pins to select which switch's nibble is routed to the ESP32's reading pins. This keeps your pinout compact while allowing you to read dozens of BCD digits sequentially.

How to Simplify (Running out of Pins)

If your ESP32 is already managing WiFi, I2C sensors, and SPI displays, you cannot afford to waste 4 pins on a switch. Simplify the build by replacing the raw BCD switch and bitwise math with a M5Stack Unit Encoder or an I2C capacitive keypad (like the MPR121). Alternatively, use a dedicated BCD-to-I2C expander chip like the PCF8574. The PCF8574 reads the 4 BCD pins locally and sends the decimal value to the ESP32 over the I2C bus, reducing your hardware footprint to just two shared I2C wires.

For deeper reading on ESP32 GPIO architecture and internal pull-up limitations, refer to the official Espressif GPIO API Reference. For foundational logic theory on how BCD nibbles map to 7-segment displays and hardware decoders, review the Texas Instruments SN74LS47 BCD-to-7-Segment Datasheet, which remains the gold standard for understanding BCD hardware translation.