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 = 32The 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 = 16The below operation performs division by 4.
uint8_t num = 0b10100111
uint8_t num >>= 2; // 0b001010013. 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 1 | Bit 2 | Outcome |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
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
4Application
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
RXNEbit is set, data is ready to be read. If you don’t check this and readDRanyway, 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:
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| Pin | PB7 | PB6 | PB5 | PB4 | PB3 | PB2 | PB1 | PB0 |
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 1 | Bit 2 | Outcome |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
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
4Application
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 1 | Bit 2 | Outcome |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
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
11Application
Toggling of bits Let us continue our example of PORTB register in AVR microcontrollers discussed previously.
PORTB ^= (1 << 3); // Toggle PB36. NOT (~)
The NOT operator is unary, meaning it takes one operand and inverts all bits.
uint8_t a = 0b00001111;
uint8_t result = ~a; // 0b11110000Summary of Applications
| Operator | Name | Embedded Use |
|---|---|---|
<< | Left Shift | Create bitmasks / Multiply by powers of 2 |
>> | Right Shift | Efficient divide by 2^n |
& | AND | Test bits / Clear bits |
| | OR | Set bits |
^ | XOR | Toggle bits |
~ | NOT | Invert all bits |