Pages Menu
TwitterRssFacebook
Categories Menu

Posted by on Jun 10, 2011 in Atmel AVR, Microcontrollers | 178 comments

I/O Port Operations in AVR

I/O Port Operations in AVR

AVR SeriesHello friends! In this post, we will discuss about the port operations in AVR. Before going further, I suggest that you read my previous post regarding AVR Basics. The examples discussed here are in accordance with ATMEGA16/32 MCU. However, the concepts are equally good for any AVR MCU.

Register

Okay, now I hope you are familiar with the term register. If not, then you must have heard of it. Basically, a processor register is a memory space within the CPU itself so that they can be accessed very frequently and fast. These registers are linked with the operation of the MCU. Let’s consider the following memory space.

Register Memory Space

Register Memory Space

Here, you can see that I have represented 8 bits together to form a memory of 1 byte. Note the sequence in which the bits are numbered. They are numbered as 7, 6, 5, … , 1, 0. This is because the bits are numbered from the Least Significant Bit (LSB) to the Most Significant Bit (MSB). From the knowledge of digital logic, we know that the last bit is the LSB whereas the first bit is the MSB. Hence, Bit 0 = LSB and Bit 7 = MSB.

Register Concept Explained

Let me explain you why LSB is the last bit. Let’s take an example. Please note that 0b stands for binary and 0x stands for hexadecimal. If nothing is prefixed, it means that it is in decimal number system.

A = 0b 0110 0111 = 103

Now here, let’s change the value of the last bit (orange colour bit) from 1 to 0. This makes

B = 0b 0110 0110 = 102

Now, once again in A, let’s change the first bit (magenta colour bit) from 0 to 1. This makes

C = 0b 1110 0111 = 231

We see that by changing the last bit, the result (B) is very close to the original data (A), whereas by changing the first bit, the result (C) varies quite significantly from the original data (A). Hence, the last bit is the LSB (as the data doesn’t change significantly) whereas the first bit is the MSB (as the data changes significantly).

Now, we also know that 1 nibble = 4 bits. Hence, bits 0,1,2,3 are called lower nibble whereas bits 4,5,6,7 are called upper nibble. So basically, a register is a memory allocated in the CPU, usually having a size of 1 byte (8 bits).

Next, every register has a name, and every bit of it also has a name. Take the following example.

ADMUX

ADMUX Register

Here, the name of the register is ADMUX (don’t worry about the name, we will discuss it later). Also, note that each bit also has a name and an initial value. Each bit of the register can have a value of either 0 or 1 (initially it is 0). Now suppose, I write

ADMUX = 0b01000111;

This means that the ADMUX register has been updated as follows:

ADMUX Register after setting values

ADMUX Register after setting values

This can also be achieved by the following codes:

ADMUX = (1<<REFS0)|(1<<MUX2)|(1<<MUX1)|(1<<MUX0);
ADMUX = 0x47;    //Hex form

So, got an idea of what registers are and how are they defined/initialized? We will discuss about various registers one by one as required. Right now, we are concerned with only three registers, DDR, PIN and PORT.

Port Operation Registers

The following registers are related to the various port operations that we can perform with the GPIO pins.

  • DDRx – Data Direction Register
  • PORTx – Pin Output Register
  • PINx – Pin Input Register

where x = GPIO port name (A, B, C or D)

Different Port Operations

Different Port Operations

DDRx Register

As I mentioned earlier, the GPIO pins are the digital I/O pins i.e. they can act as both input and output. Now, how do we know that the pin is an output pin or input? The DDRx (Data Direction Register) helps in it.

DDRx initializes the port. Have a look at it’s bit structure.

DDRx Register

DDRx Register

The ‘x’ in DDRx is just for representation. It is replaced by the corresponding port i.e. x = A, B, C, D. Say for the example shown in the diagram above

DDRC = (1<<DDC0)|(1<<DDC4)|(1<<DDC5)|(1<<DDC7);

This is equivalent to

DDRC = 0b10110001;

and as well as

DDRC = 0xB1;

and even

DDRC = (1<<0)|(1<<4)|(1<<5)|(1<<7);

So, did you get how to declare it? Suppose I would like to initialize my port B, then I would have written

DDRB = (1<<DDB0)|(1<<DDB4)|(1<<DDB5)|(1<<DDB7);
DDRC Example

DDRC Example

All right, now that we are done with the declaration, let me explain you what it does. Always remember, in the case of DDRx, 1 stands for output and 0 stands for input. In the following statement (given below), port C is initialized such that the pins PC0, PC4, PC5 and PC7 are output pins whereas pins PC1, PC2, PC3 and PC6 are input pins.

This is represented in the adjoining figure. The pins marked as input now has the capability to read the voltage level at that pin and then treat it as HIGH if it is above the threshold level, or else it will treat it as LOW. Generally, the threshold level is half the VCC.

Similarily, the pins marked as output have the capability to either become HIGH (voltage = VCC) or LOW (voltage = zero) as directed/written in the code.

DDRC = (1<<DDC0)|(1<<DDC4)|(1<<DDC5)|(1<<DDC7);

PORTx Register

The PORTx register determines whether the output should be HIGH or LOW of the output pins. In simplified terms, once the DDRx register has defined the output pins, we need to set them to give an output of HIGH or LOW. The PORTx register goes as follows.

PORTx Register

PORTx Register

The register declaration is similar to the DDRx register, except that we change the names, that’s all! One such example is given above in the diagram. The following declarations are one and the same.

PORTD = (1 << PD0)|(1 << PD3)|(1 << PD6);
PORTD = (1 << 0)|(1 << 3)|(1 << 6);
PORTD = 0b01001001;
PORTD = 0x49;

Now, let’s consider the following statements:

DDRC   = 0b10110001;
PORTC  = 0b10010001;
OUTPUT = 0b10010001;  /*This is not a C executable line, this line is just for explanation*/

The port C is initialized using the DDRx register. The highlighted bits correspond to the output pins. Now, just concentrate on the highlighted bits only. Since they are output pins, wherever I state ‘1’ in PORTC, that pin goes HIGH (1), giving an output voltage of VCC at that pin.

Now consider the following set of statements:

DDRC   = 0b10110001;
PORTC  = 0b10010101;
OUTPUT = 0b10010001;  /*This is not a C executable line, this line is just for explanation*/

Once again, the bits highlighted in orange correspond to the output pins. So, whatever value (0 or 1) desired in the orange region is reflected in the output. Now, look at the magenta highlighted bit. Inspite of being set as HIGH in the PORTx register, the output is LOW. This is because that pin is initialized as an input pin by DDRx. Hence, PORTx cannot change the properties of that pin. Hence, in general, PORTx cannot modify the properties of a pin which is initialized as input by DDRx.

PINx Register

The PINx register gets the reading from the input pins of the MCU. The register goes as follows:

PINx Register

PINx Register

The register declaration procedure stands the same. Also note that the names of the bits of PORTx and PINx registers are same.

Now, let’s consider the following statements:

DDRC   = 0b10110001;
PINC   = 0b01001011;
INPUT  = 0b01001011;  /*This is not a C executable line, this line is just for explanation*/

Here, the highlighted bits correspond to the pins that are initialized as input by the DDRx. In the second line, the PINx register is defined. Well, this line is just to explain the concept, practically, we always use PINx as a condition (like in IF or in WHILE loop). As per the second statement, the PINx command reads the values only at the input pins.

Now, consider the following set of statements:

DDRC   = 0b10110001;
PINC   = 0b01011010;
INPUT  = 0b01001010;  /*This is not a C executable line, this line is just for explanation*/

Here, you can compare it with the example I gave for PORTx. Since the magenta-highlighted bit is an output pin, PINx cannot change it’s properties. Hence, in general, PINx cannot modify the properties of a pin which is initialized as output by DDRx and vice versa.

Example Code Snippet

Let’s demonstrate the use of the DDRx, PORTx and PINx registers from the following code snippet:

DDRC = 0x0F;
PORTC = 0x0C;

// lets assume a 4V supply comes to PORTC.6 and Vcc = 5V
if (PINC == 0b01000000)
    PORTC = 0x0B;
else
    PORTC = 0x00;

Code Explained:

  • DDRC = 0x0F; is equivalent to DDRC = 0b00001111; This means that the pins PC0…PC3 are output pins (can be manipulated using PORTC) and pins PC4…PC7 are input pins (whose levels determine the value of PINC).
  • PORTC = 0x0C; is equivalent to PORTC = 0b00001100; This means that the pins PC2 and PC3 have a HIGH voltage (Vcc = 5V) and pins PC0 and PC1 have LOW voltage (0V). The other pins have low voltage by default.
  • if (PINC = 0b01000000) checks the input voltage at pin PC6. Since it is mentioned in the comment that a 4V is supplied to PORTC.6 (same as pin PC6), this condition is true (as 4 > 2.5, where 2.5V is the threshold, 5/2 = 2.5).
  • Since the if condition is true, PORTC = 0x0B; is executed.
  • If the if condition is not satisfied, PORTC = 0x00; will be executed.

We can also put it inside a while loop to run it continuously i.e. it always checks for the voltage at pin PC6, and the outputs at PC0, PC1 and PC3 go high only if the voltage at PC6 is greater than 4V.

DDRC = 0x0F;

while(1)
{
    // a condition of 4V supply to PORTC.6 and Vcc = 5V
    if (PINC == 0b01000000)
        PORTC = 0x0B;
    else 
        PORTC = 0x00;
}

To learn how to code and simulate using AVR Studio 5, visit this page.

So, here we end up with the port operations that can be performed with an AVR MCU. Though the concept has been explained using ATMEGA16/32, the concepts are equally good for any AVR MCU! Just go through the datasheet in order to get an idea of the registers.

Thank you for reading this! The comments’ box is down below! ;)

178 Comments

  1. Thanks for a brilliant tutorial! I’m still a bit confused about PINx. Do we actually assign (declare) a value to it, or is it a “read-only” variable? In the example,
    {code}
    DDRC = 0b10110001;
    PINC = 0b01011011;
    {code}
    INPUT = 0b01001011;

    Bit 0 and bit 4 of DDRC are both set to 1, meaning P0 and P4 are used for output. The two respective bits in PINC are also 1. (Bit 4 is highlighted in magenta, bit 0 is not.) Yet in the input illustration, bit 0 is 1 and bit 4 is 0. What is the difference? Is the input a preexisting condition? In that case, placing it above the C code would make this clearer.

    In this statement, “the pins PC0…PC3 are output pins (can be manipulated using PORTC) and pins PC4…PC7 are input pins (can be manipulated using PINC),” is it correct to think “the pins PC0…PC3 are output pins (whose levels can be manipulated by assigning different values to PORTC) and pins PC4…PC7 are input pins (whose levels determine the value of PINC)?”

    • Oh my! Thanks for pointing out the flaw! I have made the necessary changes… :) Seems like you have clearly understood the concepts!

      In this statement, “the pins PC0…PC3 are output pins (can be manipulated using PORTC) and pins PC4…PC7 are input pins (can be manipulated using PINC),” is it correct to think “the pins PC0…PC3 are output pins (whose levels can be manipulated by assigning different values to PORTC) and pins PC4…PC7 are input pins (whose levels determine the value of PINC)?”

      Yes of course! Why not!

      PS. Sorry for the late reply, I was stuck somewhere.

      • thank u soo much for sharing valuable knowledge,,,thanks a lot ..its very very help full
        for me,, even a 5th grad student can understand,,,nice way of explaing concepts very clear…

        • Thanks a lot Nava. I am delighted to hear this :)

  2. DDRC = 0x0F;

    while(1)
    {
    // a condition of 4V supply to PORTC.6 and Vcc = 5V
    if (PINC = 0b01000000)
    PORTC = 0x0B;
    else
    PORTC = 0x00;
    }

    The above code in your tutorial shall give you error when you compile it. The IF statement is to check whether the PORTC.6 reads HIGH is true, then you should use ‘==’ to check rather than ‘=’. A single ‘=’ means “assign to”, which means you actually say assign PINC pin 6 to HIGH not check if it is HIGH, unless I remembered wrong. Can you please comfirm?

    Another question, when we use PINCx, we normally read the voltage applied on this pin, so we should always use ‘==’ to check the condition, right?

    • Hello Jacky!
      Thanks for pointing out the flaws, I guess I missed them somehow! I have updated my post. :)
      Yes, you should always use ‘==’ with PINx (that’s what is recommended, unless you are up to some specific application or something).

  3. Hi
    I am making 16*16 keypad(on PORTB and PORTD) with ATMega32 using proteus 6.9 and AVRstudio5.
    My problem is PB7 and PD7 does not work as designed. I changed PORTs to PORTC and again PC6 and PC7 doesnt work.
    In any case PC6 and PC7 doesnt give output. I tried changing SFR and too got no solution.
    My avr code is given below.
    Please help. I am wasting a lot of days on this.

    #define F_CPU 8000000
    #include
    #include

    #define keyportpin1 PINB
    #define keyportpin2 PIND
    #define keyport1 PORTB
    #define keyport2 PORTD
    #define keyportdirection1 DDRB
    #define keyportdirection2 DDRD

    //#define bit_clear(bit) (!(keyportpin&(1<<bit)))

    int keyboard(void)
    {
    keyportdirection1=0x00;
    keyportdirection2=0xFF;

    int key=1;
    for(int i=0;i<8;i++)
    {
    keyport1=0xFF;
    keyport2=0xFF;
    keyport2&=(0xFE<<i);
    keyport2|=(0xFE<<i);

    if (!(keyportpin1&(1<<PB0)))
    {

    while (!(keyportpin1&(1<<PB0)))
    {}
    return key;

    }

    if (!(keyportpin1&(1<<PB1)))
    {

    while (!(keyportpin1&(1<<PB1)))
    {}
    key+=1;
    return key;
    }
    if (!(keyportpin1&(1<<PB2)))
    {

    while (!(keyportpin1&(1<<PB2)))
    {}
    key+=2;
    return key;
    }
    if (!(keyportpin1&(1<<PB3)))
    {

    while (!(keyportpin1&(1<<PB3)))
    {}
    key+=3;
    return key;
    }
    if (!(keyportpin1&(1<<PB4)))
    {

    while (!(keyportpin1&(1<<PB4)))
    {}
    key+=4;
    return key;
    }
    if (!(keyportpin1&(1<<PB5)))
    {

    while (!(keyportpin1&(1<<PB5)))
    {}
    key+=5;
    return key;
    }
    if (!(keyportpin1&(1<<PB6)))
    {

    while (!(keyportpin1&(1<<PB6)))
    {}
    key+=6;
    return key;
    }
    if (!(keyportpin1&(1<<PB7)))
    {

    while (!(keyportpin1&(1<<PB7)))
    {}
    key+=7;
    return key;
    }

    key+=7;
    }
    return 0;
    }

  4. amazing site !!!

  5. You are an excellent teacher! Do consider taking up teaching as a carrier! Keep up your great work!

  6. Hi,

    Thanks for creating this great tutorial!!
    Because of this tutorial I understand the basics and the datasheets.
    Now I can start creating the projects I want to create.

    • Thanks essam. I will write that tutorial in my own style soon!

  7. awesome tutorial and very good explanation :) go on :)
    i want to ask a question about the difference between if i write :

    for ex: i want to check if the pin c.6 is high or not and i used for that if condition as follows :

    if (PINC && 0b01000000)

    and

    if (PINC & 0b01000000)

    what expression should I use for that and why not the other ?

    and thanks in advance :)

    • Hi Ahmad,
      This is a classic example of the following two cases:

      Case 1: I wanna check whether pinc.6 is high or not, and I don’t give a damn to other pins
      Solution: Use if(PINC & 0b01000000)
      Explanation: The following two values of PINC will give the same result– 0b01000000 and 0b01001000

      Case 2: I wanna check whether “only” pinc.6 is high or not, and NO other pins should be high
      Solution: Use if(PINC && 0b01000000)
      Explanation: The following two values of PINC will give different results– 0b01000000 and 0b01001000

      I hope you get it!

  8. u hav mentioned as PINC in the condition if(PINC == 0b01000000)
    actually we need to check pin 6 of portC , so is it not if(PC6 == 0b01000000) ???

    ie how come PINC check for particular pin 6 of PORTC ???

    • Hi Karthik
      The condition if(PINC==0b01000000) checks for the condition of the entire port. If you want to check for individual pins, then read my reply to Ahmad’s comment above.

      • hi
        if(PINC==0b01000000) checks for the condition of the entire port , then (assuming pins 7 & 6 are input pins) if HIGH IS APPLIED to only pin 6 , it will return false ,right ?

        • Yup! It will return false. If you wish to monitor two pins, you should use bit operators.
          In the example given in the above post, I considered that only one switch is connected to PC6.

          • thank you ….

          • hello mayank bhaiya can you tell me that in AVR even also in 8051 micro-controller, is it possible to call interrupt from while function when its requirement……
            interrupt will complete its task and will return to main loop and again main function should be settle from where it left its task for hold …….
            is it possible??

          • Hi Sachet
            Yes, it is possible. This should help you out.

  9. Hi,
    Thanks for your tutorial & keep moving with this great job . . . :)

    I have a problem using PIN identification and using IF () ELSE.

    i use this code:

    while (1)
    {

    test=0b00000000;
    test=(PINC & 0b00010000); // check PINC.4

    if (test == 0b00010000)
    {
    PORTD=0b00001111;
    _delay_ms(300);
    PORTD=0b00000000;
    _delay_ms(100);
    }
    else
    {
    PORTD=0b11110000;
    _delay_ms(300);
    PORTD=0b00000000;
    _delay_ms(100);
    }

    }

    When there is no voltage supply to PIN C4, the microcontroller will execute both:
    //this
    PORTD=0b00001111;
    _delay_ms(300);
    PORTD=0b00000000;
    _delay_ms(100);
    // and this
    PORTD=0b00001111;
    _delay_ms(300);
    PORTD=0b00000000;
    _delay_ms(100);

    But, when i apply 5V supply to PINC4, the microcontroller will only looping for this:
    PORTD=0b00001111;
    _delay_ms(300);
    PORTD=0b00000000;
    _delay_ms(100);

    What I want to happen:
    When there is no voltage supply to PINC4, the Micro JUST run for this:
    PORTD=0b00001111;
    _delay_ms(300);
    PORTD=0b00000000;
    _delay_ms(100);

    And, when i apply 5V supply to PINC4, the Micro will JUST run for this:
    PORTD=0b00001111;
    _delay_ms(300);
    PORTD=0b00000000;
    _delay_ms(100);

    Can you tell me, what is wrong with the code?
    Thank You very much. :)

    • Hi Sky,

      When there is no voltage supply to PIN C4, the microcontroller will execute both:
      //this
      PORTD=0b00001111;
      _delay_ms(300);
      PORTD=0b00000000;
      _delay_ms(100);
      // and this
      PORTD=0b00001111;
      _delay_ms(300);
      PORTD=0b00000000;
      _delay_ms(100);

      May I know what are the two different codes that the MCU will execute? They both seem the same to me! Plus, how did you come to the conclusion that this is what the microcontroller executes?

      • Sorry, I forgot to edit. What I mean:
        //this
        PORTD=0b00001111;
        _delay_ms(300);
        PORTD=0b00000000;
        _delay_ms(100);
        // and this
        PORTD=0b11110000;
        _delay_ms(300);
        PORTD=0b00000000;
        _delay_ms(100);

        I connect LEDs to all PORTD of the MCU.
        Anyway, I have found the mistake. I realize it when I read Your Answer to Karthik. I didn’t use pull up resistor, and I have to connect input PINC4 to GROUND in order to get LOW (0).
        Thanks for this Great Blog. . . . :)

  10. there are many tamega32 available on the website . which one to buy. and which one owuld be suitable for making an autonomous or a grid follower bot..??

Trackbacks/Pingbacks

  1. Using AVR Studio 5 « maxEmbedded - [...] folks! Now that you are aware of the different port operations in AVR, we can move forward to actually…
  2. The ADC of the AVR « maxEmbedded - [...] it in AVR Studio 5. View this page to know how. To know about the I/O port operations in…
  3. AVR Timers – TIMER0 « maxEmbedded - [...] necessary. For those who are new to the term ‘register’, they can read about it from this page. To…
  4. AVR Timers – TIMER1 « maxEmbedded - [...] registers, we can proceed to write the code for it. To learn about I/O port operations in AVR, view…
  5. AVR Timers – TIMER2 « maxEmbedded - [...] learn about I/O port operations in AVR, view this. To know about bit manipulations, view this. To learn how…
  6. AVR Timers – CTC Mode « maxEmbedded - [...] registers, we can proceed to write the code for it. To learn about I/O port operations in AVR, view…
  7. AVR Timers – PWM Mode – Part II « maxEmbedded - [...] effects of WGM and COM won’t  come into play unless the DDRx register is set properly. Refer this tutorial…
  8. Using Atmel Studio 6 « maxEmbedded - [...] folks! Now that you are aware of the different port operations in AVR, we can move forward to actually…

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: