Controlling AVR I/O ports with AVR-GCC

Controlling pins is one of the first things to learn when learning microcontrollers. It seems that each microcontroller type has its port logic. So before using them, it is essential to understand every detail so you can effectively use them in projects. Let’s see how ports are organized in AVR and how to successfully control them.

Inside AVR port

If you try to look into any AVR datasheet, you will find port drawing which may seem a bit complex at the start. But for a simple start, let’s look at simplified port pin schematic.


x designates port (A,B,S,D,…); n designates pin number (0..7)

As you can see, each port consists of three registers DDRx PORTx, and PINx (for instance, DDRA, PORTA, and PINA). When looking into this simple logic, we can see several variants of operation. To enable output to the pin, we need to write logic ‘1’ to DDx.n pin. This will enable buffer to let bit through from PORTx register. If the PORTx.n bit is ‘1’, then it can source a pin with VCC voltage and up to 20mA of the source; on the other hand, if logic ‘0’ is written, then pin can source the target circuit.

If DDRx.n has logic ‘0’ value, then buffer enters the high-impedance tri-state (Hi-Z). In this case, PORTx.n is disconnected from the pin, but there appears to be an ability to read pin value directly. As PORTx.n bit is free, we can have some use of it – write ‘1’ to it to enable internal pull-up resistor. Otherwise, the pin will be left in the Hi-Z state, and you will need to connect external pull-ups to read pins correctly. And this is pretty much about ports. Let’s sum this up in a simple table:

 DDRx = 0DDRx = 1
PORTx = 0Read to PINX (tri-state)Pin output ‘0’
PORTx = 1Read to PINx with pull-upPin outputs ‘1’

So generally, we have four different states depending on DDRx and PORTx values. There one more, as there is a particular bit PUD in SFIOR register that disables all pull-ups if set to ‘1’. But this is a thing that may be used in exceptional cases and won’t be discussed later.

Programming AVR ports with AVR – GCC

Now it’s time to move on programming ports. When we cleared things out about hardware, it seemed very easy to program them. As a fact, it is. It can be as easy as two lines of code. Anyway, we need to learn to do it smartly so code parts can be used on any AVR. We already know that each port consists of three 8-bit registers:

#include <avr/io.h>
int main(void)
{
DDRA=0b11111111;//set all pins as output
PORTA=0b10101010;//write ones and zeros to pins
return 0;
}

We wrote ones to DDR register that set the port as output and then sent some dummy values to PORTA pins. This is OK if we need to affect all 8 bits at once. But what if we need to control separate bits of a single port? As the fact, some bits may be used as output and other as input. For this, we need to do some bit manipulation. Let’s say we need first four bits as input with pull-up and last four as output. Call these groups as nibbles. Each pin of any port has its name defined in io.h library so that we can do this way:

#include <avr/io.h>
int main(void)
{
uint8_t x;
//set lower nibble to input
//This operation clears bits 0,1,2,3 of DDRA
DDRA &=~((1<<PA0)|(1<<PA1)|(1<<PA2)|(1<<PA3));
//Setting pulls-up
PORTA |=(1<<PA0)|(1<<PA1)|(1<<PA2)|(1<<PA3);
//set higher nibble to output
/*bit shift “<<”operation and logical OR "|"
used to set bits 4,5,6,7 to “1”*/
DDRA |=(1<<PA4)|(1<<PA5)|(1<<PA6)|(1<<PA7);
//write value to higher nibble
PORTA |=(1<<PA4)|(1<<PA5)|(1<<PA6)|(1<<PA7);
//read value from lower nibble
//and filter out high nibble
x=(0b00001111)&PINA;
return 0;
}

Hopefully, you understand how bit shift ‘<<’ or ‘>>’ operation works. This is simple once you get used to it. For instance, in expression PORTA |= (1<<PA0)|(1<<PA1)|(1<<PA2)|(1<<PA3), each bit of lower nibble is shifted left by PAn position and then OR’ed between each other, and the last result is OR’ed with PORTA value without affecting higher nibble. If PA2=2, then ‘1’ is shifted to left by two positions resulting in ‘0b00000100’. So (1<<PA2) is equal to (1<<2) and equal to ‘0b00000100’. When all bits are shifted, we get:

0b00000001(1<<PA0) = (1<<0)
0b00000010(1<<PA1) = (1<<1)
0b00000100(1<<PA2) = (1<<2)
0b00001000(1<<PA3) = (1<<3)
0b00001111(1<<PA0)|(1<<PA1)|(1<<PA2)|(1<<PA3)

And then we OR this value with PORTA like this: PORTA |= 0b00001111 – the higher nibble stays untouched.  If we need to set ‘0’ bit value in the port register while retaining other bits operation looks like this:

DDRA &=~((1<<PA0)|(1<<PA1)|(1<<PA2)|(1<<PA3))

Operation in parenthesis is clear, and as a result, we have ‘0b00001111’ then follows inverting operator ~, which converts this number to ‘0b11110000’. Then DDRA register is AND’ed with inverted value DDRA &=0b11110000, that leads to setting zeros to lower nibble while leaving same values in the higher nibble. Learn these as these expressions are commonly used. And probably you know from C that X &= Y is the same as X = X & Y.

Adding some simplicity portability to port control code

First of all, portable and simple don’t go well together. But there can be a middle agreement. Imagine a situation that you wrote damn good code piece that is quite long enough, so you would want to copy it to another project instead of rewriting it. The problem is that the ported code may use different ports for the same tasks and even different pins of the port. So we want a generic code that would fit anywhere. The good news is that a simple solution exists – defines. So at the beginning of the program or even in separate files, define target-specific values to some generic ones like in the following example:

#include <avr/io.h>
//define ports and pins
#define INDDR DDRA
#define INPORT PORTA
#define INPIN PINA
#define OUTDDR DDRB
#define OUTPORT PORTB
#define BUTTON PA0
#define LED PB0
int main(void)
{
uint8_t x;
//set BUTTON pin as input
INDDR &=~(1<<BUTTON);
//Setting internal pull-up for button
INPORT |=(1<<BUTTON);
//seting LED pin as output
OUTDDR |=(1<<LED);
//turning LED1 ON
OUTPORT |=(1<<LED);
//read BUTTON1 state
//and test with mask
x=(1<<BUTTON)&INPIN;
return 0;
}

What we did here is that we got rid of particular PORT and PIN names in the program. We used generic names like INPORT for port or LED1 for the PIN. This way program may be reused in any AVR chip with different ports and pins assigned with keyword #define. This way, there are many specific hardware libraries written. For instance, LCD control driver. The program itself uses generic names for sending and receiving data. The user has to assign particular ports and pins in #define section and forget. This way, you don’t have to go through the whole program looking for operations with ports and pins and renaming them each time. The more generic program is – it is more difficult to analyze it. So be sure to use self-descriptive names of ports and pins.

You know program may become simpler by pushing more operations to define the area. Let’s see how the above program can be changed:

#include <avr/io.h>
//define ports and pins
#define INDDR DDRA
#define INPORT PORTA
#define INPIN PINA
#define OUTDDR DDRB
#define OUTPORT PORTB
#define BUTTON PA0
#define LED PB0
#define LEDON OUTPORT |=(1<<LED)
#define BUTTONIN (1<<BUTTON)&INPIN
int main(void)
{
uint8_t x;
//set BUTTON pin as input
INDDR &=~(1<<BUTTON);
//Setting internal pull-up for button
INPORT |=(1<<BUTTON);
//seting LED pin as output
OUTDDR |=(1<<LED);
//turning LED ON
LEDON;
//read BUTTON state
x=BUTTONIN;
return 0;
}

The program can be simplified by pushing expressions to compiler preprocessor area by defining them with a single word. The compiler doesn’t care where the expression is – the compiler preprocessor takes values and expressions from the defined area and replaces them with defined words in the program. These don’t affect the program at all. If you do it smartly, you can make a compact code that is not always human-readable. So use preprocessor commands wisely.

Of course, there are more ways of writing port control code. In many cases, there are particular functions used for bit manipulation functions like OUTDDR |=_BV(LED) that is same as OUTDDR |=(1<<LED).

All examples above are working and compile with no errors in AVRStudio.

Don’t hesitate to share in the comments your methods of controlling AVR ports with gcc.

 

13 Comments:

  1. Minor typo corrections (logical-AND should be bitwise-AND, binary not hex format):

    x=(0x00001111)&&PINA;

    should be

    x=(0b00001111)&PINA;

  2. Thank you for correcting this. Already updated code.

  3. AVRs also implement an output toggle function by writing a 1 to the PIN register. Always use a direct write for this (not a read-modify-write because the read and the write functionality are not related).

    For example
    PINA = 0b10000000;
    will toggle PORTA[7] (provided it is an output)

  4. DT, I doubt this will work. Writing to PINA won’t have affect to PORTA.

  5. Check the documentation. It works!

  6. I think you are confusing this with something else. First AVR documentation tells that PINx register is readonly – you cannot write to it. if you look at port pin schematic you will find that there is no way to affect PORTx or pin itself bu writing something to PINx register. if you need to toggle you usually use XOR operation; Like here for bit 7
    PORTx ^= (1<<7);

  7. Oh dear. I thought this was an expert AVR site.

    I use this well documented feature of the AVR IO pins all the time. I quote from the mega328P datasheet: “Writing a logic one to PINxn toggles the value of PORTxn, independent on the value of DDRxn.
    Note that the SBI instruction can be used to toggle one single bit in a port.”

    If you look at the port schematic, see that flip-flop with the input fed from an inverted output?…..

  8. OK it seems you are right. If you look in Atmega328/48/168… datasheet there is a change in port schematic that alows doing that. Anyway – this is a microcontrooler specific feature. You can’t do that trick with standard Atmegas like 8/16/32/64/128. It would be correct to say that this will work in some later types of AVR MCUs.
    Anyway thanks for revealing this as this might be useful for someone.

  9. Instead of:
    DDRA &=~((1<<PA0)|(1<<PA1)|(1<<PA2)|(1<<PA3));
    could one use:
    DDRA &=((0<<PA0)&(0<<PA1)&(0<<PA2)&(0<<PA3));
    ??

    I generally find positive logic to be easier to debug.

  10. You can’t use expression:
    DDRA &=((0<<PA0)&(0<<PA1)&(0<<PA2)&(0<<PA3));
    because other bits (4,5,6 and 7) are 0
    like DDRA &=0b00000000;
    so AND operation will erase high nibble of DDRA. And we do not want to affect those…
    This is why
    DDRA &=~((1<<PA0)|(1<<PA1)|(1<<PA2)|(1<<PA3));
    sets necessary bits to ones first and then inverts whole byte so AND operation only resets whats needed and other bits are AND with ones like:
    DDRA &=0b00001111;

  11. is “PAn=n” as in “PA2=2” (to be used in (1<<PA2)=(1<<2) ) predefined in some .h file?
    or do we need to always define each pin explicitly using macros like
    #define PA1 1
    #define PA4 4 and so on??

  12. It might depend on toolchain and library you are using. AVRGCC have these things already defined. For instance in iom328p.h:
    #define PINB7 7

Comments are closed