In embedded systems, hardware is configured by writing specific values to hardware registers. These registers are typically 8, 16, or 32 bits wide, and each bit has a distinct function: enabling a peripheral, setting a pin direction, selecting a clock source, or triggering an interrupt.
Bit manipulation gives you the precision to control individual bits within a register without accidentally changing other bits. It is the most fundamental skill in embedded firmware development.
Why Bit Manipulation Matters
Consider a GPIO direction register. Each bit controls whether one pin is an input or output:
DDRB Register (8-bit AVR)
Bit: 7 6 5 4 3 2 1 0
PB7 PB6 PB5 PB4 PB3 PB2 PB1 PB0
To configure PB5 as output, set bit 5 to 1:
Before: 0b00000000
After: 0b00100000
You cannot just assign the whole register — that would change all other pin configurations. Instead, you use bitwise operations to target exactly the bit you need.
Bitwise Operators in C
| Operator | Symbol | Description | |
|---|---|---|---|
| AND | & | Clears bits where mask is 0 | |
| OR | `\ | ` | Sets bits where mask is 1 |
| XOR | ^ | Toggles bits where mask is 1 | |
| NOT | ~ | Inverts all bits | |
| Left shift | << | Shifts bits left (multiplies by power of 2) | |
| Right shift | >> | Shifts bits right (divides by power of 2) |
Creating a Bit Mask
A bit mask is a value with exactly one bit set at the desired position. The standard way to create one is with a left shift:
unsigned int bit_position = 3;
// Create a mask with only bit 3 set: 0b00001000
unsigned int mask = 1 << bit_position;
This works for any bit position from 0 to the register width minus 1.
Setting a Bit
To set a bit (force it to 1) without disturbing other bits, use bitwise OR with the mask:
unsigned int flags = 0; // 0b00000000
unsigned int mask = 1 << 3; // 0b00001000
flags |= mask; // 0b00001000 — bit 3 is now set
The OR operation ensures: bits that were already 1 remain 1, and only the masked bit is forced to 1.
Clearing a Bit
To clear a bit (force it to 0) without disturbing other bits, use bitwise AND with the complement of the mask:
flags &= ~mask; // ~mask = 0b11110111 — clears bit 3 only
The complement ~mask has every bit set except the target. AND with this value clears only the target bit.
Toggling a Bit
To toggle a bit (flip its state), use XOR with the mask:
flags ^= mask; // If bit 3 was 1, it becomes 0; if 0, it becomes 1
Toggling is commonly used to blink LEDs or flip signal states.
Checking a Bit
To read whether a specific bit is set, use AND with the mask and compare to zero:
if (flags & mask) {
// Bit 3 is set
} else {
// Bit 3 is clear
}
Complete Example
This example demonstrates all four operations with a flag register:
#include <stdio.h>
int main(void) {
unsigned int flags = 0;
unsigned int bit_position = 3;
unsigned int mask = 1 << bit_position; // 0b00001000
// --- SETTING A BIT ---
flags |= mask;
if (flags & mask) {
printf("After setting: Bit %u is set.\n", bit_position);
} else {
printf("After setting: Bit %u is not set.\n", bit_position);
}
// --- CLEARING A BIT ---
flags &= ~mask;
if (!(flags & mask)) {
printf("After clearing: Bit %u is clear.\n", bit_position);
} else {
printf("After clearing: Bit %u is not clear.\n", bit_position);
}
// --- TOGGLING A BIT ---
flags ^= mask;
printf("After toggling: Bit %u state: %s\n", bit_position,
(flags & mask) ? "set" : "clear");
flags ^= mask;
printf("After toggling again: Bit %u state: %s\n", bit_position,
(flags & mask) ? "set" : "clear");
return 0;
}
Expected output:
After setting: Bit 3 is set.
After clearing: Bit 3 is clear.
After toggling: Bit 3 state: set
After toggling again: Bit 3 state: clear
Hardware Register Examples
AVR GPIO Configuration
// Set PB5 as output (set bit 5 in DDRB)
DDRB |= (1 << 5);
// Set PB5 HIGH (turn on LED)
PORTB |= (1 << 5);
// Set PB5 LOW (turn off LED)
PORTB &= ~(1 << 5);
// Toggle PB5 (blink LED)
PORTB ^= (1 << 5);
// Read pin PB0 input state
if (PINB & (1 << 0)) {
// PB0 is HIGH
}
ARM Cortex-M GPIO (STM32-style)
// Set PA5 as output using MODER register
// MODER bits [11:10] for pin 5 → set to 0b01 for output
GPIOA->MODER &= ~(0x3 << (5 * 2)); // Clear mode bits for pin 5
GPIOA->MODER |= (0x1 << (5 * 2)); // Set output mode
// Set PA5 HIGH using BSRR (Bit Set/Reset Register)
GPIOA->BSRR = (1 << 5); // Set bit 5
// Set PA5 LOW using BSRR (upper 16 bits reset pins)
GPIOA->BSRR = (1 << (5 + 16)); // Reset bit 5
// Toggle PA5 using ODR
GPIOA->ODR ^= (1 << 5);
Common Bit Manipulation Patterns
Extracting a Bit Field
To extract multiple bits from a register (for example, a 2-bit mode field):
// Extract bits [3:2] from a register
uint32_t value = REG;
uint32_t field = (value >> 2) & 0x3; // Shift right, mask 2 bits
Setting a Bit Field
uint32_t new_mode = 0x2;
// Clear bits [3:2], then set the new value
REG &= ~(0x3 << 2); // Clear field
REG |= (new_mode << 2); // Set new value
Testing Multiple Bits at Once
// Check if both bit 3 and bit 5 are set
if ((flags & ((1 << 3) | (1 << 5))) == ((1 << 3) | (1 << 5))) {
// Both bits are set
}
Bit Manipulation Summary Table
| Operation | Code Pattern | Effect | |
|---|---|---|---|
| Set bit N | `reg \ | = (1 << N)` | Forces bit N to 1 |
| Clear bit N | reg &= ~(1 << N) | Forces bit N to 0 | |
| Toggle bit N | reg ^= (1 << N) | Flips bit N | |
| Read bit N | (reg >> N) & 1 | Returns 0 or 1 | |
| Test bit N | reg & (1 << N) | Non-zero if bit is set | |
| Set field | `reg = (reg & ~mask) \ | (val << pos)` | Replaces a field |
Final Thoughts
Bit manipulation is not optional in embedded C — it is how hardware is controlled. Every peripheral configuration, every GPIO direction, every interrupt enable, and every status check involves reading and writing individual bits in memory-mapped registers.
Mastering these four operations — set, clear, toggle, check — gives you complete control over the hardware at the most fundamental level.
In embedded systems, the bit is the atom of control.