ES: Embedded C Bit Manipulation

Bit manipulation is the foundation of hardware control in Embedded C. Microcontroller registers are configured bit by bit — setting, clearing, toggling, and reading individual bits without disturbing neighboring bits is an essential skill for any embedded firmware developer.

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:

md
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

OperatorSymbolDescription
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:

c
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:

c
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:

c
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:

c
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:

c
if (flags & mask) {
    // Bit 3 is set
} else {
    // Bit 3 is clear
}


Complete Example

This example demonstrates all four operations with a flag register:

c
#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:

bash
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

c
// 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)

c
// 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):

c
// 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

c
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

c
// 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

OperationCode PatternEffect
Set bit N`reg \= (1 << N)`Forces bit N to 1
Clear bit Nreg &= ~(1 << N)Forces bit N to 0
Toggle bit Nreg ^= (1 << N)Flips bit N
Read bit N(reg >> N) & 1Returns 0 or 1
Test bit Nreg & (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.