AVR Bit Manipulation in C
Manipulating individual bits is one of the most important and fundamental concepts to understand when programming microcontrollers. Bit manipulation is necessary to read the status of components, set parameters, and change the state of output pins. It is important to know how to individually change the states of if certain bits while leaving others unchanged. Here we will take a look at some of the bitwise operators that are available to us in C and show how they are used to control an LED.
Setting individual bits
Setting a bit in a register is done using the bitwise OR operator. The truth table for OR is shown below.
A | B | A | B |
---|---|---|
1 | 0 | 1 |
1 | 1 | 1 |
0 | 0 | 0 |
0 | 1 | 1 |
The bitwise OR operator, simply |
, provides a convenient way to ensure a particular bit is set to 1, while leaving other bits unchanged. Observe that 0 OR-ed with any bit 'x' will result in 'x', while 1 OR-ed with any bit 'x' will result in 1. Thus, 0bxxxxxxxx | 0b00000001 = 0bxxxxxxx1 . In this case 0x00000001 is called a bitmask. When a bitmask is OR-ed with a sequence of bits, the sequence of bits will remain unchanged wherever the bitmask is 0, and set to 1 wherever the bitmask is 1.
Notice that we can set multiple bits in a single operation. For example, the bitmask 0b01010101can be used to set every other bit in a byte, i.e. 0bxxxxxxxx | 0b01010101 = 0bx1x1x1x1.
Clearing individual bits
Clearing a bit is done using the bitwise AND operator. The truth table for AND is shown below.
A | B | A & B |
---|---|---|
1 | 0 | 0 |
1 | 1 | 1 |
0 | 0 | 0 |
0 | 1 | 0 |
The usage of the bitwise AND operator is similar to OR, except that this time a bit will be cleared wherever there is a 0 in our bitmask, and remain unchanged wherever there is a 1. For example, in a situation where the most significant bit needs to be set to 0 we can use 0bxxxxxxxx | 0x10000000 = 0b1xxxxxxx.
Blinking an LED
The first thing we need to do in our main program is write a 1 to bit 0 in the register DDRB, which instructs the microcontroller to make PINB0 an output pin (since we are using it to drive an LED).
Based on the operators shown above, we can write a simple program to blink an LED connected to PINB0 for an AVR microcontroller as follows:
#include<avr/io.h>
#include<util/delay.h>
int main()
{
// set PINB0 to output in DDRB
DDRB |= 0b00000001;
while(1)
{
// set PINB0 high
PORTB |= 0b00000001;
_delay_ms(500);
// set PINB0 low
PORTB &= 0b11111110;
_delay_ms(500);
}
}
The first thing we need to do in our main program is write a 1 to bit 0 in the register DDRB, which instructs the microcontroller to make PINB0 an output pin (since we are using it to drive an LED).
// set PINB0 to output in DDRB
DDRB |= 0b00000001;
Note that instead, we could write
// set PINB0 to output in DDRB
DDRB = DDRB | 0b00000001;
And get the same result.
Flipping individual bits
Flipping individual bits can be done using the bitwise XOR (exclusive OR) operator. The truth table for XOR is shown below.
A | B | A ^ B |
---|---|---|
1 | 0 | 1 |
1 | 1 | 0 |
0 | 0 | 0 |
0 | 1 | 1 |
The bitwise XOR operator ^
, allows us to flip the state of a particular bit, while leaving other bits unchanged. Observe that 0 XOR-ed with any bit 'x' will result in 'x', while 1 OR-ed with any bit 'x' will result in '¬x'. Thus, 0bxxxxxxx1 ^ 0b00000001 = 0bxxxxxxx0 and conversely 0bxxxxxxx0 ^ 0b00000001 = 0bxxxxxxx1. Using this fact, we can rewrite the code from earlier which toggles an LED to
#include<avr/io.h>
#include<util/delay.h>
int main()
{
// set PINB0 to output in DDRB
DDRB |= 0b00000001;
while(1)
{
// toggle PINB0
PORTB ^= 0b00000001;
_delay_ms(500);
}
}
Shifting bits
Bit shifting can be done using the operators <<
(left shift) and >>
(right shift). Bit shifting shifts all of the bits in a sequence by a specified amount and fills the empty spaces with 0s. For example, 0b1110101 << 2 = 0b11010100 and 0b11110000 >> 3 = 0b00011110. A left shift of n
is equivalent to multiplying by 2n and a right shift of n
is equivalent to dividing by 2n. For example 0b00000001 << 4 = 0b00010000 in decimal is 1 * 24 = 16. This is a very efficient way of multiplying and dividing values but is limited to powers of 2.
Bit shifting is also very useful as it allows a particular shorthand notation for specifying a particular bit in a register. The include directive #include<avr/io.h>includes definitions for all of the microcontrollers pins. If we would like to set PINB4 to output in DDRB, we can do so with
// set PINB4 to output in DDRB
DDRB |= 0b00000001;
Alternatively, the included header file defines PINB4 as 4, so we can write
// set PINB4 to output in DDRB
DDRB |= (1 << PINB4);
Since 1 << 4 = 0b00010000
Using this method means you do not have to count bit positions for your 1s and 0s to get the correct mask. It also means you don't have to know the exact position of the bit you want to manipulate. For example, the datasheet tells us WGM21 is in register TCCR2A which gives us all the information we need to set it
// set WGM21 to 1
TCCR2A |= (1 << WGM21);
NOT
The last bitwise operator is NOT. Unlike the previous operators which were binary (i.e. they took two operands) NOT is a unary operator (it only takes a single arguement). NOT will change all of the 0s to 1s and 1s to 0s for a given value. This is most useful when combined with the AND operator to clear certain bits. For example, note above that when we wanted to clear PINB0, we used the expression
// set PINB0 low
PORTB &= 0b11111110;
The righthand side of the expression has 1s for all of the bits we do not want to change and a 0 for the bit we want to clear. Since this is the opposite of the mask given by 1 << PINB0 we could instead write
// set PINB0 low
PORTB &= ~(1 << PINB0);
Compound Statements
The operators above can be combined in single statements. For example, three different pins can be set at the same time with
// set PINB0, PINB2, PINB4 high
PORTB |= (1 << PINB0)|(1 << PINB2)|(1 << PINB4);
Alternatively, all three pins could be cleared with the statement
// set PINB0, PINB2, PINB4 low
PORTB &= ~((1 << PINB0)|(1 << PINB2)|(1 << PINB4));
Different pins cannot be simultaneously set and cleared with logical operators however. Each requires a separate operation.
Post a Comment