Binary is a base-2 numbering system where each digit (bit) represents a power of two, allowing digital circuits to store and process decimal numbers using only high (1) and low (0) voltage states. When you write a high-level command like analogWrite(pin, 173) in Arduino or set a PWM duty cycle in the ESP-IDF, the microcontroller does not process the base-10 concept of 'one hundred seventy-three'. Instead, it translates that value into a sequence of voltage highs and lows. Understanding how to map decimal numbers in binary dictates how you format data payloads for I2C sensors, configure hardware timers, and debug SPI shift registers on the bench. It changes a real circuit installation by determining whether your microcontroller successfully configures a peripheral or accidentally overwrites a critical control register, potentially brownouting your board or enabling a high-current driver unexpectedly.
The Core Mechanism: Converting Decimal to Binary Registers
Microcontrollers process data in fixed-width chunks, typically 8, 16, or 32 bits. To send a specific decimal value to a hardware register, you must decompose it into powers of two. Let's look at a concrete, worked numeric example using an 8-bit digital-to-analog converter (DAC) like the Microchip MCP4921 or a standard 74HC595 shift register.
Suppose you need to set a motor driver's digital potentiometer to exactly 173 out of 255 to achieve a specific torque threshold. We subtract the largest possible powers of 2 from 173, moving from the Most Significant Bit (MSB, bit 7) to the Least Significant Bit (LSB, bit 0):
- 128 (Bit 7): 173 - 128 = 45. (Bit 7 = 1)
- 64 (Bit 6): 45 is less than 64. (Bit 6 = 0)
- 32 (Bit 5): 45 - 32 = 13. (Bit 5 = 1)
- 16 (Bit 4): 13 is less than 16. (Bit 4 = 0)
- 8 (Bit 3): 13 - 8 = 5. (Bit 3 = 1)
- 4 (Bit 2): 5 - 4 = 1. (Bit 2 = 1)
- 2 (Bit 1): 1 is less than 2. (Bit 1 = 0)
- 1 (Bit 0): 1 - 1 = 0. (Bit 0 = 1)
The resulting 8-bit binary sequence is 10101101. In C/C++ for Arduino or ESP32, you write this directly to a register using the 0b prefix: 0b10101101, or as a hex literal 0xAD.
If you are manually toggling GPIO pins to bit-bang this value, you would set pins 7, 5, 3, 2, and 0 to 3.3V (HIGH) and pins 6, 4, and 1 to 0V (LOW). For a deeper look at how microcontrollers handle these bitwise operations natively, refer to the Arduino bitWrite() documentation, which abstracts this math into single-line function calls.
Where You Meet Decimal Numbers in Binary on the Bench
You will rarely need to do base-2 math in your head for basic LED blinking, but the moment you interface with dedicated hardware peripherals, binary formatting becomes mandatory.
1. PWM Duty Cycles and Timer Registers
On an ESP32, the LEDC (LED Control) peripheral uses hardware timers to generate PWM. If you configure a 10-bit resolution timer, your decimal duty cycle must fit within 0 to 1023. Writing a decimal value of 800 requires the MCU to load the binary equivalent (1100100000) into the timer's compare register. If you accidentally write a 16-bit value to an 8-bit register without masking, the upper bits are truncated, resulting in a drastically different duty cycle and potentially overdriving a connected MOSFET.
2. SPI Digital Potentiometers and DACs
When sending data to an SPI device like the MCP41010 digital potentiometer, you transmit two bytes: a command byte and a data byte. The data byte is pure binary representing the decimal wiper position (0-255). If your code sends the decimal value directly without casting it to an 8-bit integer, the SPI library might shift out 16 or 32 bits, confusing the slave device's state machine and causing it to ignore the command entirely.
3. I2C Configuration Registers
Sensors like the MPU6050 accelerometer use I2C registers to set measurement ranges. To set the gyroscope to ±500°/s, you must write a specific binary pattern (00001000) to the GYRO_CONFIG register. The decimal equivalent is 8, but understanding the binary layout is crucial because bits 0-2 and 4-7 in that same register control self-tests and reserved functions. You must use bitwise AND/OR masks to change the decimal target without flipping the surrounding bits.
Common Confusions: Pure Binary vs. BCD vs. Two's Complement
The most frequent reason a sensor fails to initialize or an RTC (Real Time Clock) outputs garbage data is a mismatch in binary encoding formats. Makers commonly confuse pure binary with Binary-Coded Decimal (BCD) and Two's Complement.
Chips like the DS3231 RTC do not use pure binary for timekeeping; they use BCD. In pure binary, the decimal number 59 is 00111011. In BCD, each decimal digit is encoded into its own 4-bit nibble. The decimal '5' becomes 0101, and '9' becomes 1001. Therefore, 59 in BCD is 01011001 (Hex 0x59). If you send pure binary 59 (0x3B) to a DS3231 minutes register, the clock will interpret it as 3B in hex, which translates to 30+11 minutes, throwing off your entire timekeeping logic. Always check the datasheet for 'BCD format' warnings.
Two's Complement for Signed Data: When reading negative temperatures from an I2C sensor or negative G-forces from an accelerometer, the data is formatted in Two's Complement. A decimal value of -1 is not represented by a separate sign bit; it is represented by inverting all bits of +1 and adding 1. In an 8-bit system, decimal -1 is 11111111 (0xFF). If you treat this as an unsigned pure binary integer, your code will read it as 255, leading to catastrophic logic errors in thermal shutdown routines.
Decision Tree: Which Binary Format to Send to Your IC
When writing a driver for a new module, use this decision path to determine how to format your decimal numbers in binary before shifting them out over SPI, I2C, or UART.
| IC / Peripheral Type | Data Nature | Required Binary Format | Example Payload (Decimal 45) |
|---|---|---|---|
| Standard DAC, ADC, Shift Register (e.g., 74HC595, MCP4921) | Unsigned magnitude (0 to max scale) | Pure Binary (MSB-first) | 0b00101101 (0x2D) |
| Real-Time Clock (e.g., DS3231, PCF8563) | Time/Date digits (0-59, 0-23) | Binary-Coded Decimal (BCD) | 0b01000101 (0x45) |
| Signed Sensor (e.g., LIS3DH Accelerometer, TMP102) | Positive and negative physical values | Two's Complement | 0b00101101 (for +45) or inverted for negatives |
| UART Text Display (e.g., HD44780 LCD via serial) | Human-readable characters | ASCII Hex Encoding | 0b00110100 ('4') + 0b00110101 ('5') |
0b prefix for hardcoded bitmasks, and rely on standard integer casting for variable decimal numbers. Only switch to BCD or Two's Complement when the specific IC datasheet explicitly mandates it.
FAQ: Debugging Binary Data on the Oscilloscope
How do I verify my decimal-to-binary conversion on the bench?
Connect a logic analyzer or an oscilloscope with serial decoding to your SPI/I2C lines. Set the decoder to 'MSB first' and 8-bit width. Trigger on the chip-select (CS) line going LOW. If your code sent decimal 173, the scope's decoded hex overlay should read 0xAD. If it reads 0xDA, your bit-order is reversed (LSB-first), and you need to flip your shift register logic.
Why does my ESP32 I2C write fail when I use binary literals?
The ESP32's I2C driver expects standard integer types. If you attempt to pass a raw binary literal like 0b10101101 into a function expecting a signed int8_t, the compiler interprets the MSB (1) as a negative sign bit, converting your intended decimal 173 into -83. Always cast binary literals to uint8_t (unsigned 8-bit integer) when interacting with hardware buses to preserve the exact bit pattern. For more on ESP32 peripheral register mapping, consult the ESP32 Technical Reference Manual.
What is the safest way to modify a single bit without altering the others?
Never overwrite an entire configuration register with a hardcoded binary number unless you are certain of the default state of all unlisted bits. Use bitwise operators. To set Bit 3 to HIGH (adding decimal 8), use the OR operator: REG |= (1 << 3);. To clear Bit 3 to LOW, use the AND operator with an inverted mask: REG &= ~(1 << 3);. This ensures your decimal-to-binary mapping only touches the exact hardware pin you intend to toggle.






