Introduction

Bitwise operators manipulate individual bits of data providing more granular control than regular operators. These operators provide means to perform operations like bitwise AND, OR, XOR. These operators are essential in low level programming especially in the field of embedded systems as many of the tasks involve accessing or modifying data at the bit level.

Types of Operators

There are 6 main types of bitwise operators in C:


1. Left Shift (<<)

Shifts all bits to the left by a given number of positions. Effectively multiplies the number by powers of 2.

uint8_t result = 1 << 5;  // 0b00100000 = 32

The below operation performs multiplication by 4. (2^2 = 4)

uint8_t num = 0b10100111
uint8_t num <<= 2; // 0b10011100 

2. Right Shift (>>)

Shifts all bits to the right. Effectively divides the number by powers of 2.

uint8_t result = 64 >> 2;  // 0b00010000 = 16

The below operation performs division by 4.

uint8_t num = 0b10100111
uint8_t num >>= 2; // 0b00101001

3. AND

The bitwise AND operator performs an AND operation on individual bits of the given two numbers. It is a binary operator, i.e. it requires two operands or numbers to work with and cannot operate on a single number.

The AND operator sets the bit to 1 only if both the bits are same.

Truth Table

Bit 1Bit 2Outcome
000
010
100
111

Syntax

num1 & num2

Example

uint8_t num1 = 7;
uint8_t num2 = 12;
/*
	num1   00000111
	num2   00001100
	result 00000100 = 4 (decimal)
*/
uint8_t result = num1 & num2;
printf("%u", result);

Output

4

Application

There are two applications of AND operator, namely:

1. Testing of bits

AND operator can be used to determine whether a bit or a collection of bits are set or unset. This would allow us to understand the state of a register or interpret a configuration file or even perform a simple logical task such as knowing whether a number is even or odd. The sky is the limit.

Example

You’re implementing a UART driver for an embedded system (e.g., STM32, AVR, etc.). The USART status register (e.g., USARTx->SR) contains bits that indicate events like:

  • TXE (Transmit Data Register Empty)
  • RXNE (Read Data Register Not Empty)
  • TC (Transmission Complete)
  • ORE (Overrun Error)
  • PE (Parity Error)

These are bit flags, and testing them efficiently is crucial.

  • Bit Testing for RXNE (Receive Not Empty)

    #define RXNE (1 << 5)  // Bit 5 in most UART SRs
     
    void uart_rx_handler() {
    	if (USART1->SR & RXNE) {
    		uint8_t data = USART1->DR;  // Read received data
    		// Process data
    	}
    }

    If RXNE bit is set, data is ready to be read. If you don’t check this and read DR anyway, you’ll get garbage or trigger an overrun.

  • Compact Multiple Bit Test

    You might also want to test and act upon multiple flags at once:

    #define ERROR_MASK ((1 << 3) | (1 << 1) | (1 << 0))  // ORE | FE | PE
     
    void uart_check_errors() {
        if (USART1->SR & ERROR_MASK) {
            // Handle communication error
        }
    }

Try it yourself

Determine whether a number is even or odd using bitwise AND operator.

2. Clearing bits

Clearing of bits refers to changing a specific bit to 0 without touching any other bits. This is carried out by ANDing the data with a number with all bits as 1s except the bit to be cleared, which is kept 0.

Example

PORTB Register (AVR microcontrollers like ATmega328P)
The PORTB register controls digital output levels of 8 pins (PB0 to PB7). Each bit corresponds to one I/O pin.
Register Layout:

Bit76543210
PinPB7PB6PB5PB4PB3PB2PB1PB0

Let us assume the 5th pin PB5 is already set to HIGH. Our task is to set it LOW.

Solution

PORTB &= ~(1 << 5);
// or
PORTB = PORTB & 0b00100000;

4. OR

The bitwise OR operator performs an OR operation on individual bits of the given two numbers. It is mainly used for setting bits i.e. making bits HIGH. It is a binary operator, i.e. it requires two operands or numbers to work with and cannot operate on a single number.

The OR operator sets the bit to 1 if any one of the two bits is 1.

Truth Table

Bit 1Bit 2Outcome
000
011
101
111

Syntax

num1 | num2

Example

uint8_t num1 = 7;
uint8_t num2 = 12;
/* 
num1 00000111 
num2 00001100 
result 00001111 = 15 (decimal)
*/
uint8_t result = num1 & num2;
printf("%u", result);

Output

4

Application

Setting of bits

Let us continue our example of PORTB register in AVR microcontrollers discussed previously.

PORTB |= (1 << 3);  // Set PB3 high
// or
PORTB = PORTB | 0b00001000;

5. XOR

The bitwise XOR operator performs an toggling operation on individual bits of the given two numbers. It is a binary operator, i.e. it requires two operands or numbers to work with and cannot operate on a single number.

The XOR operator sets the bit to 1 only if the bits are different.

Truth Table

Bit 1Bit 2Outcome
000
011
101
110

Example

uint8_t num1 = 7;
uint8_t num2 = 12;
/*
	num1   00000111
	num2   00001100
	result 00001011 = 11
*/
uint8_t result = num1 ^ num2;
printf("%u", result);

Output

11

Application

Toggling of bits Let us continue our example of PORTB register in AVR microcontrollers discussed previously.

PORTB ^= (1 << 3);  // Toggle PB3

6. NOT (~)

The NOT operator is unary, meaning it takes one operand and inverts all bits.

uint8_t a = 0b00001111;
uint8_t result = ~a;  // 0b11110000

Summary of Applications

OperatorNameEmbedded Use
<<Left ShiftCreate bitmasks / Multiply by powers of 2
>>Right ShiftEfficient divide by 2^n
&ANDTest bits / Clear bits
|ORSet bits
^XORToggle bits
~NOTInvert all bits