Thinking Like a Microcontroller: Bits, Bytes, Hexadecimal, Registers, Bitwise Operators, & Bitmasking
Introduction
Every electronic device around you — your phone, keyboard, smartwatch, gaming controller, or microwave — is powered by a tiny computer called a microcontroller.
Unlike a desktop computer, a microcontroller interacts directly with the physical world. It turns LEDs on and off, reads buttons, drives motors, communicates with sensors, and responds to external events in real time.
To do that efficiently, it doesn’t think in terms of LEDs and buttons.
It thinks in bits.
Learning embedded programming means learning to think like the microcontroller itself.
In this blog post, I’ll write about:
- Bits and Bytes
- Binary and Hexadecimal
- Registers
- Reading and Writing Ports
- Bitwise Operators
- Bitmasking
- Real-world examples
Bits and Bytes
A bit is the smallest unit of information a computer can store.
A bit has only two possible values:
0 = OFF
1 = ON
You can think of a bit as a light switch.
- OFF → 0
- ON → 1
Eight bits grouped together form a byte.
0 1 0 1 1 0 0 1
Almost every register inside the ATmega328P (the chip used on the Arduino Uno) is 8 bits wide, meaning it stores one byte.
The 8421 Rule
Every group of four bits (called a nibble) represents a number.
Each position has a value.
Example:
1010
Using the 8421 rule:
8 + 2 = 10
Decimal 10 is represented by A in hexadecimal.
Another example:
1101
8 + 4 + 1 = 13
Decimal 13 becomes D.
Binary and Hexadecimal
Writing long binary numbers quickly becomes difficult.
Instead of writing
11110000
we write
0xF0
The prefix 0x tells us the number is hexadecimal.
To convert binary to hexadecimal:
- Split the binary into groups of four bits.
- Convert each group using the 8421 rule.
Example:
10101101
↓
1010 1101
↓
A D
↓
0xAD
Converting back is just as easy.
0x5C
↓
5 = 0101
C = 1100
↓
01011100
Registers
A register is a very small storage location inside the microcontroller.
Each register in my Arduino usually stores 8 bits.
Example:
PORTD
00000000
Each bit corresponds to one Arduino pin.
If
PORTD = 00001000
only D3 is HIGH.
If
PORTD = 11111111
every pin is HIGH.
PORTx
The PORT register controls output voltage.
PORTD = 10101010
This produces:
- D7 HIGH
- D6 LOW
- D5 HIGH
- D4 LOW
- D3 HIGH
- D2 LOW
- D1 HIGH
- D0 LOW
PINx
The PIN register lets you read the current voltage on the pins.
Get Shivangsh Roy’s stories in your inbox
Join Medium for free to get updates from this writer.
Example:
byte value = PIND;
Suppose:
PIND = 10000101
This means:
- D7 HIGH
- D2 HIGH
- D0 HIGH
Reading and Writing Entire Ports
Instead of reading one pin at a time:
digitalRead(2);
digitalRead(3);
digitalRead(4);
you can read all eight pins together:
byte inputs = PIND;
Likewise, instead of calling multiple digitalWrite() functions, you can write all eight output pins in one operation:
PORTD = 0xAA;
This immediately sets the pattern:
10101010
Direct register access is much faster than repeatedly calling Arduino library functions.
Bitwise Operators
Registers contain multiple bits, but often we only want to modify one or two without disturbing the rest.
Bitwise operators make this possible.
OR ( | ) — Set Bits
PORTD |= (1 << 3);
Turns D3 HIGH while leaving every other pin unchanged.
AND ( & ) — Read or Clear Bits
Reading a pin:
if (PIND & (1 << 5))
D5 is HIGH
Clearing a pin:
PORTD &= ~(1 << 3);
Turns D3 LOW without affecting any other output.
NOT ( ~ )
Flips every bit.
10100101
↓
01011010
Often used when creating masks.
Shift Operators
1 << 5
produces
00100000
Instead of memorizing binary numbers, programmers simply shift a single 1 into the correct position.
Bitmasking
A bitmask is simply a pattern of bits used to select or modify certain bits in a register.
Example:
PORTD |= (1 << 7) | (1 << 2);
This creates the mask:
10000100
Only D7 and D2 become HIGH.
To clear them:
PORTD &= ~((1 << 7) | (1 << 2));
Bitmasking allows us to manipulate individual pins while leaving every other pin unchanged.
Experiment : Bit-Banging
In this very simple circuit, I used a LED, 220 Ohm resistor, and an Arduino. In my code, I wrote a function called BitBang, which took a byte as its parameter, and split it into bits. If the bit was 1, I turned the state of the pin high, and if the bit was low, I turned it to 0. This resulted in the LED being switched on when the bit was 1, and off when the bit was 0. The graph displayed on the oscilloscope represents the value of each bit(High for 1, low for 0). The unique characteristic of my project is the perfect square waves, which are formed because the byte I am using is 0xAA, or 10101010 in binary. The bits are alternating between 0 and 1, so the signals are alternating between low and high. The square waves are equal, but 1 square wave is not because once the BitBang function completes, there is a delay before it is called again.
Where Are These Concepts Used?
Once you begin building more advanced electronics projects, you’ll see these techniques everywhere.
Examples include:
- LED matrices
- Seven-segment displays
- Rotary encoders
- Keypad scanning
- SPI communication
- I²C communication
- UART serial communication
- Motor drivers
- Robotics
- IR remote receivers
- Digital sensors
- High-speed data acquisition
Whenever speed, precision, or direct hardware control is important, programmers use registers and bitwise operations instead of high-level functions.
Final Thoughts
Learning Arduino begins with blinking an LED.
Learning embedded systems begins when you ask:
“What is actually happening inside the microcontroller?”
Understanding bits, bytes, hexadecimal, registers, bitwise operators, and bitmasking completely changes the way you write code.
Instead of treating the Arduino like a black box, you begin speaking the language of the hardware itself.
And once you can think in bits, you’re no longer just programming an Arduino — you are programming the microcontroller.
Link to my other articles on learning electronics and embedded systems








