Skip to main content

Arduino Bit Manipulation

Introduction

Bit manipulation is a powerful technique that allows you to work directly with individual bits in binary data. On resource-constrained platforms like Arduino, these operations are especially valuable as they enable you to:

  • Save precious memory by packing multiple boolean values into a single byte
  • Perform certain operations much faster than with standard arithmetic
  • Directly interface with hardware registers and peripherals
  • Create more efficient code for timing-critical applications

In this tutorial, we'll explore how to use bitwise operators in Arduino to manipulate individual bits, allowing you to write more efficient and powerful programs.

Understanding Binary and Bits

Before diving into bit manipulation, let's quickly review binary notation. In computing, all data is stored as binary (1s and 0s). A single binary digit is called a "bit," and 8 bits make up a byte.

For example, the decimal number 42 is represented in binary as 00101010:

1286432168421
00101010

Arduino uses different integer types:

  • byte: 8 bits (0 to 255)
  • int: 16 bits (-32,768 to 32,767)
  • long: 32 bits (-2,147,483,648 to 2,147,483,647)

Bitwise Operators in Arduino

Arduino supports the following bitwise operators:

OperatorNameDescription
&ANDSets each bit to 1 if both bits are 1
|ORSets each bit to 1 if at least one bit is 1
^XORSets each bit to 1 if only one bit is 1
~NOTInverts all the bits
<<Left ShiftShifts bits to the left, adding zeros at the right
>>Right ShiftShifts bits to the right

Let's look at each one in detail:

Bitwise AND (&)

The bitwise AND operator compares each bit of two numbers and returns 1 only if both bits are 1.

cpp
byte a = 0b10101010;  // 170 in decimal
byte b = 0b00001111; // 15 in decimal
byte result = a & b; // Result: 0b00001010 (10 in decimal)

This is useful for masking - keeping only specific bits from a value.

Bitwise OR (|)

The bitwise OR operator compares each bit and returns 1 if at least one of the corresponding bits is 1.

cpp
byte a = 0b10100000;  // 160 in decimal
byte b = 0b00001010; // 10 in decimal
byte result = a | b; // Result: 0b10101010 (170 in decimal)

This is useful for setting specific bits to 1 while leaving others unchanged.

Bitwise XOR (^)

The bitwise XOR (exclusive OR) operator sets each bit to 1 only if exactly one of the corresponding bits is 1.

cpp
byte a = 0b10101010;  // 170 in decimal
byte b = 0b00001111; // 15 in decimal
byte result = a ^ b; // Result: 0b10100101 (165 in decimal)

XOR is useful for toggling bits and has interesting properties (like being able to XOR a value twice to get back the original value).

Bitwise NOT (~)

The bitwise NOT operator inverts all bits, changing 1s to 0s and 0s to 1s.

cpp
byte a = 0b10101010;  // 170 in decimal
byte result = ~a; // Result: 0b01010101 (85 in decimal)

Left Shift (<<)

The left shift operator moves all bits to the left by a specified number of positions. Zeros are added at the right.

cpp
byte a = 0b00000001;  // 1 in decimal
byte result = a << 3; // Result: 0b00001000 (8 in decimal)

Left shifting by 1 is equivalent to multiplying by 2, shifting by 2 multiplies by 4, and so on.

Right Shift (>>)

The right shift operator moves all bits to the right by a specified number of positions.

cpp
byte a = 0b10000000;  // 128 in decimal
byte result = a >> 3; // Result: 0b00010000 (16 in decimal)

Right shifting by 1 is equivalent to dividing by 2, shifting by 2 divides by 4, and so on.

Common Bit Manipulation Techniques

Let's explore some useful bit manipulation techniques for Arduino programming:

1. Setting a Bit

To set a specific bit to 1:

cpp
byte data = 0b00000000;
byte result = data | (1 << 3); // Set bit 3 to 1: 0b00001000

2. Clearing a Bit

To clear a specific bit (set it to 0):

cpp
byte data = 0b11111111;
byte result = data & ~(1 << 3); // Clear bit 3: 0b11110111

3. Toggling a Bit

To toggle a specific bit (change 1 to 0 or 0 to 1):

cpp
byte data = 0b10101010;
byte result = data ^ (1 << 2); // Toggle bit 2: 0b10101110

4. Checking a Bit

To check if a specific bit is set:

cpp
byte data = 0b10100000;
bool isBitSet = data & (1 << 5); // Check bit 5 (Returns true)

Note that this returns a non-zero value if the bit is set, not necessarily 1. If you need a boolean result:

cpp
bool isBitSet = (data & (1 << 5)) != 0;

Practical Applications

Example 1: Controlling Multiple LEDs with a Single Byte

Instead of using 8 separate boolean variables to track LED states, we can use a single byte:

cpp
// Define pins
const byte LED_PINS[] = {2, 3, 4, 5, 6, 7, 8, 9};
byte ledStates = 0; // Each bit represents one LED

void setup() {
for (int i = 0; i < 8; i++) {
pinMode(LED_PINS[i], OUTPUT);
}
}

void loop() {
// Turn on LED at position 3 (pin 5)
turnOnLED(3);

// Turn off LED at position 6 (pin 8)
turnOffLED(6);

// Toggle LED at position 0 (pin 2)
toggleLED(0);

// Update physical LEDs based on our byte
updateLEDs();

delay(1000);
}

void turnOnLED(byte position) {
ledStates |= (1 << position);
}

void turnOffLED(byte position) {
ledStates &= ~(1 << position);
}

void toggleLED(byte position) {
ledStates ^= (1 << position);
}

bool isLEDOn(byte position) {
return (ledStates & (1 << position)) != 0;
}

void updateLEDs() {
for (int i = 0; i < 8; i++) {
digitalWrite(LED_PINS[i], isLEDOn(i) ? HIGH : LOW);
}
}

This approach uses only 1 byte of memory instead of 8 bytes for separate boolean variables.

Example 2: Reading from Multiple Sensors

You can efficiently read from multiple digital sensors and pack the results into a single byte:

cpp
const byte SENSOR_PINS[] = {2, 3, 4, 5};
byte sensorStates = 0;

void setup() {
Serial.begin(9600);
for (int i = 0; i < 4; i++) {
pinMode(SENSOR_PINS[i], INPUT_PULLUP);
}
}

void loop() {
// Read all sensors and store their states in a single byte
for (int i = 0; i < 4; i++) {
if (digitalRead(SENSOR_PINS[i]) == LOW) {
// Sensor activated (LOW because of INPUT_PULLUP)
sensorStates |= (1 << i);
} else {
// Sensor not activated
sensorStates &= ~(1 << i);
}
}

// Print the current state in binary
Serial.print("Sensor states: ");
printBinary(sensorStates);
Serial.println();

delay(500);
}

void printBinary(byte value) {
for (int i = 7; i >= 0; i--) {
Serial.print(bitRead(value, i));
}
}

Example 3: Working with Hardware Registers

Arduino's internal hardware often requires bit manipulation to configure. For example, to set up a timer:

cpp
void setupTimer1() {
// Clear the timer control registers
TCCR1A = 0;
TCCR1B = 0;

// Set timer to CTC mode
TCCR1B |= (1 << WGM12);

// Set prescaler to 1024
TCCR1B |= (1 << CS12) | (1 << CS10);

// Set compare match register for 1Hz overflow
OCR1A = 15624;

// Enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
}

// Interrupt Service Routine
ISR(TIMER1_COMPA_vect) {
// This function runs every second
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // Toggle built-in LED
}

Arduino's Built-in Bit Manipulation Functions

Arduino provides several convenient functions for bit manipulation:

bitRead(value, bit)

Returns the value of the specified bit (0 or 1).

cpp
byte x = 0b10101010;
byte bit3 = bitRead(x, 3); // Returns 1

bitWrite(value, bit, bitValue)

Sets the specified bit of value to bitValue (0 or 1).

cpp
byte x = 0b10101010;
bitWrite(x, 2, 1); // x now equals 0b10101110

bitSet(value, bit)

Sets the specified bit of value to 1.

cpp
byte x = 0b10100000;
bitSet(x, 1); // x now equals 0b10100010

bitClear(value, bit)

Sets the specified bit of value to 0.

cpp
byte x = 0b11111111;
bitClear(x, 4); // x now equals 0b11101111

bit(n)

Returns the value of bit n, which is 1 << n.

cpp
byte bit3 = bit(3);  // Returns 0b00001000 (8 in decimal)

Memory Optimization Using Bit Fields

If you need to store multiple small values efficiently, you can use bit fields:

cpp
// Without bit manipulation - uses 4 bytes
boolean isOn = true;
boolean isInitialized = true;
boolean hasError = false;
boolean isProcessing = true;

// With bit manipulation - uses only 1 byte
byte status = 0;
#define STATUS_ON 0 // Bit position
#define STATUS_INITIALIZED 1
#define STATUS_ERROR 2
#define STATUS_PROCESSING 3

// Setting values
bitWrite(status, STATUS_ON, true);
bitWrite(status, STATUS_INITIALIZED, true);
bitWrite(status, STATUS_ERROR, false);
bitWrite(status, STATUS_PROCESSING, true);

// Reading values
boolean isOn = bitRead(status, STATUS_ON);

Performance Benefits

Bit manipulation can be significantly faster than arithmetic operations. For example:

cpp
// Slow method using division and modulo
int value = 42;
int remainder = value % 8;
int quotient = value / 8;

// Fast method using bit manipulation
int remainder = value & 0b111; // Same as value % 8
int quotient = value >> 3; // Same as value / 8

For powers of 2, bit manipulation is much faster because it translates directly to single CPU instructions.

Common Patterns for Arduino Projects

Creating Bit Masks for Port Manipulation

Direct port manipulation is faster than digitalWrite():

cpp
// Set pins 8-13 as outputs (PORTB on most Arduinos)
DDRB = DDRB | B00111111; // Or: DDRB |= B00111111;

// Set pins 8, 10, and 12 HIGH
PORTB = PORTB | B00010101; // Or: PORTB |= B00010101;

// Set pins 9, 11, and 13 LOW
PORTB = PORTB & B11101010; // Or: PORTB &= B11101010;

// Toggle pins 8-13
PORTB = PORTB ^ B00111111; // Or: PORTB ^= B00111111;

Checking Multiple Conditions Efficiently

cpp
// Define status codes
#define STATUS_OK 0b00000001
#define STATUS_CONNECTED 0b00000010
#define STATUS_READY 0b00000100
#define STATUS_PROCESSING 0b00001000

byte systemStatus = 0;

// Check if system is ready and connected but not processing
if ((systemStatus & (STATUS_READY | STATUS_CONNECTED)) == (STATUS_READY | STATUS_CONNECTED) &&
!(systemStatus & STATUS_PROCESSING)) {
// Perform action
}

Summary

Bit manipulation is a powerful technique for Arduino programming that allows you to:

  1. Save memory by packing multiple boolean values into a single byte
  2. Perform certain operations much faster than with standard arithmetic
  3. Directly interface with hardware registers
  4. Write more efficient code for timing-critical applications

Key bitwise operators include AND (&), OR (|), XOR (^), NOT (~), left shift (<<), and right shift (>>). Arduino also provides helpful functions like bitRead(), bitWrite(), bitSet(), and bitClear() to simplify bit manipulation.

By mastering these techniques, you can write more efficient Arduino code that runs faster and uses less memory—crucial advantages when programming on resource-constrained microcontrollers.

Exercises

  1. Create a program that uses a single byte to store the state of 8 LEDs and implements a binary counter.
  2. Modify the LED control example to create different light patterns using bit manipulation (e.g., alternating, sequential, random).
  3. Implement a simple state machine using bit flags to control different aspects of your Arduino project.
  4. Create a function that reverses the bits in a byte using bit manipulation.
  5. Optimize an existing sketch by replacing boolean variables with bit fields.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)