Bitwise Operators ยท Astro Tech Blog

Bitwise Operators

Bitwise operators perform operations on individual bits of integer values. Unlike Boolean operators (&&, ||, !) which work on logical true/false values, bitwise operators work directly on the binary representation of numbers. They are essential for setting, clearing, and testing individual bits in registers, flags, and hardware control bytes.

& (bitwise AND)

The & operator compares each bit of two values. A result bit is 1 only if both corresponding bits are 1. It is commonly used to mask (clear) specific bits.

void setup() {
  Serial.begin(9600);
  byte a = 0b11001100;
  byte b = 0b11110000;
  byte result = a & b;
  Serial.println(result, BIN);  // prints 11000000
}

void loop() {
  // nothing to do here
}

In this example, only bits that are 1 in both a and b remain 1. The lower 4 bits of a are cleared because b has 0s there. Use & with a mask to clear specific bits while preserving others.

| (bitwise OR)

The | operator compares each bit of two values. A result bit is 1 if either corresponding bit is 1. It is commonly used to set specific bits.

void setup() {
  Serial.begin(9600);
  byte a = 0b00001111;
  byte b = 0b11110000;
  byte result = a | b;
  Serial.println(result, BIN);  // prints 11111111
}

void loop() {
  // nothing to do here
}

Here, a | b combines the bits โ€” all 8 bits are set because each position has a 1 in at least one operand. Use | with a mask to set specific bits to 1 without altering other bits.

^ (bitwise XOR)

The ^ operator compares each bit of two values. A result bit is 1 if the corresponding bits are different. XOR is useful for toggling bits โ€” applying the same mask twice restores the original value.

void setup() {
  Serial.begin(9600);
  byte a = 0b11110000;
  byte toggle = 0b11111111;
  byte result = a ^ toggle;
  Serial.println(result, BIN);  // prints 1111 (00001111)

  // XOR twice restores the original
  byte restored = result ^ toggle;
  Serial.println(restored, BIN);  // prints 11110000
}

void loop() {
  // nothing to do here
}

In this example, XOR with 0b11111111 flips all bits, inverting the value. XORing again with the same mask restores the original. This makes ^ ideal for toggling LED states or flipping control bits.

~ (bitwise NOT)

The ~ operator inverts all bits of a value โ€” 1s become 0s and 0s become 1s. It is a unary operator that acts on a single operand.

void setup() {
  Serial.begin(9600);
  byte a = 0b11110000;
  byte result = ~a;
  Serial.println(result, BIN);  // prints 11111111111111111111111100001111
}

void loop() {
  // nothing to do here
}

Note that ~a prints many leading 1s because byte is promoted to int before the NOT operation, and the higher bits of the int are inverted as well. To mask back to a single byte, combine with &: byte result = ~a & 0xFF;. The NOT operator is often used to create masks for clearing bits: flags &= ~(1 << 3); clears bit 3.

<< (left shift)

The << operator shifts all bits of a value to the left by a specified number of positions. Bits shifted off the left end are lost, and zeros fill in on the right. Each left shift multiplies the value by 2.

void setup() {
  Serial.begin(9600);
  byte x = 1;
  for (int i = 0; i < 8; i++) {
    Serial.println(x << i, BIN);  // prints 1, 10, 100, 1000, ...
  }

  // Calculate 2^5 using left shift
  int power = 1 << 5;  // 32
  Serial.println(power);  // prints 32
}

void loop() {
  // nothing to do here
}

This example shows how shifting 1 left by i positions produces powers of two. Left shift is faster than multiplication and is the standard way to create bit masks: 1 << 3 gives 0b00001000, which is useful for targeting bit 3.

>> (right shift)

The >> operator shifts all bits of a value to the right by a specified number of positions. Bits shifted off the right end are lost. For unsigned types, zeros fill in on the left. Each right shift divides the value by 2 (truncating).

void setup() {
  Serial.begin(9600);
  byte x = 0b11110000;
  byte result = x >> 4;
  Serial.println(result, BIN);  // prints 1111

  int value = 256;
  for (int i = 0; i < 4; i++) {
    value >>= 1;  // divide by 2 each time
    Serial.println(value);  // prints 128, 64, 32, 16
  }
}

void loop() {
  // nothing to do here
}

This example shifts the upper nibble of x down to the lower nibble, effectively isolating it. Right shift is faster than division and is commonly used to extract specific bit fields from sensor data or register values.