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 own port logic’s. So before using them it is important to understand every detail of it so you could efficiently use in projects. Let’s see how ports are organized in AVR and how successfully control them.

Inside AVR port

If you try to look in to 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 in to this simple logic we can see several variants of operation. In order to enable output to pin we need to write logic ‘1’ to DDx.n pin. This will enable buffer to let bit through from PORTx register. If PORTx.n bit is ‘1’ then it can source pin with VCC voltage and up to 20mA of source; in other hand if logic ‘0’ is written then pin can source the target circuit.

If DDRx.n has logic ‘0’ value then buffer goes to high impedance tri-state (Hi-Z). So in this case PORTx.n is disconnected from pin, but there appears ability to read pin value directly. As PORTx.n bit is free we can have some use of it – write ‘1’ to it in order to enable internal pull-up resistor. Otherwise pin will be left on 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. As fact there one more as there is a special bit PUD in SFIOR register that disables all pull-ups if set to ‘1’. But this is a thing that may be used in special 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 seems very easy to program them. As fact it is. It can be as easy as two lines of code. Anyway we need to learn to do it smart so code parts could 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;
}

What we did here is wrote ones to DDR register that set port as output and then sent some dummy value 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 single port. As 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 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 really 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 last result is OR’ed with PORTA value without affecting higher nibble. If PA2=2, then ‘1’ is shifted to left by 2 positions resulting as ‘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 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 result we have ‘0b00001111’ then follows inverting operator ~ which converts this number to ‘0b11110000’ and then DDRA register is AND’ed with inverted value DDRA &=0b11110000 that leads to setting zeros  to lower nibble while leaving same values in higher nibble. Learn these as these expressions are commonly used. And probably you know from C that X &= Y is same as X = X & Y.

Adding some simplicity portability to port control code

First of all portable and simple doesn’t go well together. But there can be a middle agreement between these. Imagine 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 ported code may use different ports for same tasks and even different pins of port. So we want a generic code that would fit anywhere. Good news is that there is simple solution to that – defines. So at the beginning of program or even in separate file define target specific values to some generic ones like in 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 specific PORT and PIN names in program. We used generic names like INPORT for port or LED1 for 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. Program itself uses generic names for sending and receiving data. User has to assign specific ports and pins in #define section and forget. This way you don’t have to go through 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 area. Lets see how 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;
}

You see how program can be simplified by pushing expressions to compiler preprocessor area by defining them with single word. Compiler doesn’t care where expression actually is – compiler preprocessor takes vales and expressions from define area and replaces with defined words in program. These don’t affect program at all. If doing it smart you can make really compact code but 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 special functions used for bit manipulation functions like OUTDDR |=_BV(LED) that is actually same as OUTDDR |=(1<<LED).

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

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

Bookmark the permalink.

13 Comments

  1. Pingback: Electronics-Lab.com Blog » Blog Archive » Controlling AVR I/O ports with AVR-GCC

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

    x=(0x00001111)&&PINA;

    should be

    x=(0b00001111)&PINA;

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

  4. 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)

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

  6. Check the documentation. It works!

  7. 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);

  8. 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?…..

  9. 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.

  10. 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.

  11. 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;

  12. 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??

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

Leave a Reply

Your email address will not be published. Required fields are marked *