Friday 15 February 2013

ATMEGA16 Tutorial: Digital Write, Digital Read

Basics for programming an ATMEGA16

1. Digital Write.
When you want to program a uC, you always start from it's datasheet.
http://www.atmel.com/Images/doc2466.pdf

You can see in the datasheet that this uC has 4 8 bit PORTS: PORT A, PORT B, PORT C, PORT D.
Each port can be controlled using 3 registers. PORT, PIN and DDR
You can read in the datasheet at page 51 all the details in the chapter Configuring the Pin.
To summarise:
DDR means Data Direction Register, so using this register we can set if one pin of a port is being used for input or for output.
In the datasheet we can read the following If DDxn is written logic one, Pxn is configured as an output pin. If DDxn is written logic zero, Pxn is configured as an input pin.
Using the PORT register we write to that port, using the PIN register we read from the port.

Case 1:our port is set as output(all 8 pins are output)
            We write to one pin from that port the logic value one
            Result: On that pin we will have aprox 5V.
            We write to one pin from that port the logic value 0
            Result: On that pin we will have aprox 0V
            Everything seems logic....
Case 2:
          our port is set as input
          We write to one pin set as input the  logic value 1
          Result: an internal pull-up resistor is activate.
          We write to one pin set as input the logic value 0
          Result: the internall pull-up resistor is deactivated
What is a pull-up resistor: http://en.wikipedia.org/wiki/Pull-up_resistor
Basically, it is used to pull the pin to VCC when nothing is wired to it. Nothing does not mean 0 logic or 1 logic, so to avoid hazard(noise signal that influences the pin) we pull it to VCC and we know that when nothing is wired, we will have a logic 1.

Now, let's do a digita write on one pin of PORTA for example.

As we can see in the datasheet, PORTA consists of 0 bits (PA7, PA6, ...,PA0).
Let's say we want to write a logic value of 1 to PA6.
PA7, PA5....PA0 will remain unchanged(input or output)
We have to set PA6 as output and write to it.

First, set PA6 as output. We have to make the 7'th bit of DDRA 1, without modifying the rest of the bits.
We know that x | 0 equals x and x | 1 equals 1.
DDRA = DDRA | (1 << PA6 ); 
1 << PA6 means 1 shifted to the felft with PA6 positions. PA6 is defined in a library as 6.
1 << 0 means 00000001
1 << 1 means 00000010
and 1 << PA6 means 01000000 (what we want)
We make a logic or on bits between DDRA and  (1 << PA6). All the bits beside PA6 remain unchanged, and PA6 will become 1 .
Now, we have to write the logic value of 1 to PA6
Using the same technique:

PORTA = PORTA | (1 << PA6);
If we want to write a logic 0 to PORTA and leave the other bits beside PA6 unchanged, we have to use the function & (and)
x & 1 equals x
x & 0 equals 0.  So we have to make a logic and between PORTA and 0 placed at the PA6'th position. Besides that 0, the other bits have to be 1.
1 << PA6  is 01000000
Not changes a logic value:   NOT 1 equals 0
                                          NOT 0 equals 1.
~ is a NOT operator that makes NOT on every bit of the register.
~(1 << PA6) equals 10111111 (exactly what we want)

PORTA = PORTA & (~ (1 << PA6 ));

In C programming language if you have an expression like:

OPERATOR1 = OPERATOR1  operand OPERATOR2
you can write it as:
OPERATOR1 operand = OPERATOR2

For example
a = a + b; <=>  a + = b;
a = a | b <=> a | = b;
a = a & b <=> a & = b;
PORTA = PORTA | (1 << PA6) is the same as PORTA | = (1 << PA6) ;

Another important thing, when using Atmel libraries you have the macro BIT VALUE :
 _BV(PA6) is equal to (1 <<  PA6 ) , so instead of writing the whole (1 <<  PA6 ) you can write  _BV(PA6) and it's the same thing.

PORTA | = (1 << PA6 ); is the same as PORTA | = _BV(PA6);

So, a complete program to write to a pin of a port will be:


#include <avr/io.h>

#define F_CPU 16000000//the cpu is running at 16MHZ)
#include <util/delay.h>

int main() //this is the main function that is being executed when the program starts
 {
while(1)//the program runs continous
  {
    DDRA | = ( 1 << PA6 ); //set PA6 as output
    PORTA | = (1 << PA6); //set PA6 on logic value 1
    _delay_ms(1000);//we wait for 1 second
    PORTA & = ~ (1 << PA6 ); //set PA6 on logic value 0
    _delay_ms(1000);
   }
return 0;
}


This simple program will make PA6 go to 5V, stay 1 second , then go to 0V and stay 1 sec.

2. Digital READ
For digital read we will read pin PD6. If PD6 is HIGH (logical value 1), we will make PA6 go to 5V.
Else, PA6 will be LOW(0V aka logical value 0)
We have to set PD6 to be input, so we have to make the 7'th bit in DDRD 0.

(as we did in the previous case , we will use AND function)

DDRD & = ~ (1 << PD6 ) ; // The PD6th bit will be 0, so it will be an input.
Let's suppose that on PD6 we have connected a button that pul PD6 to GND. We have to activate, for a correct reading of the pin, the internal pull-up resistor.

PORTD | = (1 << PD6 );// we activate the internal pull-up resistor on PD6.

The value of the pin is determined in the following method:
we know that x & 1 equals x, and x & 0 equals 0. We want to tell if PD6 is 1, we don't care about the rest.

PIND & ( 1 << PD6) gives us exactly the value of PD6 - or it's 1("HIGH") , or 0 ( "LOW"). Put this into an if, and voila:




#include <avr/io.h>
#define F_CPU 16000000
#include <util/delay.h>

int main()
{
/* PORT D pins are set as output */
DDRD & = ~ (1 << PD6 ) ; // The PD6th bit will be 0, so it will be an input.
        PORTD | = (1 << PD6 );// we activate the internal pull-up resistor on PD6.
         DDRA | = ( 1 << PA6 ); //set PA6 as output
while(1)
        {
       if((PIND & (1 << PD6))) // if PD6 is "HIGH"
                    {
PORTA |=  ( 1 << PA6 ); // PA6 goes HIGH
    }
else
PORTA & = ~ (1 << PA6 ); //PA6 goes LOW
}

return 0;
}

If you have questions, please comment below

No comments:

Post a Comment