Today I bring you a tutorial about how to use the bit by bit operations, lso called bitwise. This kind of operations are very useful programming, because it will allow us to do some operations like for example:

  • Save memory packing multiple booleans inside every byte
  • Work with microcontroller registers (wich I’ll talk later on my blog)
  • Make arithmetical operations like multiply or divide by the power of two.

In this guide I’ll try to talk about the basic, and later I’ll talk about more advanced things like registers. Anyway, I’ll try to make you understand correctly.

The binary system

The first you have to learn is what the binary system is and how it works, because from now I’ll write the numbers in this system.

A binary number is a number expresed in the base-2 system. wich is used in digital electronics. That means that every number can be just 0 or 1.

In the tradicional base-10 system, a number can be decomposed in powers of base-10 from the following way:

684 = (6 * 102) + (8 + 101) + (4 * 100)

Like the base-10 system, the binary can be decomposed in powers, but this time of base-2.

45 = (1 * 25) + (0 * 24) + (1 * 23) + (1 * 22) + (0 * 21) + (1 * 20) = 101101

Is very important that you’ll learn how the binary system works to be able to do operations with this numbers.

In programming, it is usually used 0b to indicate that the number is binary, for example 0b10 = 2. By legacy reasons, another method is also allowed using the B prefix, for example B10 = 2.

Bitwise AND

The bitwise operator AND is written as a simple ampersand (&), used between two integers. This operator will compare every bit in both numbers and the resulting bit will be 1 if both are 1, otherwise will return 0:

0 & 0 == 0
0 & 1 == 0
1 & 0 == 0
1 & 1 == 1

In programming, the numbers are stored in bits, for example, an INT in Arduino contains 16 bits. Using this comparator on this numbers will do the operation bit by bit, for example:

int a = 47; //    0000000000101111
int b = 75; //    0000000001001011
int c = a & b; // 0000000000001011 -> In decimal 11

If you take a look into the last line, the number is the result of comparing every bit, bit by bit. of the two above lines with the AND operator.

The most common usage of the bitwise AND is to select a concrete bit to check if is 1 or 0.

int x = 5;       // binary: 101
int y = x & 1;   // now y == 1
x = 4;           // binary: 100
y = x & 1;       // now y == 0

Bitwise OR

The bitwise operator OR is written with a simple pipe | and like the AND operator, it compares the number bit by bit. The difference is that OR bitwise will result as 1 if one of the bits is 1, and will only result 0 if both are 0.

0 | 0 == 0
0 | 1 == 1
1 | 0 == 1
1 | 1 == 1

This will make the same example used for the bitwise AND changing the operator, give us a quite different result:

int a = 47; //    0000000000101111
int b = 75; //    0000000001001011
int c = a | b; // 0000000001101111 -> In decimal 111

This operator is often used to ensure that one bit changes to 1 without modifying the rest, for example:

00101011 | 00000100 = 00101111 // Just modify the third bit starting from the right

Bitwise XOR

The XOR operator is indicated by a ^ caret symbol. In order not to vary, this operator also performs bitwise operations on the numbers. Its operation is similar to OR with the difference that it gives 1 when the two bits are different and 0 when they are equal:

0 ^ 0 == 0
0 ^ 1 == 1
1 ^ 0 == 1
1 ^ 1 == 0

Obviously, the same example used in the two previous operators will give us a different result:

int a = 47; //    0000000000101111
int b = 75; //    0000000001001011
int c = a | b; // 0000000001100100 -> In decimal 100

This operator is mainly used to toggle the state of a bit, making a 0 bit become 1, and a 1 bit become 0:

00101011 | 00000110 = 00101101 // Toggles the state of the second and third bits

Bitwise NOT

This operator is indicated by the tilde ~. As you may have guessed, this operator inverts the bits of the number and, like the rest, performs the operation bit by bit.

int a = 103; // binario:  0000000001100111
int b = ~a;  // binario:  1111111110011000 = -104

You will surely be struck by the fact that the number is negative. This is due to the fact that in the variables signed int (or simply int), the first bit is the sign bit, so if it is 0 the number will be positive, and if it is 1 it will be negative (more information on wikipedia). Obviously, this does not happen with variables of type unsigned int, since as the name itself indicates, they lack a sign.

This sign bit can cause unexpected surprises, as we will see later.

Bit Shift Operators

Bit Shift operators are operators that move the bits to the positions that we indicate. We have two operators depending on whether we want to move the bits to the left (<<), or to the right (>>).

int a = 5;        // binario: 0000000000000101
int b = a << 3;   // binario: 0000000000101000, o 40 in decimal
int c = b >> 3;   // binario: 0000000000000101, becomes equal to a (5 in decimal)

When you move the bits to the left, the bits from the beginning will be banished to absolute oblivion. One way to understand this operator more easily is to think that the number will be multiplied by 2 raised to the number of places that we move it:

1 <<  0  ==    1
1 <<  1  ==    2
1 <<  2  ==    4
1 <<  3  ==    8
...
1 <<  8  ==  256
1 <<  9  ==  512
1 << 10  == 1024

In the case that you move the bits to the right, the operation will vary depending on the type of variable. For example, an INT with a negative number (the highest bit at 1), will cause that bit to be copied into the lowest bits.

int x = -16;     // binary: 1111111111110000
int y = x >> 3;  // binary: 1111111111111110

This effect called sign extension is most likely not what you want. To avoid this, you can take advantage of the fact that the right shift operation is different in unsigned int variables and do a type casting to suppress the 1’s that are copied from the beginning:

int x = -16;               // binary: 1111111111110000
int y = unsigned(x) >> 3;  // binary: 0001111111111110

If above I indicated that the bit shift operator to the left multiplied by 2 raised to the number of squares to move, the bit shift operator to the right can be understood as the opposite: dividing the number by 2 raised to the number of squares:

1024 >>  0  == 1024
1024 >>  1  ==  512
1024 >>  2  ==  256
1024 >>  3  ==  128
...
1024 >>  8  ==    4
1024 >>  9  ==    2
1024 >> 10  ==    1

Assignment operators

Assignment operators work exactly the same as those used with addition, subtraction… If you don’t know what I’m talking about, an arithmetic operation on a variable can be written as follows:

int x = 2;
int x = x + 3; // Substitute x for the result of x + 3 (5)

In short, we can perform the same operation as follows:

int x = 2;
int x += 3; // It does exactly the same as the example above

This same method can be used for bitwise operators, except for the NOT operator, which does not have a shortcut:

int x = 1;  // binary: 0000000000000001
x <<= 3;    // binary: 0000000000001000
x |= 3;     // binary: 0000000000001011 - because 3 is 11 in binary
x &= 1;     // binary: 0000000000000001
x ^= 4;     // binary: 0000000000000101 - Toggle using binary mask 100
x ^= 4;     // binary: 0000000000000001 - Alternate the bits again leaving them as before

As I mentioned, the NOT operator doesn’t have a shortcut, but it doesn’t need it either:

x = ~x;

As you can see, the operation is more or less like the previous abbreviated ones.

Don’t confuse bitwise operators with Boolean operators!

It is very easy to confuse the bitwise AND operator (&) with the boolean operator (&&). Both are different for two reasons:

  • They do not calculate the numbers in the same way, bitwise does the calculations bit by bit while the Boolean operator first converts both to Boolean. For example, 4 & 2 == 0 because 4 is 100 and 2 is 010, and neither bit is 1 in both. However 4 && 2 == true, since both numbers are different from 0 and are therefore treated as true. The equivalent would be true && true == true.
  • Bitwise operators evaluate the two operands before performing the operation, while Boolean operators use an evaluation called short-cut. This only matters if the operators have side effects, such as displaying output and modifying the value of something in memory. In the example below:
int fred (int x)
{
    Serial.print ("fred ");
    Serial.println (x, DEC);
    return x;
}

void setup()
{
    Serial.begin (9600);
}

void loop()
{
    delay(1000);    // wait 1 second, so output is not flooded with serial data!
    int x = fred(0) & fred(1);
}

The result of this program will be:

fred 0
fred 1

This is because the bitwise operator first evaluates the result of both operands. If, on the other hand, we replace the bitwise operator with a boolean one (&&), the result will be the following:

fred 0

As you can see, the second function has not been executed. This is because the result of the function on the left is false, so it is not necessary to evaluate the function on the right as it is known with certainty that the result of the operation will be false.

The same is true of the bitwise OR operator (|) and the boolean OR operator (||). The bitwise operator will evaluate both operands before performing the operation, while the boolean operator will evaluate only the first one in the event that it is already true for the same reason, the program will already be sure that the result will be true.

Save memory and optimize speed with bitwise operations

Now that I’ve shown you how bitwise bitwise operations work, it’s time for me to introduce you a little into how they can help you. We’ll start with something easy like saving memory on booleans.

Save memory on booleans

Something very interesting to save space of the so precious RAM memory of our microcontrollers, is to convert the booleans to bits. A boolean is a variable that can have two values true and false, which is the equivalent of a bit, which can have a value of 0 or 1. The difference is that a boolean consumes one byte of memory (8 bits), while one bit consumes one eighth. Unfortunately you cannot use the bits separately and you have to put several together in bytes, so this saving measure does not help us if we only have one boolean.

To give an example so that you understand it better:

bool led1 = true;
bool led2 = false;
bool led3 = true;
bool led4 = false;
bool led5 = false;
bool led6 = true;
bool led7 = false;
bool led8 = true;

The size of this string of booleans will be 8 bytes. How can we optimize it? Well, using bits:

byte leds = B10100101;

This example, unlike the boolean one, will consume only 1 byte of memory and will contain the same data, so we will be saving a large amount of memory.

Surely you ask yourself: yes, very good, but how do I check the status of each boolean?. The answer is easy, using bitwise operators, as we learned above.

if (led4 == true)
{
  // Things to Do
}

This example is the simple example that would be used with the bolean, you just check if it is true and act accordingly. In the example above it would be false, so it would do nothing.

For the bits it would be similar, and for this we will use the bitwise AND operator:

if (leds & B00010000)
{
  // Things to Do
}

In this case, the bitwise comparison would be B10100101 & B00010000, and following what we learned earlier we will realize that the result will be 0, which is the equivalent of false.

This will also allow us to check several boolean at once instead of using OR, for example:

if (led4 == true && led6 == true && led7 == true)
{
  // Things to Do
}

It would be as follows with a bitwise operator:

if (leds & B00010110)
{
  // Cosas que hacer
}

If any of the LED bits is 1, the result will be different from 0 and therefore will give true.

Improve the speed of your programs

I would like to be able to go into more detail about how to modify the Arduino registers, so in this case I will give you a simple example related to bitwise operations, and I will not focus on explaining it too thoroughly.

Suppose you want to configure all digital pins from 2 to 13 as output. In a standard way, you would activate them by executing the pinMode command on each pin either one by one or with a loop:

void setup()
{
    pinMode (2, OUTPUT);
    pinMode (3, OUTPUT);
    pinMode (4, OUTPUT);
    pinMode (5, OUTPUT);
    pinMode (6, OUTPUT);
    pinMode (7, OUTPUT);
    pinMode (8, OUTPUT);
    pinMode (9, OUTPUT);
    pinMode (10, OUTPUT);
    pinMode (11, OUTPUT);
    pinMode (12, OUTPUT);
    pinMode (13, OUTPUT);
}
void setup()
{
    for (int pin=2; pin <= 13; ++pin) {
        pinMode (pin, OUTPUT);
    }
}

Both methods are equally valid, but they have two problems: they are slow and consume more program memory. To give you an idea, the first example uses 638 bytes of program memory, and the second 576 bytes. Yes, it is better to use loops instead of doing it by hand one by one, but it is much better if we use bitwise operators and Arduino registers. The two examples above could be condensed into two lines:

void setup()
{
    // set pin 1 (serial transmission) and pins 2..7 as OUTPUT,
    // leave pin 0 (serial receive) as INPUT or it will stop working
    DDRD = B11111110;  // pines digitales 7,6,5,4,3,2,1,0
    // set pins 8..13 as OUTPUT ...
    DDRB = B00111111;  // pines digitales -,-,13,12,11,10,9,8
}

The operation of this last example will be the same as the previous examples, but unlike these, the execution of this code will be faster and the example setch will occupy only 452 bytes, saving 186 bytes compared to the manual method, and 124 bytes versus method using a loop. Obviously on the subject of speed it will not be noticeable since it is executed once and at the beginning, but let’s suppose you do a project that you have to change the pins from 0 to 1 many times per second. In this case, you will benefit from the increased speed.

As always, I hope you liked it and we continue to see each other here. All the best.

Daniel Carrasco

DevOps with several years of experience, and cloud architect with experience in Google Cloud Platform and Amazon Web Services. In his spare time experimenting with Arduino and electronics.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.