Learning to program AVR microcontroller using AVR C++ is quite challenging at first. I mean what are these DDRs and bit shifting? Dive in to find out…
As the name implies, AVR C++ language is based on C++. But it has its own unique features of configuring the IO pins as inputs or outputs and reading/writing digital or analog signals from/to them. If you are familiar with Arduino C++, AVR C++ is quite different and kind of challenging because it is low-level compared to it. In AVR C++, we are accessing the memory registers that store data directly and manipulating them. Don’t get discouraged because once you get familiar, you will realize how much capability it gives you.
Pins and Ports
What are ports and pins on a microcontroller?
Pins are what sticks out of the microcontroller IC itself that you can use as IO/Special Purpose pins. Ports can be explained as a group of pins, which are represented by a single register inside the microcontroller. For an example, in an 8-bit microcontroller like ATmega328P/ATmega32, 8 pins are grouped together into a single port with a 8-bit register that stores a byte in binary. Each bit corresponds to each pin in that port and you read/write those respective bits to perform operations.
In AVR microcontrollers, ports are named as PortA,PortB,…etc. and each pin in a port is named as PA1,PA2,…etc.
The Structure of AVR C++ Code
If you have already looked at an AVR C++ code or followed my blog post on blinking an LED using ATmega328P, you would have noticed that the AVR C++ code falls into the following rough structure.
<Preamble and includes>
<Some function definitions>
int main(void){
<chip initializations>
while(1){
<Do this forever>
}
return(0);
}
Preamble
Preamble is where the information from other files, global variables and functions are defined. If you are using a library of functions or even reusing your own code, this is where you’ll do it.
The inclusion of avr/io.h is essential in every project as this library includes the functions for defining pins and ports. The library that defines delay function is also included in most of the projects, followed by other library that you may want to include.
#include <avr/io.h>
#include <util/delay.h>
You can use #define statement in this section to improve code readability and portability by defining reader-friendly synonyms for ports and pins. These are handled by the preprocessor, which goes through the code and perform replaces for all the definitions before compiling.
#define LED_PORT PORTA
Main Function
After the preamble comes the main() function. Your C program must have exactly one main() function and this is where the AVR starts executing the code when power goes ON first.
Inside the main function, there is a while(1) loop, often referred to as the main loop or event loop. What is inside this loop keeps running on forever as long as the power is ON.
Hardware Registers
Almost all the pins on AVR chips are configurable either as inputs or outputs. We do this by changing bits in a specific register assigned to each port. As these hardware registers are special memory locations, the compiler cannot treat them exactly as variables when we are assigning values to them. But these memory locations are spelled out in avr/io.h file so we don’t have to remember their numerical value but rather names like DDRA/PORTA. With that, we configure these pins as inputs or outputs simply by assigning these special variables certain values.
There are three most important hardware registers for digital inputs/outputs.
DDRx – Data Direction Registers of Port x
These registers control whether each pin is configured as input or output (the data direction). The default is zero, corresponding to input state. To enable a pin as output, you write a one to the respective bit in DDR.
For an example, say you want to set PC5 on Port C as output.
DDRC = 0b00100000; // Bit 5 on the DDR register of Port C is set to 1
PORTx – Data register of Port x
This register serves two different purposes when the respective pin is configured as input and output.
When the pin is configured as output, the PORT register controls whether that pin is set to logic high or low.
For an example, let’s write PC5 pins output to logic HIGH.
PORTC = 0b00100000;
When the pin is configured as input, the PORT register controls whether it has an internal pull-up resistor attached or not.
PINx – Input Pins Address of Port x
This are the registers that you read the digital voltage values for each pin that is configured as input. Each PINx memory location is connected to a comparator which detects whether the external voltage on that pin is high or low. You can read from them like a normal variable in your code.
For an example, to read the values of each pin that is configured as input on Port C,
input_values = PINC;
These registers are the basics you will have to learn when you are getting started. As you move forward, you will encounter many other special registers to configure serial communication, analog inputs, PWM and many more.
Now we will learn how we can manipulate these registers more effectively
We know how to configure IO pins as input or output and read/write to them. But have you noticed that, when we want to change a particular bit of a given pin, we have to define all the bits of that port? For an example, when we want to set PC5 as output by DDRC = 0b00010000, we are explicitly setting all other pins as inputs. But most of the we don’t really care about them.
You might also encounter times where you want to preserve the current state of register of pin but change another in the same port. Can’t we only access the required bit and change it? Yes! You can do that by bit manipulations.
Bit Manipulations for writing to registers
These bit manipulations are done suing two operation. The first is bit shifting to select the bit that should be changed and the second is logical operations to set that particular bit high or low.
Bit shifting
Wouldn’t it be nice, instead of writing 0b00001000, if we could say “give me a number with a 1 in bit number three”? It turns out bit shifting will exactly do that for us. They have the effect of rolling bits n positions to the left or right. Bits that fall of either end of disappear, and any new bits added are all zeros.
1 << 3 = 0b00000001 << 3 = 0b00001000
Anytime you want to set the nth bit to one, you can shift the value 1, n times to the left. (bit numbering starts at 0)
Say you want to set the ith and jth bit to one. You can do that by,
(1 << i) | (1 << j)
But bit shifting like this alone will set the remaining bits explicitly to zeros. To avoid defining other bits we can use logical operations AND/OR/XOR together with bit shifting.
Logical Operations with Bit Shifting
Setting bits with OR
Let’s learn how to set (assign logical high to) an individual bit in a register, leaving all the other bits as they were.
First we will study how OR operation works. If you draw the truth table of OR, you will notice that any bit(say A) ORed with 0 will output A (first two rows in the truth table). Any bit ORed with 1 will output 1 (last two rows on the truth table).
A | B | OUT |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
1 | 1 | 1 |
First, we will create a bitmask, which is simple a byte with 1s in bits where you want to set and 0s everywhere else.
For an example, if want to set bit number 1,3,7. The bitmask is 0b10001010. You can use bit shifting together will OR operations as explained above to generate this kind of bitmasks.
Then we will perform OR operation between the current byte and the bit mask.
PORTB = 0b11000011 // current state
bitmask = 0b00000100 // bit we want to set (in this case 2)
PORTB | bitmask = 0b11000111 // bit 2 set with other bits unaltered!
We will write the byte returned back to PORTB now.
PORTB = PORTB | bitmask
This operation is common that the shorthand version is what you will mostly prefer.
PORTB |= bitmask
Clearing a bit with AND and NOT
Running a similar analysis with AND gate, you will notice that if we AND any bit with 0, the output is 0 and if we AND any bit(say A) with 1, this output is A.
A | B | OUT |
---|---|---|
0 | 0 | 0 |
1 | 0 | 0 |
0 | 1 | 0 |
1 | 1 | 1 |
This means we need to have 0s at bits we want to clear and 1s everywhere else in our bitmask. These are the exact complements of the bitmasks we used before. The easiest is to create a mask with 1s where we want to change the bit and use NOT to flip the bits.
We will then perform AND operation between the current byte and the complemented bitmask.
PORTB = 0b11000011 // current state
bitmask = ~(0b00000010) = 0b11111101 // bit we want to clear (in this case 1)
PORTB & bitmask = 0b11000001 // bit 1 cleared with other bits unaltered!
We will write the returned byte back to the PORT using the shorthand version.
For an example, when you want to clear bit number 4 on Port B,
PORTB &= ~(1 << 4);
Toggling bits with XOR
Now imagine, you want to flip a bit. You don’t care care if it’s on or off right now, you just want the other state. For this you use XOR operation.
A | B | OUT |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
1 | 1 | 0 |
If a bit is XORed with 0, the bit is unchanged. If a bit is XORed with 1, the bit is flipped.
As you can now guess, all we have to do to toggle the bits is, generating a bitmask with 1s where we want to toggle the bit and 0s everywhere and perform XOR it with the current state.
PORTB = 0b11000011 // current state
bitmask = 0b00000010 // bit we want to toggle (in this case 1)
PORTB ^ bitmask = 0b11000001 // bit 1 toggled with other bits unaltered!
We will write the returned byte back to the Port using a shorthand version.
PORTB ^= bitmask
The Summary
Reading and writing digital inputs and outputs from the IO pins is done mainly using three kind of registers. In order to configure them,
- Configure the relevant pin as input/output using the DDR register at the initialization.
- Read digital inputs using PIN register or write digital outputs using PORT register within the main loop.
You can use bit manipulation to effectively change the bits in registers.
With this basic knowledge about special registers on AVR microcontroller and bit manipulations, you are ready to start exploring.
One Reply to “Programming in AVR C++ – Digital Outputs and Bit Manipulation”