Bitwise maths and logic operators

Fonte: Bitwise maths and logic operators

Hello, here I’m again, today I will give a small maths lesson, don’t run away, its easy and its very useful for embedded programming, and its also nice to know a bit more about how our little micro-controller works. In fact I will write about bitwise operator and their usage to set, clear, toggle and even multiply and divide(!), its also the fastest way to configure registers, and to handle digital input and output operations, this bitwise maths is done using basic logic operator, like AND, OR, NOT and other.
Lets start, first I will show what is the correspondent operator for each logic function:

AND &
OR |
NOT ~
XOR ^
Shift left <<
Shift right >>

To help us understand what each logic operator does, there are some little things called truth tables, those are tables/arrays that have all the possible input combinations for each logic operator and the result that each combination produces, to simplify the truth tables they are always shown with only 1 bit input parameters, because for the logic operator it doesn’t matter if we are dealing with 1bit or with 128bits.
But wait, what is all that bitwise here, bitwise there?!
Bitwise means that we are performing operations to lets say a 8bits variable like a char or an uint8_t and working bit by bit, after this tables are shown I think that you will understand this a bit better, if not, just leave a comment 😉
Here are the truth tables for all the logic operator that I will talk about today:

Truth table for AND:
 0 AND 0 = 0
 0 AND 1 = 0
 1 AND 0 = 0
 1 AND 1 = 1
 
 Truth table for OR:
 0 OR 0 = 0
 0 OR 1 = 1
 1 OR 0 = 1
 1 OR 1 = 1
 
 Truth table for XOR:
 0 XOR 0 = 0
 0 XOR 1 = 1
 1 XOR 0 = 1
 1 XOR 1 = 0
 
 Truth table for NOT:
 NOT 0 = 1
 NOT 1 = 0
 
 Truth table for Shift left using an 8 bits variable:
 0x01<<0 = 0b00000001
 0x01<<1 = 0b00000010
 0x01<<2 = 0b00000100
 0x01<<3 = 0b00001000
 0x01<<4 = 0b00010000
 0x01<<5 = 0b00100000
 0x01<<6 = 0b01000000
 0x01<<7 = 0b10000000
 
 Truth table for Shift left using an 8 bits variable:
 0x80>>0 = 0b10000000
 0x80>>1 = 0b01000000
 0x80>>2 = 0b00100000
 0x80>>3 = 0b00010000
 0x80>>4 = 0b00001000
 0x80>>5 = 0b00000100
 0x80>>6 = 0b00000010
 0x80>>7 = 0b00000001
 
 Table with decimal, binary and hexadecimal conversion:
 Binary Hexadecimal Decimal
 0000 = 0 = 0
 0001 = 1 = 1
 0010 = 2 = 2
 0011 = 3 = 3
 0100 = 4 = 4
 0101 = 5 = 5
 0110 = 6 = 6
 0111 = 7 = 7
 1000 = 8 = 8
 1001 = 9 = 9
 1010 = A = 10
 1011 = B = 11
 1100 = C = 12
 1101 = D = 13
 1110 = E = 14
 1111 = F = 15
 Bit numbering of an 8bits variable:
 0b00000000
...||||||||
...|||||||bit0
...||||||bit1
...|||||bit2
...||||bit3
...|||bit4
...||bit5
...|bit6
...bit7

Please note that the shift left and shift right truth tables where done using only two different values, but you can use this logic operators with what ever value you want, but I will talk about this two operator a little more ahead with more depth.
There is a lot of doubts when dealing with binary and hexadecimal number and how to convert between those numbering bases and decimal, so I will show you how to convert a simple 8bits variable from binary to hexa and from that to decimal.
Lets say that you have this 0b10101001, first you remove the 0b part because that’s there only to say to the compiler that that number is in a binary representation, then you split the 8bits in two pairs of 4bits each, so now you have 1010 and 1001, now using the table that I provided you can see that 1010 in hexadecimal is A and 1001 is 9, so the result is 0xA9, again the 0x part is a indicator to the compiler(and people) that this is a hexadecimal represented number, now its much easier to convert to decimal, and 0xA9 is 10 (10 is the A in decimal) plus 9, so 0x10101001 is equal to 0xA9 which is equal to 19, with a bit of practice you will memory all this and its great if you want to make some patterns with leds, or led matrices, or led cubes, and a lot other things!

If you look with some care to the above tables, you can see that there are some similarities between the logic operators and the basic maths operators, and in fact you can use this little idea that I used to distinguish/memorize this operators when I learn about then.

The AND is like a multiplication, its not the same as the * operator, because it does it work for each individual bit and not for a full variable at once, the OR is like an addition, and the XOR is like a simple difference detector, because its 1 when its inputs are different and 0 when its inputs are equal, the shift left works as a real multiplier but it can only multiply in powers of 2(this means that we can multiply by 2,4,8,16,32,64,128,256, etc), because its a logic operator, and shift right works as a divisor, again it only divides in powers of 2 and only returns the integer part of a division.

Lets dive a bit more into this bitwise world, now with a more practical side.
In a micro-controller its usual that we need to configure peripherals, and those have registers where we either set or clear bits to enable or disable certain functions of the said peripheral, or to turn an led on or off, so first here is what we can do using the OR function, lets say that we want to set bit 0 of PORTB without affect the other bits that might be set or clear:

PORTB = PORTB | 0x01;

Or in a more compact way that does exactly the same, the |= is called a compound operator, its a faster way of writing the same that is done above but its shorter, the generated assembly code is the same, but we don’t need to type so much, use whatever you like more:

PORTB |= 0x01;

Now lets say that we want to clear the bit 0 that we have just set, for that we need two logic operator, the AND and the NOT:

PORTB = PORTB & ~0x01;
//or the short version
PORTB &= ~0x01;

This one is a little bit more complex to understand so I will show you why do we need the NOT to clear a bit:

//0x01 in binary is 0b00000001
//if we AND this value with the PORTB value it would clear all the bits and only mantain the original value in bit 0
//So we negate the 0x01 first
// NOT 0x01 is NOT 0b00000001 that is 0b11111110
//Now doing the AND of this will give us the desired result of clearing only the first bit and maintaining all the other bits unchanged

Another use for the AND operator is to detect if a bit is 1 or 0 for example in an if() statement, lets imagine that we have a switch wired in PORTB 3 and we want to detect that:

if(PORTB & (1<<PB3)){
//do some stuff
}

Be aware that the result of PORTB & (1<<PB3) is either 0 or different from 0 and not 0 or 1, in fact the result is 0 or 0x08(0b00001000 in binary or 8 in decimal) if you don’t use any == with this statement with will work perfectly but if you do ==1 it will not work because the result will never be 1 it is only either 0 or 0x08.

Now lets talk about XOR, its not a very common operator and its use might seem limited, but its great to toggle output pins in a very fast and clean way, you want to toggle a led each second for example you could use those usual if() chains were the current state is tested and then changed for the contrary state, using XOR you do that in only one line and way faster than using the if() chains or ever worse, case: chains, or even more stranger ways of doing what is so simple.
Lets toggle for example the PB5 led included in the Arduino board:

PORTB = PORTB ^ (1<<PB5);
//And the short way
PORTB ^= (1<<PB5);

It is really easy and the code is much cleaner, it like XOR a lot!

You might have already heard about bit masks, but what are they and what are they purpose?
A bit masks is a variable that is used with the logic operators, I have been using then in this tutorial, for example (1<<PB5) is a bit-mask that is useful in the XOR example to toggle the value of PORTB5, those masks might be like this one, simple about what they do, but you might have programs that are more complex, and imagine that you use PORTB0,1,2,3 as inputs and PORTB4 and 5 as outputs, you could write (1<<PB0)|(1<<PB1)|(1<<PB2)|(1<<PB3) every time you wanted to check if all the inputs where high or low, or you could define a bit-mask in the begin of your program and just use that instead of writing that small train of letter and symbols every-time.

#define MASK (1<<PB0)|(1<<PB1)|(1<<PB2)|(1<<PB3)
//This define is together with all the other
//includes in the begin of your program
 
if(PORTB & MASK){
//do some stuff when all your inputs are high
}

They are handy and the name that you give them will probably make more sense that just seeing that train of letters.

Finally lets head over the shift left/right operators, I’m already using them, the << is the shift left and is very handy to set bits when dealing with the digital PORTS and registers, the PB0,1,2,3 are just defined values in the <avr/io.h> that have the 0,1,2,3 decimal values, they are just small wrappers that show us in a visual manner with port and pin we are dealing with, so as you can this operators are useful when setting bit-masks, lets say that you want to set bit7 of some variable or a bit-mask, using a shift left its easy to do, and because the values are know at compile time this values will all be calculated by the compiler and not in our micro-controller at run-time:

#define myMask (1<<7)
//latter in the program
myVar |= myMask;

And how about multiplying and dividing with the shift operator?
Lets start with the shift left operator that let us do multiplications by powers of 2, for example:

2<<1 = 4 //But how?
//2 in binary is 0b00000010
0b00000010 << 1 is 0b00000100
0b00000100 in decimal is 4

The value was shifted to the left one position and doing that it was multiplied by 2, the sift left is the same as multiplying by 2^n where n is the amount of shifts applied to the original variable, always keep in mind that when using an 8 bits variable and doing <<8 will return you an empty variable because the shift inserts 0’s when shifting the original value, and well putting eight 0’s in an 8 bits variable leaves it clean.
Another heads-up when using an 8bits variable every bit shifted after the bit7 is lost, this is called an overflow, to prevent that when using the shift operator you can use an int/uint16_t or an even bigger uint32_t.

The divide is done using the shift right and works exactly in the same way as the shift right, but instead of multiplying you are dividing by 2^n, and with an added limitation, this operator only allows us to do integer divisions, the fractional part of the result is lost, for example 7/2 is 3.5, lets see what is the output of using the shift right:

7 in binary is 0b00000111
0b00000111 >> 1 = 0b00000011
0b00000011 in decimal is 3

Our fractional part was lost, because any bit shifted beyond bit0 is lost because our 8 bits variable cant old it, in fact not even a bigger variable will ever give us back the lost fractional part, but integer divisions are also widely used and using right shifts is a major speed-up than using an division routine, of course it only works when you are dividing in powers of 2.

And for today this is it, you might want to read this tutorial two or three times until you can understand it clearly but I can guarantee you that bitwise maths are a powerful tool!