Binary numbers are a base-2 numeral system that uses only two digits—0 and 1—to represent all values, where each digit's place value is a power of two rather than a power of ten. In physical electronics, these abstract digits map directly to tangible voltage states on a silicon die: a '1' represents a logic HIGH (typically 3.3V or 5V) and a '0' represents a logic LOW (0V or ground).
The Core Concept: Base-2 vs Base-10 in Hardware
Humans use a base-10 (decimal) system because we have ten fingers. Microcontrollers use base-2 because their fundamental building block—the MOSFET transistor—operates as a microscopic switch that is either fully ON (saturated) or fully OFF (cut-off). There is no 'volume dial' inside a CPU's memory register; there are only billions of switches holding a charge or draining it to ground.
When a microcontroller reads a digital input pin, it doesn't measure the exact analog voltage. Instead, the signal passes through a hardware Schmitt trigger that snaps the continuous voltage into a strict binary state based on specific thresholds. For standard 5V CMOS logic, anything above 2.0V is latched as a 1 (HIGH), and anything below 0.8V is latched as a 0 (LOW). For 3.3V LVCMOS logic (like on the ESP32-WROOM-32), those thresholds drop to roughly 1.26V and 0.9V, respectively. This physical reality is what binary numbers represent in a real circuit: the literal presence or absence of a voltage threshold being crossed.
Worked Example: Reading an 8-Bit GPIO Register
To understand how binary translates to physical pins, let's look at a real-world scenario: writing directly to an 8-bit hardware register. Suppose you are programming an ATmega328P (the chip on the Arduino Uno) and you want to set the decimal value 173 to PORTD, which controls digital pins D0 through D7.
First, we convert decimal 173 to binary by subtracting the largest possible powers of two (the place values) from left to right:
- 128: 173 - 128 = 45 (Bit 7 = 1)
- 64: 45 is less than 64 (Bit 6 = 0)
- 32: 45 - 32 = 13 (Bit 5 = 1)
- 16: 13 is less than 16 (Bit 4 = 0)
- 8: 13 - 8 = 5 (Bit 3 = 1)
- 4: 5 - 4 = 1 (Bit 2 = 1)
- 2: 1 is less than 2 (Bit 1 = 0)
- 1: 1 - 1 = 0 (Bit 0 = 1)
The resulting binary number is 10101101. If you execute PORTD = B10101101; in your Arduino sketch, the microcontroller physically drives the pins to the following voltages:
| Pin (Bit) | Place Value | Binary Digit | Physical Output (5V Logic) |
|---|---|---|---|
| D7 (Bit 7) | 128 | 1 | 5.0V (HIGH) |
| D6 (Bit 6) | 64 | 0 | 0.0V (LOW) |
| D5 (Bit 5) | 32 | 1 | 5.0V (HIGH) |
| D4 (Bit 4) | 16 | 0 | 0.0V (LOW) |
| D3 (Bit 3) | 8 | 1 | 5.0V (HIGH) |
| D2 (Bit 2) | 4 | 1 | 5.0V (HIGH) |
| D1 (Bit 1) | 2 | 0 | 0.0V (LOW) |
| D0 (Bit 0) | 1 | 1 | 5.0V (HIGH) |
This direct port manipulation executes in a single clock cycle, whereas using digitalWrite() eight separate times takes dozens of cycles and can cause visible flickering on high-speed LEDs or motor drivers.
Where You Meet Binary in Practical Electronics
You will encounter base-2 logic constantly when configuring hardware at the bench. Here are the most common physical manifestations:
- DIP Switches on Motor Drivers: Stepper motor drivers like the TB6600 use 6-position DIP switches to set microstepping and current limits. The switches are literally wired to the driver's internal pull-up/pull-down resistors. Flipping switches 1, 2, and 3 to 'ON' sends a binary
111to the logic chip, commanding 1/32 microstepping. - I2C Address Selection: When wiring multiple PCF8574 I/O expanders to an ESP32, you use jumper pads or address pins (A0, A1, A2) to set the device address. Grounding a pin sends a '0', tying it to VCC sends a '1'. Setting them to
101shifts the base I2C address so the microcontroller can distinguish between chips on the same SDA/SCL bus. - Subnet Masks in WiFi Configs: When hardcoding a static IP for an ESP32 web server, the subnet mask
255.255.255.0is actually a 32-bit binary string:11111111.11111111.11111111.00000000. The '1s' tell the network stack which bits define the local network, and the '0s' define the host device.
0100000), but your Arduino Wire library expects an 8-bit integer. The 8th bit is the Read/Write flag. Always verify if the datasheet address needs to be bit-shifted left by one (address << 1) before passing it to your code.
Common Confusions: Hexadecimal and Signed Integers
The most common trap for beginners is confusing binary numbers with hexadecimal (base-16). Hexadecimal is not a different physical state; it is simply a human-readable shorthand for binary. Because reading 10101101 is prone to eye-tracking errors, programmers group binary digits into nibbles of four. 1010 equals 10 in decimal, which is 'A' in hex. 1101 equals 13, which is 'D'. Therefore, 10101101 is written as 0xAD. The microcontroller still processes it as eight individual 1s and 0s.
Another major confusion is signed vs. unsigned binary (Two's Complement). In an 8-bit unsigned integer, 10101101 is 173. But if the microcontroller is instructed to read that same register as a signed 8-bit integer (like an int8_t in C++), the Most Significant Bit (Bit 7) acts as a negative sign flag. The exact same physical voltages on the pins will be interpreted by the CPU as -83. Always check your variable types when reading raw sensor registers via SPI or I2C, or a temperature sensor returning 10101101 will tell your code it's -83°C instead of a raw unsigned value.
Frequently Asked Questions
What are binary numbers used for in Arduino and ESP32 programming?
Beyond simple pin toggling, binary numbers are the foundation of bitwise operations, which allow you to manipulate specific hardware registers without altering neighboring pins. For example, if you want to force GPIO pin 3 HIGH on an ESP32 register without changing the state of pins 0, 1, 2, or 4, you use a bitwise OR operation: REG |= (1 << 3);. This creates a binary mask (00001000) and merges it with the existing register state, leaving all other physical outputs exactly as they were.
Why do programmers use hexadecimal instead of binary numbers in code?
Strictly for human readability and memory alignment. A 32-bit memory address written in binary is 11000011101011110000111111001100, which is nearly impossible to debug visually. Written in hex, it becomes 0xC3AF0FCC. Each hex character maps perfectly to exactly four binary bits, making it trivial to translate back to hardware states in your head while keeping the source code compact. Compilers treat 0b11111111, 0xFF, and 255 as the exact same machine code instruction.
How do binary numbers represent negative values in microcontrollers?
Microcontrollers use a system called Two's Complement. To represent a negative number like -5 in an 8-bit register, the CPU takes the binary for positive 5 (00000101), flips every bit to its opposite (11111010), and then adds 1 to the result (11111011). This elegant mathematical trick allows the ALU (Arithmetic Logic Unit) to use the exact same physical addition circuitry for both positive and negative numbers, saving millions of transistors on the silicon die.






