Bitwise or bitlevel operations form the basis of embedded programming. A knowledge of Hexadecimal and Binary Numbering system is required along with conversion from binary to hex and vice-verse. A tutorial for that is located @ www.ocfreaks.com/hexadecimal-and-binary-number-system-basics-for-embedded-programming/. If you are less familiar with hexadecimal and binary number systems that will help you.
Further , I assume that the reader has a basic understanding of C programming language , Digital Logic , MCU(Registers,etc..).
Bit level Operations in C
Now getting armed with the knowledge of interconversion between Hexadecimal and Binary we can start with Bitwise(or bit level) operations in C. There are bascially 6 types of Bitwise operators. These are :
1. Bitwise OR operator denoted by ‘|‘
2. Bitwise AND operator denoted by ‘&‘
3. Bitwise Complement or Negation Operator denoted by ‘~‘
4. Bitwise Right Shift & Left Shift denoted by ‘>>‘ and ‘<<‘ respectively
5. Bitwise XOR operator denoted by ‘^‘
1. Bitwise OR operator denoted by ‘|‘
2. Bitwise AND operator denoted by ‘&‘
3. Bitwise Complement or Negation Operator denoted by ‘~‘
4. Bitwise Right Shift & Left Shift denoted by ‘>>‘ and ‘<<‘ respectively
5. Bitwise XOR operator denoted by ‘^‘
Important Note: Bitwise Operations are meant to be done on Integers only! No Float and No Double here plz!
Below is the Truth table for OR , AND , XOR – each of them require 2 operands:
OP1 (Operand 1) | OP2 (Operand 2) | OP1 | OP2 (OR) | OP1 & OP2 (AND) | OP1 ^ OP2 (XOR) |
0 | 0 | 0 | 0 | 0 |
1 | 0 | 1 | 0 | 1 |
0 | 1 | 1 | 0 | 1 |
1 | 1 | 1 | 1 | 0 |
Hexadecimal Numbers in C/C++ program have a ‘0x’ prefix. Without these prefix any number will be considered as a Decimal number by the compiler and hence we need to be explicit with numbers.
Implied Declaration and Use of Numbers when working at bit level:
Consider: unsigned int n = 0x7F2; Here its is implied that n will be 0x000007F2 since n is a 32bit number. Here number will be automatically padded with Zeros form right hand side as required.
Consider: unsigned int n = (1<<3); Here the 3rd bit will be 1 and rest all will be made zeros by the compiler.
In short 0s are padded towards the left as required.
Consider there is a 32 bit register which is connected to 32 pins. Changing any bit to 1 will produce a HIGH (LOGIC 1) and making it ‘0’ will produce a LOW i.e LOGIC 0 on the corresponding bit. If we assign bit number 19(from right) a ‘1’ then pin 19 will produce a HIGH.
In C this can be done as :
REG = 0x00100000; //hexadecimal
=or=
REG = (1<<19); // sort of compact binary rep.
As you can see using binary number directly is a headache - specially when there are like 32 bits to handle. Using Hexadecimal instead makes it a bit easier and using Left shift operator makes it super simple. Here '<<' is called the Left Shift Operator. Similar to this is ">>" viz. Right Shift operator.
"(1<<19)" Simply means 'Shift 1 towards the LEFT by 19 Places'. Other bits will be Zero by default.
We generally use Hexadecimal when we need to change bits in bluk and Left shift operator when only few bits need to be changed or extracted.
ORing in C
This is the same exact thing as in Digital Logic i.e 1 ORed with 'x' is always 1 and 0 ORed with 'x' is always 'x' , where x is a bit. Lets take two 4-bit numbers and OR them. Consider two 4-bit numbers n1=0100 & n2=1001. Here the 1st bit of n1 will be ORed with 1st bit of n2 , 2nd bit of n1 will be ORed with 2nd bit of n2 and soo on. In this case n1 is decimal 4 and n2 is decimal 9.
n1 (4) => | 0 | 1 | 0 | 0 |
n2 (9) => | 1 | 0 | 0 | 1 |
ORed Result (13) => | 1 | 1 | 0 | 1 |
Hence we get 4 | 9 = 13.
It can be seen that bitwise ORing is similar to addition but this is not always the case since bitwise OR doesnt deal with carry generated after adding(i.e ORing) two 1s. For e.g. 12 | 9 is also = 13.
Now , If we want to make bit 19 and 12 as '1' we can use the binary OR operator which is represented by a pipe i.e '|'.
REG = (1<<19) | (1<<12); // 19th and 12th bits are set to '1' , rest are Zeros.
Now consider we want to making the first 21 bits (from right) to '1'. This is can be done using hexadecimal notation instead of using shift operator since we will need to write 21 left shift operations for each of the 21 bits. In this case just consider we have a 32 bit number which has all first 19 bits '1'. Then convert this number to hexadecimal and use it!
REG = 0x003FFFFF;
Hence using Hexadecimal or Binary operator depends on the situation.
Bitwise 1's Complement / Negation in C :
Now lets say.. we need to convert all 0s to 1s and vice-verse. This can be done using the Bitwise negation operator denoted by '~'. The result of this operation is called 1's Complement. Its also called a 'NOT' operation. '~' is a unary operator since it requires only 1 operator while rest all are binary operators.
1st lets take a 4bit example to keep it simple.
Consider a binary number 1101. Its Negation will be ~(1101) => 0010.
Consider a binary number 1101. Its Negation will be ~(1101) => 0010.
Now lets get back to 32 bit numbers.. In C it can be done as follows:
unsigned int x = 0x0FFF000F;
REG = ~(x); // REG is assigned 0xF000FFF0;
=or=
REG = ~(0x0FFF000F); // REG is assigned 0xF000FFF0;
ANDing in C
Binary AND operator in C is denoted by '&'. When 2 numbers are ANDed each pair of 'corresponding' bits in both numbers are ANDed. Consider two 4-bit binary numbers ANDed : 1010 & 1101 , here nth bit of both numbers are ANDed to get the result. Here the same truth table(already shown above) is followed as in Therotical Digital Logic i.e 1 ANDed with x is always 'x' (x is a binary number.. a bit) and 0 ANDed with 'x' is always 0.
unsigned char n1,n2,n3; //we can declare an 8-bit number as a char;
n1 = 0xF4; // binary n1 = 0b11110100;
n2 = 0x3A; // binary n2 = 0b00111010;
n3 = n1 & n2; // binary n3 = 0b00110000; i.e 0x30;
XORing in C
XOR is short for eXclusive-OR. By definition of XOR , the result will be a '1' if both the input bits are different and result will be '0' if both are same (as seen in the table few paragraphs above). XOR can be used to check the bit difference between 2 numbers or Registers.
unsigned int n1,n2,n3;
n1 = 0x13; //binay n1 = 0b10011
n2 = 0x1A; //binay n2 = 0b11010;
n3 = n1^n2; // n2 = 0x09 which is binary 0b01001;
Working with Read/Write Registers in C
Generally its a bad Idea to assign a value directly to Registers since doing so may change the value of other bits which might be used to contol some other hardware.
Consider an 8 bit Register say REGT_8b is used to Start/Stop 8 different Timers. Bit 0 (from left) controls Timer 0 , Bit 1 Controls Timer 1 and so on... Writing a '1' to a bit will Start the timer and a '0' will Stop the Timer.
Now lets say Timers 7,6,5 are started and others are stopped. So the current value of REGT_8b will be '11100000'. Now assume that we want to Start Timer 2. We can do this in a manner which doesn't affect the other bits as follows :
REGT_8b = REGT_8b | (1<<2); // which is 11100000 | 00000100 = 11100100
=or simply=
REGT_8b |= (1<<2); // same as above
Now lets say we want to Stop Timer 6. This can be achieved as follows :
REGT_8b = REGT_8b & (~(1<<6));
=or simply=
REGT_8b &= ~(1<<6);
Here (1<<6) will be 01000000 (considering it 8bit.. for 32 bit 0s will be padding on left). Then ~(1<<6) will be ~(01000000) = 10111111 and finally when its ANDed with current value of REGT_8b 6th bit of REGT_8b will be set to '0' and others will remain as they were since ANDing any bit with 1 doesn't change its value.
More Examples
For below examples assume current value of REGT_8b as '11100011' which is 8 bit.
1) Stop Timers 0 and 5 :
REGT_8b &= ~( (1<<0) | (1<<5) );
=> ((1<<0) | (1<<5)) is = ((00000001) | (00100000)) = 00100001
=> ~(00100001) is = 11011110
=> Finally REGT_8b & 11011110 is = (11100011) & (11011110) = 11000010 => Timers 0 and 5 Stopped!
=> ~(00100001) is = 11011110
=> Finally REGT_8b & 11011110 is = (11100011) & (11011110) = 11000010 => Timers 0 and 5 Stopped!
2) Start Timers 3 and 4:
REGT_8b |= ( (1<<3) | (1<<4) );
=> ((1<<3) | (1<<4)) is = ((00001000) | (00010000)) = 00011000;
=> Now REGT_8b | (00001000) is = (11100011) | (00011000) = 11111011 => Timers 3 and 4 Started!
=> Now REGT_8b | (00001000) is = (11100011) | (00011000) = 11111011 => Timers 3 and 4 Started!
3) Stop Timer 7 and Start timer 3:
REGT_8b = (REGT_8b | (1<<3)) & (~(1<<7));
Above complex expression can be avioded by doing it in 2 steps as:
REGT_8b &= ~(1<<7); // Stop Timer 7
REGT_8b |= (1<<3); // Start Timer 3
Monitoring Specific bit change in Registers
Many times we need to read certain Flags in a register that denotes change in Hardware state. Consider a 32 bit Register REGX in which the 12th bit denotes the arrival of data from UART Receive pin into buffer. This data may be a command for the MCU to start or stop or do something. So we need to read the command then interpret it and call appropriate function. In simplest approach we can continuously scan for change in 12th bit of REGX as follows :
while( REGX & (1<<12) ) //wait indefinitely until 12th bit changes from 0 to 1
{
//do something
//exit loop
}
/*=OR=*/
while(REGX & (1<<12)); //wait indefinitely until 12th bit changes from 0 to 1
//do something
Unless the 12th bit of REGX is '1' the result of (REGX & (1<<12)) will always be zero. When 12th bit is 1 then (REGX & (1<<12)) will be = (1<<12) which is obviously greater than 0 and hence is evaluated as TRUE condition and the code inside the while loop gets executed.
To monitor for the change in 12th bit from 1 to 0 we just Negate the condition inside while to :
while ( !(REGX & (1<<12)) ) //wait indefinitely until 12th bit changes from 1 to 0
{
//do something
//exit loop
}
/*=OR=*/
while ( !(REGX & (1<<12)) ); //wait indefinitely until 12th bit changes from 1 to 0
//do something
A real life example would as follows:
#define RDR (1<<0) // Receiver Data Ready
char U0Read(void)
{
while( !(U0LSR & RDR) ); // wait till any data arrives into Rx FIFO
return U0RBR;
}
Note that you are forcing the CPU into an indefinite wait state. This can only useful in a few cases like, for e.g., you have made a command parser for a CNC machine which waits for a command and then processes it accordingly(or takes appropriate action) then again waits for new command.
Extracting/Testing Bit(s) from a Register:
To extract a bit from a register we can use a variable in which all other bit locations , except the one we are intereseted in , are forced to 0. This can be done using masks.
Lets assume we want to extract bit number 13. For this we first define a mask in which bit location 13 is 1 and rest are all zeros. Then we AND this mask with the register and save the result in a variable.
unsigned int mask ,extract_n;
mask = (1<<13);
extract_n = REGX & mask; // bit 13 goes to extract_n
If the 13th bit of REGX was 1 then the 13th bit of extract_n will also be one. Similarly for 0. This can be easily extended to extract multiple bits.
To test whether bit 13 of REGX is 0 or 1 we can accomplish it as follows :
if(REGX & (1<<13))
{
...
...
}
This is similar to what we did using mask and monitoring except that the result here is used as condition for 'if' construct. Here too '(REGX & (1<<13))' will evaluate to true if bit 13 is 1 and false if bit 13 is 0.
No comments:
Post a Comment
commnet here