Binary to decimal conversion is the mathematical process of translating a base-2 sequence of ones and zeros into a base-10 integer by summing the powers of two for each active bit. When debugging embedded systems or reading datasheets, you constantly translate between formats—often moving from decimal, binary to decimal, and hexadecimal depending on what the hardware documentation demands. Understanding this translation is not just a software exercise; it dictates which physical pins on your microcontroller go HIGH or LOW, directly impacting your circuit's behavior.
PORTD on an AVR or GPIO_OUT_REG on an ESP32) can accidentally configure a pin as an output and drive it LOW while an external circuit drives it HIGH. This creates a dead short through the microcontroller's silicon, permanently bricking the GPIO bank or the entire chip. Always verify your bitmask math before flashing to bare metal.
The Core Math: Binary to Decimal Translation
In an 8-bit system, each bit position represents a specific power of two, starting from $2^0$ (1) on the far right and scaling up to $2^7$ (128) on the far left. To convert a binary number to decimal, you simply add the positional values of every bit that is set to 1.
Worked Numeric Example
Let’s decode the 8-bit binary value 10110100 (written as 0b10110100 in Arduino/ESP32 C++ code). We map each bit to its positional weight:
- Bit 7 (MSB): 1 × 128 = 128
- Bit 6: 0 × 64 = 0
- Bit 5: 1 × 32 = 32
- Bit 4: 1 × 16 = 16
- Bit 3: 0 × 8 = 0
- Bit 2: 1 × 4 = 4
- Bit 1: 0 × 2 = 0
- Bit 0 (LSB): 0 × 1 = 0
Summing the active bits: 128 + 32 + 16 + 4 = 180. Therefore, the binary sequence 0b10110100 equals the decimal value 180 (or 0xB4 in hexadecimal).
Common 8-Bit Bitmasks in Embedded C
Before moving to complex registers, memorize these fundamental 8-bit patterns. You will use these constantly when masking or setting specific GPIO pins via direct port manipulation.
| Binary (0b) | Decimal | Hexadecimal | Hardware Action / Register Meaning |
|---|---|---|---|
00000001 |
1 | 0x01 |
Set/Read Bit 0 (LSB) only |
10000000 |
128 | 0x80 |
Set/Read Bit 7 (MSB) only |
11111111 |
255 | 0xFF |
Set all 8 pins HIGH (or enable all pull-ups) |
10101010 |
170 | 0xAA |
Alternating pattern (Odd bits HIGH) |
01010101 |
85 | 0x55 |
Inverse alternating (Even bits HIGH) |
00001111 |
15 | 0x0F |
Lower nibble HIGH, upper nibble LOW |
Reference: For deeper exploration of bitwise operations in microcontroller programming, consult the Arduino Bit Math Documentation.
Where You Meet This in Practice
You rarely sit down with a calculator to convert binary to decimal on the bench. Instead, you encounter these conversions when configuring hardware peripherals where the datasheet specifies pin states in binary or hex, but your code requires decimal integers.
1. I2C Address Configuration (PCA9685 PWM Driver)
The PCA9685 16-channel PWM driver has six address pins (A0 through A5). The base I2C address is 0x40 (decimal 64). If you solder a jumper across the A2 and A4 pads, you are setting bits 2 and 4 to HIGH.
Binary addition: Base 01000000 + A2 (00000100) + A4 (00010000) = 01010100.
Decimal conversion: 64 + 4 + 16 = 84. In your C++ code, you must initialize the wire library with pwm.begin(84).
2. Stepper Driver Microstepping (A4988 / DRV8825)
The A4988 stepper driver uses three pins (MS1, MS2, MS3) to set the microstepping resolution. The datasheet provides a truth table in binary. For 1/16th microstepping, the table requires MS1=HIGH, MS2=HIGH, MS3=HIGH. If you are reading the combined state of these three pins as a single 3-bit register on a microcontroller, 111 in binary equals 7 in decimal. If your code checks for a decimal state of 3 (binary 011), it will incorrectly assume the driver is in 1/8th step mode.
3. Direct Port Manipulation
When toggling pins too fast for digitalWrite(), you write directly to the port register. On an ATmega328P (Arduino Uno), PORTD controls digital pins 0-7. If you want to set pins 2, 3, and 7 HIGH simultaneously without disturbing the others, you construct a bitmask. Bits 2, 3, and 7 yield the binary 10001100, which is 140 in decimal. The command becomes PORTD |= 140;.
What It Changes in a Real Circuit and Common Confusions
Misinterpreting a binary sequence as a decimal literal is one of the most common bugs in embedded hardware development. If a datasheet tells you to write 10 to a configuration register to enable a specific feature, it almost always means binary 10 (which is decimal 2), not decimal ten. Writing a decimal 10 (binary 1010) will set multiple bits, potentially enabling a high-side MOSFET while simultaneously disabling a critical safety interlock.
The Endianness Trap (MSB vs. LSB)
When shifting bits out over SPI or I2C, you must know whether the peripheral expects the Most Significant Bit (MSB) or Least Significant Bit (LSB) first.
If you send the decimal value 1 (binary 00000001):
- MSB-first: The receiver sees
0, 0, 0, 0, 0, 0, 0, 1. It reads the value as 1. - LSB-first: The receiver reads the
1immediately as the highest bit, interpreting the sequence as10000000, which is decimal 128.
Always check the timing diagram in the component datasheet. The All About Circuits digital textbook provides excellent visual breakdowns of bit-shifting and endianness.
0-Indexed vs. 1-Indexed Bits
Hardware engineers and software engineers often clash over bit numbering. A datasheet might refer to the "first bit" as Bit 1 (value 1). A C++ programmer will refer to the "first bit" as Bit 0 (value 1). When a datasheet says "Set Bit 3 to enable the PLL," verify if they mean the third physical pin (Bit 2 in code, decimal 4) or the bit with a weight of $2^3$ (Bit 3 in code, decimal 8). When in doubt, look at the register map's decimal weight column.
1011 0100: Upper 1011 is 11. Lower 0100 is 4. (11 × 16) + 4 = 176 + 4 = 180.
FAQ: Quick Reference for Bench Work
Q: How do I extract a single bit from a decimal value in C++?
A: Use the bitwise AND operator (&) combined with a bit shift. To check if Bit 3 is HIGH in a variable named regVal, use: bool isHigh = (regVal >> 3) & 1;. Alternatively, use the Arduino-specific bitRead(regVal, 3) macro, which compiles down to the same machine code.
Q: Why does my ESP32 throw an error when I try to write decimal 256 to a GPIO register?
A: Standard GPIO port registers on 8-bit architectures (and specific sub-registers on 32-bit chips like the ESP32) are often 8 bits wide. The maximum decimal value an 8-bit unsigned integer can hold is 255 (0b11111111). Attempting to write 256 causes an integer overflow, wrapping the value back to 0 and clearing all your pin states.
Q: Is there a difference between a binary literal and a decimal literal in Arduino code?
A: Yes, strictly in syntax. The compiler converts both to the exact same machine code. int x = 180; (decimal), int x = 0b10110100; (binary), and int x = 0xB4; (hexadecimal) result in identical memory allocation. Use binary when mapping physical pins, hex when dealing with I2C/SPI addresses, and decimal for human-readable thresholds like ADC voltage levels.






