Binary numbers are a base-2 numeral system using only 0s and 1s to represent discrete electrical states, such as 0V (LOW) and 3.3V (HIGH), in digital logic circuits. Knowing how to list binary numbers changes your approach from guessing pin states to precisely manipulating hardware registers, configuring I2C multiplexers, and setting GPIO port masks. Beginners commonly confuse raw binary values with binary coding schemes like Gray code or Binary-Coded Decimal (BCD), which are specific mapping rules for encoders and displays rather than the underlying base-2 mathematics used for direct hardware control.

The Core Mechanism: Base-2 vs Base-10 at the Bench

When you write code for a microcontroller, the compiler handles base-10 (decimal) to base-2 (binary) conversion automatically. However, when you are debugging a logic analyzer trace or writing directly to a hardware register, you must think in binary. Each digit (bit) represents a power of 2, starting from 2^0 on the far right (the Least Significant Bit, or LSB) and increasing as you move left.

Worked Numeric Example: The 0xAA Bus Test Pattern

Suppose you need to test an 8-bit data bus or a port of GPIO pins to ensure no adjacent pins are shorted together. The standard industry practice is to write an alternating pattern: HIGH, LOW, HIGH, LOW. In binary, this is listed as 10101010.

Let us convert 10101010 to decimal to verify the value you would pass to a function like digitalWrite() or a port register:

  • Bit 7 (1): 1 × 128 = 128
  • Bit 6 (0): 0 × 64 = 0
  • Bit 5 (1): 1 × 32 = 32
  • Bit 4 (0): 0 × 16 = 0
  • Bit 3 (1): 1 × 8 = 8
  • Bit 2 (0): 0 × 4 = 0
  • Bit 1 (1): 1 × 2 = 2
  • Bit 0 (0): 0 × 1 = 0

Summing the active bits: 128 + 32 + 8 + 2 = 170. In hexadecimal, this is 0xAA. If your logic analyzer shows a decimal 170 on the bus, you instantly recognize it as the alternating 0xAA test pattern.

Where You Meet This in Practice: ESP32 and Arduino

Listing binary numbers is not just an academic exercise; it is a daily requirement when working with modern embedded systems. You will encounter base-2 logic in three primary areas on the workbench:

1. Direct GPIO Register Manipulation
On the ESP32, toggling individual pins using digitalWrite() is slow because the Arduino core performs safety checks and pin mapping. For high-speed bit-banging (like driving WS2812B addressable LEDs), you write directly to the GPIO_OUT_W1TS_REG (Write 1 to Set) and GPIO_OUT_W1TC_REG (Write 1 to Clear). These 32-bit registers require you to list binary masks to target specific pins without disturbing the others. According to the Espressif ESP32 Technical Reference Manual, writing a 1 to bit 2 of the W1TS register sets GPIO2 HIGH, while leaving all other bits as 0 ensures pins 4, 5, and 18 remain untouched.

2. Shift Registers (e.g., 74HC595)
When you run out of GPIO pins, you use an 8-bit shift register like the NXP 74HC595. You clock in 8 bits serially. If you want to turn on relays connected to outputs Q0, Q3, and Q7, you must list the binary number 10001001 (decimal 137) and shift it out MSB-first or LSB-first, depending on your wiring.

3. I2C Addressing and Multiplexing
I2C addresses are 7-bit or 10-bit values. A common OLED display (SSD1306) has an I2C address of 0x3C. In binary, 0x3C is 0111100. Understanding this binary list is critical when you are pulling A0/A1/A2 address pins HIGH or LOW on sensors like the MCP23017 I/O expander to create a custom address mask.

Real-World Scenario Walkthrough: Bricking an I2C Bus with a Bad Bitmask

The most common point of failure when makers transition from basic Arduino sketches to intermediate hardware design is confusing decimal channel indices with binary bitmasks. Here is a real-world bench scenario.

The Setup:
You are building an environmental monitor using an ESP32 and three BME280 temperature/humidity sensors. Because all three BME280s share the same default I2C address (0x76), you wire them through a Texas Instruments TCA9548A I2C multiplexer. The TCA9548A allows you to route the main I2C bus to one of 8 downstream channels (0 through 7) by writing a single byte to its control register.

The Numbers:
The TCA9548A control register uses the lower 3 bits to select the active channels.

  • Channel 0 requires binary 00000001 (Decimal 1)
  • Channel 1 requires binary 00000010 (Decimal 2)
  • Channel 2 requires binary 00000100 (Decimal 4)
To select Channel 2, the bitmask must be decimal 4.

The Outcome:
In your C++ code, you write a function selectChannel(2) to read the third sensor. Inside the function, you send the number 2 directly to the Wire.write() command, assuming "2" means "Channel 2". The ESP32 compiles and runs, but the serial monitor prints NaN (Not a Number) for the temperature. The logic analyzer shows the I2C bus scanning the wrong physical wires.

What Went Wrong:
You sent decimal 2, which is binary 00000010. Looking at the TCA9548A datasheet, 00000010 enables Channel 1, not Channel 2. Because no sensor was wired to Channel 1, the ESP32 received no ACK (acknowledge) bit and read floating garbage data.

The Fix: Use Bitshifting

Never hardcode decimal equivalents for mux channels. Instead, use the bitshift operator to generate the correct binary list dynamically based on the channel index:

uint8_t channel = 2;
uint8_t bitmask = 1 << channel; // Shifts binary 00000001 left by 2 positions = 00000100 (Decimal 4)
Wire.beginTransmission(0x70);
Wire.write(bitmask);
Wire.endTransmission();

Quick Reference: How to List Binary Numbers for 4-Bit Registers

When working with 4-bit nibbles (common in BCD thumbwheel switches, 4-bit LCD interfaces, or the lower nibble of a port expander), you need to quickly map physical switch states to code. Below is the definitive reference for listing 4-bit binary numbers alongside their hex and decimal equivalents.

Decimal Hex Binary (4-Bit) Common Hardware Application
00x00000All GPIO pins LOW / Mux disabled
10x10001Enable Channel 0 / LSB active
20x20010Enable Channel 1 / I2C bit 1
30x30011Channels 0 & 1 active simultaneously
40x40100Enable Channel 2 / SPI Chip Select
50x50101Alternating pins (LOW-HIGH-LOW-HIGH)
60x60110Center two pins active (H-bridge drive)
70x70111Lower 3 bits HIGH (Max 3-channel Mux)
80x81000MSB active / 4-bit LCD Enable pin
90x91001Outer pins active (diagonal LED matrix)
100xA1010Alternating pins (HIGH-LOW-HIGH-LOW)
110xB1011All LOW except bit 2
120xC1100Upper 2 bits HIGH (UART TX/RX mapping)
130xD1101All HIGH except bit 1
140xE1110All HIGH except LSB (Inverted logic)
150xF1111All 4 pins HIGH (Max drive / Pull-ups)

Troubleshooting and FAQ

Q: Why do we write 0b or B before binary numbers in C++ and Arduino sketches?
A: The compiler defaults to base-10. If you type 10, the compiler reads it as ten. By prefixing with 0b (standard C++14 and later) or B (legacy Arduino macro), you force the compiler to interpret the string as base-2. For example, 0b00001010 evaluates to decimal 10. Always use 0b for modern ESP32/ARM development, as the B macro can cause namespace collisions in larger libraries.

Q: What is the difference between MSB-first and LSB-first when listing binary numbers for shift registers?
A: MSB (Most Significant Bit) means the left-most bit (the 128s place in an 8-bit byte) is clocked into the shift register first. LSB (Least Significant Bit) means the right-most bit (the 1s place) goes in first. If your physical wiring maps the shift register's Q0 output to your first LED, but your code shifts MSB-first, your LED pattern will appear reversed. Check your component datasheet; the 74HC595 shifts data into Q7 first (MSB), meaning the first bit you clock in ends up at the Q7 physical pin.

Q: How do I handle binary numbers larger than 8 bits on an 8-bit microcontroller like the Arduino Uno?
A: An 8-bit MCU processes data in 8-bit chunks. If you need a 16-bit binary mask (e.g., for an MCP23017 16-bit I/O expander), you must use the uint16_t data type. Do not use standard int, as its size can vary or behave unpredictably with bitwise shifts on AVR architectures. List your binary number as two separate 8-bit bytes (high byte and low byte) when transmitting over I2C or SPI.