Binary counting is a base-2 numeral system where each digit (bit) represents a successive power of two, toggling between a 0 (low voltage) and 1 (high voltage) state to construct any integer value. In a physical circuit, this counting method changes how a microcontroller translates physical 0V and 3.3V/5V logic levels into actionable data, directly dictating your ADC resolution, I2C device limits, and GPIO pin states. Beginners most commonly confuse standard binary counting with Binary-Coded Decimal (BCD)—where each 4-bit nibble represents a single decimal digit 0-9—or mistakenly assume an n-bit system counts up to n rather than 2n - 1.
The Core Mechanism: Powers of Two and Bit Weighting
Unlike the decimal system (base-10) which uses ten distinct symbols (0-9) and rolls over to a new column at ten, binary (base-2) uses only two symbols: 0 and 1. Every time you increment a binary number and run out of symbols, you carry a 1 over to the next column to the left. Each column represents a specific weight, doubling as you move left from the Least Significant Bit (LSB) to the Most Significant Bit (MSB).
To read a binary number, you simply add together the weights of every column that contains a 1. The table below maps out an 8-bit byte, which is the standard data width for most basic microcontroller registers and shift registers like the TI SN74HC595.
| Bit Position | Designation | Weight (2n) | Decimal Value | 3.3V Logic Level |
|---|---|---|---|---|
| 7 | MSB (Most Significant Bit) | 27 | 128 | 3.3V (HIGH) |
| 6 | Bit 6 | 26 | 64 | 0V (LOW) |
| 5 | Bit 5 | 25 | 32 | 3.3V (HIGH) |
| 4 | Bit 4 | 24 | 16 | 3.3V (HIGH) |
| 3 | Bit 3 | 23 | 8 | 0V (LOW) |
| 2 | Bit 2 | 22 | 4 | 3.3V (HIGH) |
| 1 | Bit 1 | 21 | 2 | 0V (LOW) |
| 0 | LSB (Least Significant Bit) | 20 | 1 | 0V (LOW) |
11111111, which equals 255 in decimal. Because we must also count zero (00000000), an 8-bit system can represent exactly 256 unique states (28 = 256).
Worked Numeric Example: Decoding a Port Expander Register
Let’s apply this to a real bench scenario. You are using an MCP23017 I2C GPIO port expander to read a bank of 8 limit switches on a CNC router. The microcontroller reads the GPIOA register over the I2C bus and returns the hexadecimal value 0xB4. Your firmware needs to know exactly which switches are closed (logic HIGH) and which are open (logic LOW).
First, convert the hex value 0xB4 to binary. B in hex is 11 in decimal (1011 in binary), and 4 is 0100. Combined, the 8-bit register reads: 10110100.
Now, map this to our bit-weighting table to find the decimal equivalent and the physical switch states:
- Bit 7 (1): Weight 128. Switch 7 is CLOSED.
- Bit 6 (0): Weight 0. Switch 6 is OPEN.
- Bit 5 (1): Weight 32. Switch 5 is CLOSED.
- Bit 4 (1): Weight 16. Switch 4 is CLOSED.
- Bit 3 (0): Weight 0. Switch 3 is OPEN.
- Bit 2 (1): Weight 4. Switch 2 is CLOSED.
- Bit 1 (0): Weight 0. Switch 1 is OPEN.
- Bit 0 (0): Weight 0. Switch 0 is OPEN.
Adding the active weights together: 128 + 32 + 16 + 4 = 180. The decimal value of the register is 180. More importantly, by looking at the binary string 10110100, you can instantly see that limit switches 7, 5, 4, and 2 are currently triggered, allowing your firmware to execute a bitwise AND operation (e.g., register_val & 0x20) to check the status of a single specific switch without parsing the whole decimal number.
Where You Meet Binary Counting in Practice
Binary counting isn't just abstract math; it defines the physical limits and behaviors of the components on your workbench.
Analog-to-Digital Converter (ADC) Resolution
When you read an analog voltage using the Arduino analogRead() function, you are relying on binary counting. The classic ATmega328P uses a 10-bit ADC. This means it counts from 0000000000 (0) to 1111111111 (1023). It divides the 5V reference into 1,024 discrete steps, giving a resolution of roughly 4.88mV per step. If you upgrade to an ESP32, you get a 12-bit ADC, which counts up to 4095 (111111111111), yielding a finer 0.8mV resolution on a 3.3V reference.
I2C Device Addressing
The I2C bus specification relies on a 7-bit binary counting scheme for device addresses. Because it is 7 bits, the counter maxes out at 127 (1111111). However, 16 of those addresses are reserved for special functions (like general call or CBUS), leaving exactly 112 usable binary addresses for your sensors, displays, and port expanders. If you try to put two identical sensors on the same bus without changing their hardware address pins, they will both respond to the same binary count, causing data collisions.
PWM and LED Dimming
Pulse Width Modulation (PWM) uses binary counters to set duty cycles. An 8-bit PWM timer counts from 0 to 255. When you write analogWrite(pin, 127), you are telling the hardware comparator to flip the pin LOW when the internal binary counter reaches 01111111, resulting in a roughly 50% duty cycle.
Common Pitfalls and Troubleshooting Binary Values
Even experienced makers trip over specific edge cases when dealing with binary data streams. Here is how to avoid the most common bench errors.
When shifting binary data out via SPI or I2C, you must know if your device expects the MSB (Most Significant Bit) or LSB (Least Significant Bit) first. Sending
10000001 MSB-first transmits a decimal 129. Sending that exact same byte LSB-first transmits a decimal 130. Always check the sensor datasheet's timing diagram to see which bit hits the wire on the first clock edge.
Signed vs. Unsigned Integers (Two's Complement)
If you are reading a temperature sensor like the DS18B20, it outputs a 16-bit binary value. If the temperature drops below freezing, the sensor doesn't output a negative sign; it uses a binary counting method called Two's Complement. In a signed 16-bit system, the MSB (Bit 15) acts as a negative weight (-32768). If your code treats this 16-bit register as an unsigned integer, a reading of -1°C (1111111111111111) will be misinterpreted as 65,535°C, causing your thermal shutdown logic to fail catastrophically.
The Off-By-One (Fencepost) Error
When sizing memory arrays or setting up binary counters for timing loops, remember that a 4-bit counter has 16 states (0 through 15), not 1 through 16. If you allocate an array in C++ using int states[15]; to hold all possible states of a 4-bit DIP switch, attempting to write the state for binary 1111 (decimal 15) will cause a buffer overflow and crash your microcontroller. Always allocate 2n elements.






