AVR Programming

From CCRMA Wiki
Revision as of 15:45, 21 June 2007 by Gurevich (Talk | contribs)

Jump to: navigation, search

Anatomy of a C program for AVR

The following presents a rough overview and breakdown of a demo program from the avrlib-demos. The code is in the avrlib-demos in the button directory in the file button.c. Details of C syntax and AVR-specific commands will follow.

  • The first portion of a C program is usually a bunch of comments that describe the file. Usually this includes the name, author, and date of the file; a revision history; and directions on how to use the file.
      //---------------------------------------------------
      //---------------------------------------------------
      //
      //  AVRLIB-DEMO
      //  For avrlib and avrmini development board.
      //
      //
      //  File:     button.c
      //  Author:   Wendy Ju
      //            based on code written by Michael Gurevich & Matt Wright
      //
      // Revision History:
      // When           Who               Description of change
      // -----------  -----------         -----------------------
      // 04-Oct-2004  Wendy Ju            Created a new instance
      // 01-Jun-2006  Michael Gurevich    makefile->mega32, superfluous #includes
      //
      //---------------------------------------------------
      //  USING THE AVRMINI DEVELOPMENT BOARD, CONNECT
      //  THE LED/PUSHBUTTON JUMPER TO THE PORT B JUMPER.
      //---------------------------------------------------
      //
      //---------------------------------------------------
      //  This program will cause LED 0 to light when corresponding
      //  pushbutton 4 is pressed.
      //---------------------------------------------------
  • Next are #includes. They tell the compiler where to look for other bits of code you are using that do not reside in this file. They are normally ".h" or header files that contain macros and function prototypes. Before a the .c file is compiled, the contents of the #includes are literally copied into the file.
      // compiler includes
      #include <avr/io.h>

      // avrlib includes
      #include "global.h"
  • #defines are "macros". Before the program is compiled, the argument of the #define is simply substituted everywhere the name is used. They are handy for giving meaningful names to numbers and for changing a single value that may be used numerous times in a program without having to change every instance.
      #define DELAY 1000
  • Function prototypes give the name, arguments and return type of functions that will be used later in the program. They can be included in header files or in the .c file before the function is defined.
      int checkButton(int whichButton);
      void setLED(int whichLED, int on);
  • The main function is literally the main part of the code. There can only be one main function in the final compiled program. The code inside the main function is executed sequentially, one line at a time.
      int main(void) {

        // set LED pins as outputs,  button pins as inputs
        outb(DDRB, 0x0F);

        // Turn off LEDs - looking at the circuit
        // you can see that they are off when pulled high
        outb(PORTB, 0xFF);

        while(1) {  // loop forever
          setLED(0, checkButton(4));
          //sampling delay goes here
        }

        return 0;
      }
  • Finally, there are function definitions. These are pieces of code that are called from the main function or from other functions, that have a specific functionality that may want to be used over and over.
      int checkButton(whichButton) {
        // On our boards the four buttons are numbered 4, 5, 6, and 7

        return (! bit_is_set(PINB,whichButton));
        /* Logical negation is because when the button is pushed, the pin
           is drawn to ground, so the button is "on" when the bit is zero. */
      }

      void setLED(int whichLED, int on) {
        // On our board the four buttons are numbered 0, 1, 2, and 3

        if (on) {
          //light the LED
          cbi(PORTB,whichLED);
        } else {
          //turn off the LED
          sbi(PORTB,whichLED);
        }
      }

C Syntax

This is a very basic overview of the essential parts of the C language that are frequently encountered when writing simple programs for the AVR. It is obviously impossible to thoroughly cover the C language in a few pages, but this should provide a brief explanation of most things that will be encountered in demo programs. If you know C, you can skip this section. For a better reference, see Kernighan, B.W. and D. M. Richie (1988) The C Programming Language. Prentice Hall.

Comments

  • Comments are not evaluated by the compiler. They are there to help the programmer. There are two styles of comments that can be used:
      // This is a C++-style 'slash-slash comment' 

      /* This is a C-style comment*/
      /* These comments must be terminated with a star-slash */

Variables

  • Declaring a variable means allocating a chunk of RAM on the AVR and giving it a name. After a variable is declared, the chunk of memory can be assigned a value, and that value can be recalled, using its name. A variable declaration looks like this:
      u08 myvar;   // unsigned 8-bit integer named myvar
     where u08 is the type and myvar<tt> is the name. Notice the statement ends with a semicolon, like all C statements. A variable must be declared before it is used, and before any executable statements in the function in which it is declared. Integer types are:
      u08 a;	// unsigned 8-bit integer (0 to 255) or 0 to MAX_U08
      s08 b;	// signed 8-bit integer (-128 to 127) or MIN_S08 to MAX_S08
      u16 c;	// unsigned 16-bit integer (0 to 65535) or 0 to MAX_U16
      s16 d;	// signed 16-bit integer (-32768 to 32767) or MIN_S16 to MAX_S16
      u32 e;	// unsigned 32-bit integer (0 to 4294967295) or 0 to MAX_U32
      s32 f;	// signed 32-bit integer (-2147483648 to 2147483647) or MIN_S32 to MAX_S32
     <tt>MAX_U08, etc. are macros that are #defined in the AVRLib.
  • An assignment gives a value to a variable, using the assignment operator = :
      u08 foo, bar;   // define 2 unsigned 8-bit integers
      foo = 12;       // assign the value 12 to the variable foo
      bar = foo * 10; // assign the 10 times the value of foo to the variable bar

Arrays

  • Arrays are adjacent chunks of memory of the same size that allow convenient indexing. They are defined as follows.
      u08 myarray[5];  // define an array of 5 unsigned 8-bit integers
      u08 myarray2[3] = {10, 12, 18};  // decfine an array of 3 unsigned 8-bit integers 
                                       // and initialize their values to 10, 12 and 18

      Arrays can be accessed for assignment or use in expressions with square brackets as well. Note that array indices begin at 0:

      myarray2[0] = 11;  // assign the value 11 to the first element of the array myarray2	
      myarray2[1] = myarray[0]; // assign the value of the first element of myarray 
                                // to the second element of myarray2

Conditionals

  • The basic conditional statement is the if ... else statement. It looks like this:
      if (a == 2) {          // if a is equal to 2
           foo++;            // increment the variable foo
           bar = foo + 10;   // assign the value of foo plus 10 to bar
      } 
      else {                 // otherwise (a is not equal to 2)
           foo--;            // decrement foo
           bar = foo - 10;   // assign the value of foo minus 10 to bar
      }

Note the increment and decrement operators, ++ and -. The keyword if is followed by an expression in parentheses that evaluates to a number. If the expression evaluates to a number other than zero, then the statements in braces immediately following are evaluated. If the expression evaluates to 0, anything following the else in braces will be evaluated. If there are no braces, then only the next line will be evaluated. An if does not need to be followed by else: you may only want something to be done in one case of the condition, and not in others.

The conditional expression normally has a relational operator. The relational operators take two arguments and evaluate to 1 if the relation is true and 0 if it is false. The operators are:

     ==     equality
     !=     inequality
     <      less than
     >      greater than
     <=     less than or equal to
     >=     greater than or equal to

Note the difference between the equality operator == and the assignment operator =. This is one of the most common sources of bugs in C programming.


Looping

There are 2 looping structures in C: while and for loops.

while Loops

  • A while loop is a block of code that is executed repeatedly until some condition is met. Its structure is:


     while(expression) {
          statements
     }


As with an if statement, the expression in parentheses is evaluated first. If it evaluates to non-zero, the statements in the body of the loop are executed. At the end of the loop body, the expression is evaluated again, and the body is executed until the expression evaluates to 0. The expression is normally relational, depending on some value that is being updated in the loop.

     u08 button;
     while (button != 1) {         // while the value of button is not equal to 1
          button = checkButton(3); // call the function checkButton with 
                                   // argument 3, and assign its value to 
                                   // the variable button
     }

This loop will execute until the function checkButton(3) returns a 1. This type of structure is common for polling an input.

  • A common exception to using relational operators is the expression
     while (1) {  // loop forever
          ...
          ...
     }

This never-ending loop is found in almost all of our AVR programs, because we have some functionality that we want to repeat continuously.

for Loops

  • A for loop is normally used when you want to perform an action a specific number of times. Its structure is:
     for (initialization; condition; update) {
           statements
     }

where initialization, condition, and update are expressions. Normally, the initialization and update are assignments or function calls, and the condition is a relational expression. A typical for loop might look like this:

     for (i=0; i<4; i++) {   // for i from 0 to 3
          checkButtons(i);   // call function checkButtons with argument i
     }

Here, the initialization sets the value of the variable i, the loop counter, to 0. This statement is executed only once, before the loop is entered. The condition is normally a relational expression. If it evaluates to non-zero, the body of the loop is evaluated. At the end of the loop body, the update expression is evaluated, before the condition is evaluated again, to determine whether the loop body should be executed again. Normally, the condition will be violated after the update has been evaluated a certain amount of times. The structure in the example above is most common.

  • Notice that
     for (initialization; condition; update) {
          statements
     }

is equivalent to

     initialization;
     while(condition) {
          statements
          update
     }

Functions

  • A function is defined as:
     returntype FunctionName(arg1type arg1name, arg2type arg2name ...)
     {
         declarations
         statements
     }

A function definition specifies what the function does, and can occur in a few different places. Functions can be defined after the main function, as long as a function prototype has first been declared. The prototype contains the name, return type and arguments, but not the body of the function. Here, the term declaration is used for function prototypes that declare that the function exists. The term definition is used where the body of the function is actually specified. Normally, a .c file has a companion .h file that contains all the function prototypes. With the AVRlib, we normally use a number of .c files (e.g. timer.c or a2d.c) that contain function definitions. The .c files are compiled together with the .c file you write, and are specified in the makefile (see below). But, in order to use functions from those .c files, you first need to declare their prototypes. This is done by including the corresponding .h file (e.g. timer.h or a2d.h). Look in a .h file in the AVRlib to see what it looks like. Functions can also be defined before the main function, but this is generally considered bad style.

  • After a function is declared, it can be "called" or used, as long as its definition exists. If the function does not return a value, its return type is declared as void. void is also used if there are no arguments. The example program in the first section contains such a function:


     void checkButton(void) {
        ...
     }

The code inside the function is executed when it is "called" from the another function:

     checkButton();

If the function returns a value, the keyword return must be present in the function, followed by the value to be returned. Once return is reached, the function exits and evaluates to the return value. If a function has arguments, they are normally passed as a value in parentheses after the function name in the function call. Inside the function, the arguments act like variables that have the value that has been passed. Take the following program for example:

     u16 timesten(u16 foo);  // function prototype
     int main(void) {
          u16 bar;           // declare an unsigned 16-bit variable 
          bar = timesten(9); // call the function timesten with the argument 9, store the result in bar
          ...
      }
          
      u16 timesten(u16 foo) { //define the function timesten, takes 1 argument
           return (foo * 10); //return the argument multiplied by 10
      }

Here the variable bar will end up with the value 90.

  • You'll notice that the main function normally has a return type int, and returns a value of 0. This is mainly used as a convention for our purposes. A return value of zero typically indicates that a program has exited normally, and other numbers are used to specify different errors. The main function does not require a prototype.