Core Embedded Systems Skill: Bitwise Operation

Alwin Arrasyid
7 min readFeb 23, 2022

--

If you know how to manipulate bits in memory, you know how to control memory-mapped I/O devices.

Photo by Shopify Partners from Burst.

What is it?

In computer programming, a bitwise operation operates on a bit string, a bit array or a binary numeral (considered as a bit string) at the level of its individual bits.

Wikipedia.

In the C programming language, you can do bitwise operations on numeric data-type such as integer and char.

Why do we need to understand bitwise operation?

Imagine that you have an 8-bit microcontroller that has 23 general-purpose I/O (GPIO) pins that you can use to send/receive digital signals. I/O mode (input or output), output data, input data, and pull configuration (pull up, pull down, no pull) of every individual GPIO pin stored as an 8-bit (1 byte) register. So, one pin can have at least four configurations and data registers. In total, the microcontroller will use 92 bytes of memory address space just for GPIO pins. That’s inefficient.

I don’t even know if that kind of microcontroller exists. In reality, operating GPIO pins on an 8-bit microcontroller such as ATMega328p is a lot simpler.

The ATMega328p microcontroller has three GPIO ports: Port B, Port C, and Port D. Port B have eight pins, Port C has seven pins, and Port D has eight pins. In total, you can use twenty-three pins for GPIO.

Register summary for GPIO ports of ATMega328p microcontrollers
Register summary for GPIO ports from the datasheet document.

ATMega328p uses three bytes of registers for every port. Each bit of the port register refers to the pin of that particular port. In total, the ATMega328p microcontroller uses only nine bytes of memory for GPIO! That is very efficient compared to the exaggerated non-ideal condition where every pin requires a one-byte register.

To configure a specific pin, you must perform a read/write to a bit in the configuration register. For example, if you want to use pin PD5 (pin 5 of GPIO port D) as an output, you need to set bit 5 of the DDRD register. Conversely, if you want to use PD5 as an input, you need to clear bit 5 of the DDRD register.

That is why you need to do bitwise operations.

How to do the bitwise operation?

You can use bitwise operators in the C programming language to do bitwise operations.

Bitwise AND operator

Bitwise AND takes two operands and produces one if both operands are one. Sounds familiar if you have learned about AND gate or boolean algebra.

In the C programming language, you can use the & operator.

Here’s an example:

unsigned char a = 5;
unsigned char b = 1;
unsigned char c = a & b;// c == 1

The value of the c variable will be 1. Why? The number 5 in binary is 00000101, and the number 1 in binary is 00000001. Only the first bit of each value equals 1, so the result is 1.

00000101 & 00000001 = 00000001

Let’s take a look at another example:

unsigned char a = 10;
unsigned char b = 2;
unsigned char c = a & b;// c == 2

The value of variable a in binary is 00001010, and variable b in binary is 00000010. Only the second bit (bit 2) of both operands equals 1, so the result is 00000010 (2 in decimal).

Bitwise OR Operator

Bitwise OR takes two operands and returns one if one of the operands is one. In the C programming language, you can use the | operator.

Here’s an example:

unsigned char a = 10;
unsigned char b = 2;
unsigned char c = a | b;// c == 12

The value of variable a in binary is 00001010, and variable b in binary is 00000010. The expression evaluates to 00001010 because the bitwise operator will yield 0 if both operands are 0.

00001010 | 00000010 = 00001010;

Another example:

unsigned char a = 1;
unsigned char b = 4;
unsigned char c = a | b;// c == 5

The value of variable a is 00000001, and variable b is 00000100, so the expression evaluates to 00000101 or 5 in decimal.

00000001 | 00000100 = 00000101

Bitwise NOT Operator

The bitwise NOT operator takes a single operand, flips every bit of the operand, then returns the inversion result.

In the C programming language, you can use the ~ operator.

unsigned char a = 10;
unsigned char b = ~a;
// b == 245

If you guess the result is 0, your head confuses the bitwise NOT (~) with the logical NOT (!) operator. That is okay, though. You will learn the difference soon enough.

So, why is it 245 and not 0? The value of variable a in binary is 00000110. Now, flip each bit of that value, one to zero and vice-versa. You will get 11111001 or 245.

~00000110 = 11111001

Bitwise XOR Operator

The bitwise XOR operator takes two operands and returns one if the operands are different. In the C programming language, you can use the ^ operator.

unsigned char a = 10;
unsigned char b = 2;
unsigned char c = a ^ b;// c == 8

The value of variable a is 00001010, and variable b is 00000010. Only bit 3 of variable a different from variable b, so the result is 00001000 or 8 in decimal.

00001010 ^ 00000010 = 00001000;

Left Shift Operator

This operator takes the first operand and shifts every bit to the left by n times, where n is the second operand. The C programming language uses << to denote the left-shift operator.

unsigned char a = 1;
unsigned char b = 2;
unsigned char c = a << b;// c == 4

The expression shifts the value of variable a (00000001) to the left by 2 (value of variable b) times, then produces 00000100 or 4 in decimal as a result.

00000001 << 2 = 00000100;

Let’s take a look at another example:

unsigned char a = 10;
unsigned char b = 4;
unsigned char c = a << b;// c == 160

This expression shifts 00001010 to the left four times, so the result is 10100000 or 160 in decimal.

Right Shift Operator

This operator takes the first operand and shifts every bit to the right by n times, where n is the second operand. The C programming language uses >> to denote the right-shift operator.

unsigned char a = 12;
unsigned char b = 2;
unsigned char c = a >> b;// c == 3

The expression takes the value of a (00001100) and shifts to the right twice, then returns 00000011 (3 in decimal).

00001100 >> 2 = 00000011

Using Bitwise Operation in the Real World

These operations are used in the real world, especially in the embedded system realm where you program a memory-mapped I/O device such as a microcontroller.

Let us go back to our tiny ATMega328p microcontroller that you can find on the infamous Arduino UNO board. We will use bitwise NOT, AND, OR, and shift left operators to configure a pin connected to an LED, and then we will send a HIGH and LOW signal every second.

Arduino UNO connects PB5 (pin 5 of GPIO port B) to the LED. So, we need to set PB5 as an output pin by clearing bit 5 of the DDRB register.

DDRB = DDRB & ~(1 << 5);

If every pin in GPIO port B is an input pin, then the value of the DDRB register will be 11111111 in binary. If we want to set the 5th pin as an output pin, we need to clear bit 5 of the DDRB register.

DDRB = 0b11111111 & ~(1 << 5);DDRB = 0b11111111 & ~(0b00100000);DDRB = 0b11111111 & 0b11011111;DDRB = 0b11011111;

Note that this case might not apply to your situation, but you get the point.

Then, we can send a HIGH signal by setting bit 5 of the PORTB register. Conversely, we can send a LOW by clearing bit 5 of the PORTB register.

// send a HIGH signalPORTB = PORTB | (1 << 5);// halt for 1 seconddelay(1000);// send a LOW signalPORTB = PORTB & ~(1 << 5);// halt for 1 seconddelay(1000);

We use the bitwise OR to set bit 5 of the PORTB register.

PORTB = 0b00000000 | (1 << 5);PORTB = 0b00000000 | 0b00100000;PORTB = 0b00100000;

Then we use the bitwise AND and bitwise NOT to clear bit 5 of the PORTB register.

PORTB = 0b00100000 & ~(1 << 5);PORTB = 0b00100000 & ~(0b00100000);PORTB = 0b00100000 & 0b11011111;PORTB = 0b00000000;

Here is the full version of the code

Bitwise Assignment Operators

The bitwise assignment operator combines the bitwise operator with the assignment operator to simplify an assignment and bitwise expression in a single line of code.

PORTB = PORTB | (1 << 5);

It is too long, do this instead:

PORTB |= (1 << 5);

Another example:

PORTB = PORTB & ~(1 << 5);// equals toPORTB &= ~(1 << 5);

Another example, but not used in the Arduino code:

unsigned char a = 1;a = a ^ a;// equals toa ^= a;// ...a = a << 1;// equals toa <<= 1;// ...a = a >> 1;// equals toa >>= 1;

As you can see, bitwise assignment operators are only valid for operators with two operands.

Conclusion

Memory-mapped I/O devices such as microcontrollers use bitwise operators to configure and read/write data. Once we know how to set and clear bits in memory, we can control the MMIO devices. So embedded system engineers need to understand how to operate bits using bitwise operators.

References

--

--

Alwin Arrasyid
Alwin Arrasyid

Written by Alwin Arrasyid

Principal Engineer, working on better IoT solutions and enabling AI on the edge.

Responses (2)