AVR - GPIO (General Purpose Input  Output):

All AVR micro-controllers have GPIO pins, which are used for reading or writing digital data (either logic 0 or 1).  You, as a programmer use the GPIO control so frequently in your programs and knowledge about GPIO is very essential before programming any micro-controller, including AVR.

GPIO for AVR is controlled using respective Registers.  Almost all AVR external pins may be configured as GPIO pins, except power supply pins etc.  AVR identifies GPIO registers using Alphabet, like A, B, C, D . . .

A set of GPIO pins are grouped together (nomally 8 or less, depends on availability of external pins) and named as PORT_A, PORT_B, PORT_C, PORT_D . . . and so on.  The GPIO oridentation, the number of ports and number of bits (maximum 8) depends on the micro-controller pins availability and its pinouts. 

So, each port and each bit in a port, may be configured as either Input or Output.  If, a port or a bit of a port is configured as input, then it can read the logical value ( either 0 or 1) from the external pins.  Similarly, if a port or a bit of a port is configured as output, then it can be (used to send) set to the required logical value (either 0 or 1).

Before using the registers the following statement should be used in the program:

#include <avr/io.h>  // input and output header file

The above statment should be declared globally in the program before using the registers and main function / method to identify the registers with its names explained below.

Many concepts are used to program GPIO. The programming / code explained below is directly accessing the register(s) and simple to understand. 

Programming AVR GPIO for DIRECTION:

While programming GPIO, initially set the direction of data transifer (read / write) by setting the DDR (Data Direction Register) of the particular port, like DDRA for PORT_A, DDRB for PORT_B, DDRC for PORT_C. . . so on.  You have to check the existance of particular port and number of pins for the selected micro-controller, before programming.

eg 1: write the following code (any one line) in your program to set 3 MSB (Most Significant Bits) as OUTPUT and 5 LSB (Least Significant Bits) as INPUT for PORT_A.  So, note that, setting a bit to 1 for OUTPUT and 0 for INPUT.

DDRA = 0b11100000;  // Binary format for easy under standing

DDRA = 0xE0;                // Hexadecimal format  

eg 2: write the following code (any one line) in your program to set 6 MSB as OUTPUT and 2 LSB  as INPUT for PORT_B.  

DDRB = 0b11111100;  // Binary format 

DDRB = 0xFC;                // Hexadecimal format  

Programming AVR GPIO for OUTPUT:

Once the direction of data transfer is set as OUTPUT in DDR register of the particular port and its bits (by setting 1s), then the actal data may be WRITTEN to its Ouput Register PORTA or PORTB or PORTC or PORTD . . . so on.  The value to the register may be written as a whole ( all 8 bits, i.e., a byte ), or each required bit of the register.  The value in the register reflects at the external pins of the micro controller connected to the register, named as port and its bits.

eg 1: write the following code (any one line) in your program to set all 8 bits (byte) in a single command / statement.  So, note that, setting a bit to 1 in a port register is for logic HIGH and 0 for logic LOW, which reflects the value as output at the connected external pin of the micro-controller.

PORTA = 0b11001000;  // Binary format for easy under standing

PORTA = 0xC8;                 // Hexadecimal format  

eg 2: write the following code (any one line) in your program to set logic 1 for only one bit (PB2 or bit 2) of register PORT_B, without disturbing other bits in the PORT_B.  Here, logic OR symbol ( | ), is used for the purpose. The | symbol is also called pipe key on keyboard and commonly used for logic OR in C, C++, Java etc.

PORTB |= 0b00000100;  // Binary format 

PORTB |= 0x04;                // Hexadecimal format  

eg 3: write the following code (any one line) in your program to set logic 1 for ANY bits (PC1, PC4 & PB7) of register PORT_C, without disturbing other bits in the PORT_C.  Here also, logic OR symbol ( | ), is used, wherever the value of bits to be changed to 1.

PORTC |= 0b10010010;  // Binary format 

PORTC |= 0x92;                // Hexadecimal format  

eg 4: write the following code (any one line) in your program to set logic 0 for only one bit (PB4 or bit 4) of register PORT_B, without disturbing other bits in the PORT_B.  Here, logic AND symbol ( & ), is used for the purpose. The & symbol is also called ampersand key on keyboard and commonly used for logic AND in C, C++, Java etc.

PORTB &= 0b11101111;  // Binary format 

PORTB |= 0xEF;                // Hexadecimal format  

eg 5: write the following code (any one line) in your program to set logic 0 for ANY bits (PD2, PD3 & PD6) of register PORT_D, without disturbing other bits in the PORT_D. 

PORTD |= 0b01001100;  // Binary format 

PORTD |= 0x4C;                // Hexadecimal format  

Programming AVR GPIO for INPUT:

Once the direction of data transfer is set as INPUT in DDR register of the particular port and its bits (by setting 0s), then the actal (logical) data may be READ from external pins of the port, which is always available at its Input Register PINA or PINB or PINC or PIND . . . so on.  The value of the register may be read as a whole ( all 8 bits, i.e., a byte ), or each required bit of the register.  The value in the register reflects the logical value available at the external pins of the micro controller connected to the register.

eg 1: write the following code (any one line) in your program to read all 8 bits (byte) in a single command / statement.  Here, always read FULL register (8 bits) named as PIN, then analyse the required bit(s) using logical operators. So, the external logical values are always available at registers PINA, PINB, PINC, PIND . . . etc., and you are reading the values, whenever is required while programming.

unsigned char val = PINA;  // unsigned char can hold 8 bits and PINA size is also 8 bits.

int val = PINA;                     // int can hold 16 bits and PINA size is 8 bits

eg 2: write the following code (any one line) in your program to  read only one bit (PB3 or bit 3) of register PORT_B and save it to a variable, for further processing.  Here, logic AND symbol ( & ), is used for the purpose. 

unsigned char val = (PINB & 0b00001000);  // Binary format 

unsigned char val = (PINB & 0x08);                // Hexadecimal format  

eg 3: write the following code (any one line) in your program to  read multiple bits (PC6 and PC1) of register PORT_C and save it to a variable, for further processing.  Here also, logic AND symbol ( & ), is used for the required bits to read.. 

unsigned char val = (PINC & 0b01000010);  // Binary format 

unsigned char val = (PINC & 0x42);                // Hexadecimal format  

Full Programming Code for AVR GPIO:

Note the steps to start programming to access and control GPIO pins of AVR micro-controller as

1) include input and output header file and other required header files.

2) define by name for any required pins

3) start main function / method

4) set Data Direction Registers as per your requirement.

5) use Port registers. i.e., Read from Registers or Write to Registers 

6) you may use loop to make it continuous operation(s).

eg 1: writing the code for sending high output to all bits of PORT_B and 3 LSB of PORT_A

(you may change to any PORT, which is avialable for the selected AVR micro-controller)

#include <avr/io.h>  // input and output header file

int main ( )           // start main function / method

{

   DDRB = 0xFF;        // set (all bits) Data Direction of PORT_B as output

   DDRA = 0b00000111;  // set (3 LSB) Data Direction of PORT_A as output, remaining as input

   PORTB = 0xFF;       // set (all bits) Data in PORT_B as high

   PORTA = 0x07;       // set (3 LSB) Data in PORT_A as high

   return 0;           // exit from main

}

Once you compile and write the HEX code to AVR micro-controller, then the external pins of PB0 to PB7 and PA0 to PA2, were set to  logic high (about Vcc voltage of micro-controller), which may be tested with an LED (with series resistance) or using mult-meter in voltage mode.

eg 2: writing the code for reading input value from one port (POPRT_A) and send the same to another port (PORT_B)

(you may change to any PORT, which is avialable for the selected AVR micro-controller)

#include <avr/io.h>  // input and output header file

int main ( )         // start main function / method

{

   unsigned char val;  // use a variable to store the value read from the input port

   DDRB = 0xFF;        // set (all bits) Data Direction of PORT_B as output

   DDRA = 0b00000000;  // set (all bits) Data Direction of PORT_A as input

       while ( 1 )         // start a never ending loop

      {

     val = PINA;       // read (all bits) from PORT_A save it to variable val

     PORTB = val;      // write (all bits) value avialble in val to PORT_B

      }                   // end of while(1) loop and control goes to first statement in while loop.

   return 0;           // exit from main

}

Once you compile and write the HEX code to AVR micro-controller, then the external pins of PB0 to PB7 are replica of PA0 to PA7, in logic levels.  i.e., if any pin of PORT_A is set to high (connect to Vcc through a series resistance), like PA3, then same pin of PORT_B (PB3) also set to logic high (about Vcc voltage of micro-controller), which may be tested with an LED (with series resistance) or using mult-meter in voltage mode.

SUMMARY:

The main registers used to control, read and write the GPIO of the AVR micro controller are:

 DDRX, PINX, PORTX, where X is the port name A / B / C / D ...

for more details, refer at following link (courtesy:microchip):

 http://ww1.microchip.com/downloads/en/Appnotes/90003229A.pdf