Saturday, 30 June 2012

AVR- Intro to I/O ports in C Language instead of Arduino/Processing Language

As sometimes we do need to use a faster code, i have been looking into using AVR coding in C , as opposed to the normal Arduino/Processing language that makes away with it all, allowing us to use a simplified dumbed down version of it.

For this basic introductory tutorial, I'll be assuming that you have a basic grasp/knowledge of Digital Logic systemsbinary numberslogic gates and Boolean algebra.  at least the basics of C Programming ( which can be replaced at least with some experience/knowledge of the  Arduino/Processing language, for purposes of this exercise, or any High Level programming language) and of course, some basic knowledge of Electronic circuits and the Ohm's Law.

So, as first thing, ( and i will come back to this in more detail at another time) i have to let you know the structure of a program, as it is different from the Arduino way.
Arduino, as you probably read by now, made it much easier for beginners and newcomers to code, by only needing us to use a void setup()  and a void loop().

But in AVR we will be using C, (  avr-gcc C compiler  , version 4.3.0  or later at least so we will have to do it differently , by the rules. I will just leave here a template of a basic program with a loop, so it doesnt end on us.
Personally i use CodeBlocks ( that even has a modified version for Arduino with a basic simulatoor and all: HOW COOL IS THAT  ?! lol )

#include <avr/io.h>  // In this line, called a header file is included avr/io.h , 
// an AVR device-specific I/O definitions library that register 
// names defined, used in the later stages. Check 
// for more info and reference */

int main(void)  // This is the main part of the program, where it actually starts. */
    // Note that we use a '1' as the argument to the while loop, because anything other than '0' is 
    //a logical true. Therefore the while loop condition will never be anything other than 
logically true, and the program 
    //will continue to execute indefinitely  */
    // Code would be in here if needed to execute over and over and over ... endlessly */

As starting poing, i'd recommend the ATMega328 Datasheet everytime you dont understand a term here used (hardware related), or have doubts regarding the hardware side of the architecture of the micrcontroller itself.

Also, we will use the pin mapping of the ATMega 328 as a guidelline for this exercise !

So, first a quick explanation of the concept of the PORTs in the ATMega. To note the fact that there are 3 types of functions we can perform with the uC's : Controlling, sensing and communicating.
For test purposes we will start with controlling a led, as it is the HELLO WORLD of electronics and also in Uc's world.

Each AVR implements 3 different set of GPIO registers (GPIO - General Purpose Input / Output). The registers used are:

  • DDRx- Data direction register for port x

x corresponds to A , B , C , D , etc. (Depending on the number of ports used by the AVR chip that we are using at the time). Bit (1) in the register  for output. bit (0) (cleared)  for input.

  • PINx - Address for input port x .

State of the port. Independent of the setting of Data Direction bit DDRx, the port pins can be read through the PINx Register bit.
In order to use the data read from port pin, first you have to change port’s data direction to input. This is done by setting bits in DDRx to zero. If port is made output, then reading PINx register will give you data that has been output on port pins.
Bit 1 if pin is "HIGH"; if bit 0 , port pin is "LOW".

  • PORTx - Data register for port x

This register is used for two purposes:  To output data ( when port is configured as output) and to activate/deactivate pull up resistors ( when port is configures as input).
For pins that were connected by DDRx on input, the internal pull-up resistors can be enabled or disabled via PORTx  (1 = enabled).

So for purpose of this exercise we will use the PORTDPD0 to PB7) which are the Digital pins 0, 1, 2, 3, 4, 5, 6 and 7 on the Arduino Uno/ATMega328.

OK, first maybe we should define the state of the direction of the data; We already know it can be INPUT or OUTPUT.So, when defined as an INPUT, he will be waiting  to sense a voltage ( or "listen" to it).
When the pins are in OUTPUT mode, use a logic of either 5 V or 0V; HIGH and LOW; 1 or 0 when in an OUTPUT mode !

And how can we control through the uC?!?

So, it is easy: All we have to do is simply tell Pin5 on PORTD to output 5 volts( Digital Pin 5 on Arduino UNO ; Maybe you can try this on PORTC IF YOU USING A MEGA... Digital pin 37 to 30). Note that this must be accomplished for each pin we wish to use in our circuit.

So, first, we will have to define the PINs we want to use as outputs in the following register:

DDRD = 0b00000001;

*There are several ways to set pin5 of in the PORT B to output.This is just one way, in bynary in this case. Mind the fact that This notation is only available in GNU C and not defined in ISO-C.  But we will talk about that at a later stage.

So, let me explain in detail what we doing here.  "DDRD" refers to the Data Direction Register for port D; "0b" is to tell the compiler that what follows is the binary expression of a number ( as we are using bynary in this instance; Mind that you can also do it in Hexadecimal, to which we will come to , on another time); and the "1" on the end denotes the pin 0 position (the first pin in port D).
Mind the fact that there are 8 pins for port B; pins 0 through 7. And maybe the brightest sparks noticed already that we are using also 8 digits in our code statement. So each digit represents a pin on the port, and we can use the individual digits to specifically refer to any one of the pins in port D.
So the '1' at the end of our code statement refers to the first pin in port B, which in this case is pin 0. (To note the fact that C and C++ are ZERO-BASED languages, so the first index of a data structure refers to, is the zero'th element; the second index refers to the first element, etc.). So, based on the explanation of the registers given above we can see that to apply a HIGH logic to a pin, we should use PORTD in this case, which is  the Data register for port D.
So the result is as follows:

PORTD = 0b00000001;

#include <avr/io.h>  

int main(void)         // For now we are just going to turn a led on */
  DDRB = 0b00000001;   // Data Direction Register setting pin0 to output and the remaining pins as input
  PORTB = 0b00000001;  // Set pin0 to 5 volts

    // * "empty" loop, we including so the code keeps being executed. Maybe you would 
    // like to try it without the while loop 
    // Code would be in here if needed to execute over and over and over ... endlessly */

  // * Never reached * / 
  return  0 ;                  


=>Regarding Hexadecimal, Binary and decimal<=

Just a quick cheat-sheet for those who might need it ( some is from the port 
manipulation post i done a while ago):

Binary| Hex
0000  |  0
0001  |  1
0010  |  2
0011  |  3
0100  |  4
0101  |  5
0110  |  6
0111  |  7
1000  |  8
1001  |  9
1010  |  A
1011  |  B
1100  |  C
1101  |  D
1110  |  E
1111  |  F

(precede with 0x)

                                                                                                                              ERRATA: The right-hand picture has an error in 1 decimal, as it is easy seen ( I DIDNT,lol ! Thanks, dropes !)

So in those terms, this code

void setup()
  DDRD = B11111111; // set PORTD (digital 7~0) to output

could be written as:
void setup()
  DDRD = 0xFF; // set PORTD (digital 7~0) to output

Same way that

void loop()
  PORTD = B00000000; //Digital 0~7  to LOW
Could be done as
void loop()
  PORTD = 0x00; //Digital 0~7  to LOW

To be continued...

Useful Reference links: - Has a nice animation as well, counting in bynary so you can get a better understanding - nice handy converter between hexa, Bynary and decimal

No comments:

Post a Comment

Feel free to contact me with any suggestions, doubts or requests.