To share this page click on the buttons below;
Bitwise operators
C programming language is the perfect solution to interact directly with hardware and in fact it was designed to develop an Operating System. It is for this reason that C supports some low level operators that can be used to manipulate bits inside the microprocessor registers.
Every microprocessor has many registers which can be used to configure and program the desired behavior of the microprocessor peripherals. Registers can be viewed as special memory locations which have a particular meaning for the processor, because they are bounded with hardware and you can use them to configure the microprocessor behavior. Registers have a fixed dimensions, depending on the number of bits of the microprocessor.
For instance microprocessors are able to manage digital input / output: they have, in other words, some physical pins that can be used to set (output) a certain level of tension (low or high) or that can sense (input) which kind of level of tension is set by some external device on that pin. When you press a button on an electronic device what actually is happening is a change to the value of the tension to a pin of a microprocessor, the microprocessor is configured to sense this variation and reacts in some way depending on the program it has to execute.
The digital input / output registers are used to configure the pins "versus" (i.e. if the pin has to be used as an input or as an output) and to read or set the logical value (i.e. a high or low voltage level) of that pin.
If we have a look on the ATMEGA328 microprocessor (which is the microprocessor used by Arduino) data-sheet we will find something like that:
Different pins are grouped into Ports, each port handle 8 pins of the microprocessor (i.e. 8 digital input / output microprocessor pin are connected with this register). The registers are 8 bits long because this microprocessor is an 8 bits microprocessor.
If you write a 1 inside a bit of the Data Direction Register, that pin (the one which connected to this bit of the register) will be used as an output (so the microprocessor will be able to force a low or a high level of voltage on that pin). If the bits of the Data Direction Register are set to 0 then the correspondent pin is set to input mode and so the processor will be able to read the value of the voltage present on that pin (low or high).
The actual value of each pin can be set (if configured as output) or read (if configured as input) using the correspondent bit of the Data Register.
As you can see from that simple example it is really important in cases like that to have the possibility to deal with each particular bit into a register rather than consider its global value: bitwise operators are the tools to deal with that.
Left and right shift
Consider the register we have seen so far as a sort of piece of pipe where the bits inside can actually flow: that is what you can obtain using the shift operator. Suppose, for example, that our 8 bit register contains the following 8 binary digits: 1000 1010. If you shift left this register of 1 position all the binary digits move toward left of one position: the leftmost 1 is lost because there is no more space into the register and by default a 0 is inserted into the rightmost position, so that the result of the left shift will be 0001 0100. In the same way you can shift the original value right of 1 position, in this case the rightmost 0 will be lost and a default 0 is inserted into the leftmost position, so that you obtain 0100 0101.
The left shit operator is <<
: it shifts what is on the left of the operator a number of times equal to what is written on the right of the operator.
The right shift operator is >>
and it works exactly in the same way of the left one but of course it shifts bits on the other direction.
Mathematical interpretation
From the math point of view a right shift is equivalent to a division by 2 and a left shift is equivalent to a multiplication by 2.
Note
Shift can be used to obtain a binary number which has a 1 into a particular position. Suppose for example that you want to obtain a binary number which has a 1 on the third bit (we are counting bits from right to left starting from 0, i.e. the rightmost position is 0): 1<<3 is the number you desire (of course that is equivalent to 8, 1000 in binary, but I found easier to get that with shift, especially for high positions).
Bitwise AND
The bitwise AND operator performs the AND boolean operation between each bits of 2 numbers. The result of this operation is a number that (expressed in binary form) has a 1 in every position both the original numbers have a 1 and 0 otherwise.
The bitwise AND operator is represented by the character &.
The following image shows an example of that:
Bitwise OR
The bitwise OR operator performs the OR boolean operation between each bit of 2 numbers. The result of this operation is a number that (expressed in binary form) has a 0 in every position both the original numbers have a 0 and 1 otherwise.
The bitwise OR operator is represented by the character | .
The following image shows an example of that:
Bitwise NOT
The bitwise NOT operator performs the boolean NOT operation on each bit of a number. The result of this operation is a number that (expressed in binary form) has a 0 in every position the original number had a 1 and a 1 in every position the original number had a 0.
The bitwise NOT operator is represented by the character ~ .
The following image shows an example of that:
Short form of bitwise operators
Like for math operators also bitwise operators have a shortcut for bitwise and assignment: so that you can write reg |= 5;
: that is equivalent to reg = reg | 5
;. The very same is possible for other bitwise operators.
An example
Here below an example of application of bitwise operator: I made up a "virtual" register (8 bits long) and I play a little bit with it changing its bits individually.
#include <stdio.h>
/* program to demonstrate the use of bitwise operators */
#define IS_BIT_SET(reg,pos) ((reg & (1<<pos)) >> pos)
#define SET_BIT(reg,pos) reg |= (1<<pos)
#define RESET_BIT(reg,pos) reg &= ~(1<<pos)
void print_register(unsigned char reg) {
int i = 0;
printf("\n -----------------\n");
printf(" |");
for(i = 7; i >= 0; i--) {
if(IS_BIT_SET(reg,i)) {
printf("1");
}
else {
printf("0");
}
printf("|");
}
printf("\n -----------------\n\n");
}
int main(void) {
unsigned char virtual_registry = 0;
/* set the bit 3 (bit 0 is the rightmost one) */
SET_BIT(virtual_registry,3);
printf("Set the bit 3 (0 is the rightmost one) \n");
print_register(virtual_registry);
/* set the bit 5 (bit 0 is the rightmost one) */
SET_BIT(virtual_registry,5);
/* set the bit 7 (bit 0 is the rightmost one) */
SET_BIT(virtual_registry,7);
printf("After the set of the bit 5 and 7 (0 is the rightmost one)\n");
print_register(virtual_registry);
/* reset the bit 5 */
RESET_BIT(virtual_registry,5);
printf("After the reset of the bit 5 (0 is the rightmost one)\n");
print_register(virtual_registry);
return 0;
}
Preprocessor macros
This example heavily uses the preprocessor macros. This is another interesting application of the preprocessor.
You can use the preprocessor #define
to define a macro which looks like a function. Pay attention: they are not really functions, although they can be extremely powerful and handy.
Macro relies on the substitution mechanism used by preprocessor #define
. Every time the preprocessor (which is the first "operation" done during the compilation process) finds what is the first term of the define it substitutes that, in all the subsequent code, with the second term of the #define
. Quite simple: if you write #define DIMENSION 32
, starting from the line immediately following the define, every times the preprocessor finds the token "DIMENSION" it replace it with "32". This is useful for readability and for keeping constants defined in just one place so that you can change them without looking for them in the whole code.
It turns out that the substitution mechanism works perfectly fine if we use a #define
which has arguments like a function.
For example took #define SET_BIT(reg,bit) reg |= (1<<bit)
: when the preprocessor finds in code SET_BIT(virtual_registry,5)
it substitutes it with virtual_registry |= (1<<5)
exactly like a function call.
Bear in mind though that macros are not really functions for 2 reasons:
- macros do not deal with types, they just only make a substitution so you don't have any check on the type of the variable you are using when compiling
- macros always perform a substitution: the code of the macro is really duplicated because it is substituted in all the code, while a call to a function works in a completely different way (we will see those details in a later chapter).
Now we can decode what the macros do.
- with
reg |= (1<<pos)
we are applying the bitwise OR to the content of the variablereg
and the value(1<<pos)
and we are putting again the result in thereg
variable.(1<<bit)
is a 1 shifted left of bit positions: so that for example 1<<3 is equal to 8 (00001000b): the binary representation help to understand that we are selecting the third bit (start counting from 0 and from the rightmost position). When one of the operands used with the bitwise OR has this form, a one in a certain position and zero everywhere else, we are guarantee that the result is equal to the other operand (the variablereg
in our case) except for the fact that a one is certainly written is in that position (the one selected by the other operand). That is why the macro is calledSET_BIT
: because it sets to 1 the bit in the posth position and leave all other bits to their old value reg &= ~(1<<pos)
performs exactly the same but it reset the bit (it writes 0 into the bit in the posth of thereg
value and leaves unchanged the other bits). In fact~(1<<pos)
is something which has a 0 in the positionpos
(start counting from 0 and from the rightmost position) and 1 everywhere else. When we apply the bitwise AND with something that has this form, the bit in positionpos
is guarantee to be 0 and all other bits remain unchanged.- finally the macro
IS_BIT_SET(reg,pos)
returns 1 if the bit at positionpos
in the variablereg
is set to 1 and 0 otherwise (I leave the details for exercise).
The program
The remaining part of the program just uses those macros to set and reset some bit of the variable virtual_registry
and prints the updated content of that: here I played a little bit with printf
to give some sort of graphical representation of the content of this virtual registry.
The last note is for the the virtual_registry
variable. That variable is declared to be of unsigned char
type: as you should remember the char
type is 1 byte long and keeps numbers that are associated to the ASCII character representation.
The use of the unsigned
modifier performs exactly the same operation we saw on int
type: by default int
and char
are signed so that they can held positive and negative values, with the unsigned
modifier you are telling the compiler that you want to avoid the negative representation so that an unsigned char
is able to contain values from 0 to 255. That is exactly the suitable representation for a virtual registry: all processor registry are unsigned
because what is worth is the value of each bit and not the sign of the whole value.
Other applications
Such use of the bitwise application, although really useful to deal with microprocessor registries, is not limited to that. In can be also used reduce the amount of memory used by our program or in case we want to transfer information (and so we want to reduce the amount of data transmitted).
Suppose in fact that we have 8 boolean variables, variables that can be only true - 1 or false - 0. For example suppose that you have a house which has 8 doors / windows, you make a system that is able to detect the status of those doors / windows (open - 1 or closed - 0) and you want to transmit this status to your phone. You can use an unsigned char
(the smallest C type) for each door / windows, but in this case you have to transfer 8 bytes every time you want to check the status of the doors / windows of your house. A better approach is to code the status of the doors / windows into a single unsigned char
(which has 8 position) and only transmit that byte. You are reducing the amount of transmitted information of a factor of 8!
To share this page click on the buttons below;