CS4610: LAB 0

In this lab you’ll get hands-on experience with the AVR-based microcontroller board we’ll use for the duration of the course, the Pololu Orangutan SVP, which from now on we’ll abbreviate as the “org”.

Remember, the course references page has links to important reference documentation for this board and its software environment.

The Lab Tarball

We have prepared an archive file of sourcecode that you’ll use in this lab. Boot up your linux virtual machine if it is not running already. Click the link to download the archive file. The archive is in “tar gzipped” format (.tar.gz), which you can unpack by running the command

> tar xzvf lab0.tar.gz

At a terminal prompt. This should produce one new directory named lab0. If you then execute

> cd lab0/src/org
> ls

you should see that there are five subdirectories. libpololu-avr is the Pololu AVR Library, which provides helpful functions and well-documented interfaces to most of the important hardware on the org. The other five subdirectories are example programs we have prepared for this lab.

SVP Demo Program

The org ships with a nice little demonstration program. In this part you will use it to see some of the capabilities of the board.

Multiple demos are presented in a menu-based interface using the on-board LCD and the three “user pushbuttons” to the left of the LCD (orient the board so that the USB connector is facing away from you). Some of the demos will not do much without extra hardware connected (like motors), but many use only things available onboard.

  1. Use the supplied USB cable to connect the org to any free port on your workstation. For this lab we have configured all orgs to draw power from USB; later when you use them on your robot, the org will take its power from the robot’s battery. If you get a dialog box asking whether to connect the new USB peripheral to Windows or to your Linux guest OS running in VMWare, select Linux (you may also make this choice “permanent”).

  2. Experiment with the demo program. Some documentation about it is provided in the org user manual. The full sourcecode is also provided in libpololu-avr/examples/atmega1284p/svp-demo-program.

Four Simple Programs

Now you will compile four simple programs and run them on the AVR. Each is in a separate directory under lab0/src/org/:

hello — “hello world” using the org LCD

led — turn on the red “user LED” (Light Emitting Diode), using either the Pololu AVR Library or direct access to the AVR Special Function Register (SFR) for the pin connected to the LED

add — add two integers and display the result on the LCD using printf()

blink — blink the red user LED

  1. Build the Pololu AVR Library. The lab tarball contains only the sourcecode for the Pololu AVR Library; you will now need to compile it so that it can be used from your own programs.

    > cd lab0/src/org/libpololu-avr
    > make
    

    You should see a lot of scrolling output as the makefile builds the library code. The most important result of this process will be the file libpololu-avr/libpololu_atmega1284p.a, which is called a library file. It is an archive of the compiled code of all the Pololu AVR Library functions. To use the library in your own C code, the .h files in libpololu-avr/pololu with lowercase names are also important: they are header files that provide declarations for the library functions. We will explain more how these work below. (The .h files with uppercase letters in their names are for C++; the Pololu AVR Library is designed to work either with C or C++.)

    When you compile your own code, avr-gcc will need to be able to find the library and header files. There are a few different ways to do this. The makefile we have included with each of the four simple programs will automatically find these by assuming they are in the expected places in the libpolou-avr directory, which must be a sibling of the directory containing the example program. You may also copy the library and header files into a system location where avr-gcc “knows to look”. On Linux, this is under /usr/lib/avr. The README file supplied with the Pololu AVR Library directs you to run the command sudo make install which will copy the files into the appropriate system place for you. However, we recomend you not to do this. That will make it a little easier if we ever want to modify the Pololu AVR Library itself, for example, to fix any bugs we may find.

  2. Each of the simple programs is provided with a makefile that automates the process of both building (i.e. compiling) the code and programming the code to the org. We have coded this makefile in a general way so that exactly the same makefile can be used for each program; by default, it assumes the name of the program is the name of the containing directory.

    This may be a more complicated makefile than you may have encountered before. It uses make’s facilities to define and use variables and conditionals and to include other makefiles. The makefile is in fact split into two files: makefile is the top-level file, it contains documentation and default settings that could be modified on a per-project basis if desired. makefile_AVR.inc is included by makefile to provide basic functions related to building C code for AVR processors and for programming them to flash with a program called avrdude. Normally we should not need to modifiy makefile_AVR.inc; it is best not to, so that if we fix a bug or add a feature to it “upstream”, you can drop in the new version without clobbering any local modifications you might have made to it.

    Use an editor to examine hello/makefile and hello/makefile_AVR.inc but do not modify them. You may not understand all the syntax; later you can spend more time and look things up in the make manual.

  3. While the makefile may appear complex under the hood, it is very easy to use. Just run the command

    > make
    

    In any of the program directories — we recommend you start with hello and work through them in the order listed above — to compile the code. Several files will be generated, the most important of which will be hello.hex. This is a machine-readable file containing the data that should be programmed to the AVR flash. The other files include

    hello.lss — dissassembly listing, human readable text, for debugging. This shows the assembly code corresponding to the machine code that the compiler generated for you. In most cases you can ignore this (and it is not used for anything else), but it can be very informative to read so you understand how your C code was translated to machine instructions. In some cases, it can also be helpful to understand some types of bugs that can crop up due to misunderstandings between you and the compiler.

    hello.o — binary machine readable object code file. Normally the compiler will generate one .o file corresponding to each .c file in your project. The .o file contains “object code”, which is basically the machine code corresponding to each function in the corresponding .c file. While it is not usually helpful for you to read the contents of .o files directly, they are very important to the compiler. See the section below for more explanation.

    hello.elf — binary machine readable file of your executable program, including info to help an OS load and run it. If you were writing C code for your PC, this would be the most important final product, the program you could run at the command line to test your code. Since we are programming the AVR, the makefile generates the .hex file from this .bin file, which puts it into a format that can be programmed to the AVR’s flash.

    hello.bin — similar to hello.elf but does not contain the OS loader info.

    makefile.depend — a working file created by make that helps it figure out dependencies.

  4. To get rid of the non-essential build products, you can run the command

    > make clean
    

    This will delete all generated files except the .hex. If you want to get rid of that too, run

    > make realclean
    
  5. It is now finally time to program the org with your own code. Examine hello.c carefully in an editor. Look up things you don’t understand in the appropriate documentation. When you are satisfied you understand what it’s supposed to do, run this command (in the hello directory)

    > make program
    

    This will (re)generate the .hex file if needed, and then it will attempt to program it to the org, which should still be connected by USB to Ubuntu. You will see a sequence of messages in your terminal window that help you monitor the programming process. If all goes well you should eventually see

    avrdude done.  Thank you.
    

    The org will automatically reset itself after programming, and you should see the expected results of your code on the org.

  6. Examine each of the other provided programs, build them, program them to the org one at a time, and test them out. Later, we’ll ask you to make some modifications to these programs.

The Three Stages of Compilation

Part of the challenge of learning C, even if you already know another language like Java, is understanding the mechanics of how the compiler works. What is up with #include <foo.h>? Why do we need .o, .h, and .a files? What is the linker?

The answers to the “why” questions are partly due to the historical development of C programming, which was related to the development of Unix-like operating sytems stretching back 30 years or more.

Let’s just focus on what happens in practice using avr-gcc for programming your org (though just about all of this also applies to other C programming systems).

  1. The preprocessor. A C project may contain code in any number of .c files. The first thing that happens when you build the project is that avr-gcc runs them through a textual preprocessor called “the C preprocessor”, sometimes abbreviated as “cpp”. The preprocessor looks for preprocessor directives which are lines starting with #directive, where directive is commonly something like include, define, ifdef, etc. You can read the avr-gcc manual (actually the preprocessor used for avr-gcc follows the regular C standard, so it should not be AVR-specific) to understand all the possibilities. Some of the most important ones are

    #include <file.h>
    

    which finds file.h in one of the system directories (/usr/lib/avr/include for avr-gcc on Linux) and inserts a copy of its contents textually into the file being processed;

    #include "file.h"
    

    similar to the above syntax, but using double quotes instead of angle braces changes the search behavior so that “local” directories are searched first. These include the directory containing the file being processed as well as any directories explicitly listed with -I incdir options on the avr-gcc command line;

    #define NAME value
    

    defines the symbol NAME (by convention, preprocessor defines are all caps) to have the text replacement value — any later instance of NAME will be replaced by value;

    #define ADD_MACRO(arg1,arg2) (arg1+arg2)
    

    defines a preprocessor macro taking two arguments; any time ADD_MACRO(foo,bar) is encountered later, it will be replaced with (foo+bar).

    A very common use of the include directives is to load header files (.h) that contain function declarations. Recall that in C, a function may be declared with syntax like

    int myFunc(int arg);
    

    that tells the compiler that somewhere there is a definition of that function. Header files are used by convention to allow functions defined in different .c files to call each other. Importantly, this allows you group code related to different subsystems into different .c files.

    The final result after cpp finishes handling all its directives is called a translation unit. Usually, the translation units resulting from cpp’s work are not stored as files on disk, though they could be.

  2. The next step is that the compiler actually compiles each translation unit, separately. Usually there is one translation unit per .c file, but the translation unit will also include any code that was #included, or that otherwise resulted from preprocessor directives (like macros). The result will be one .o object file for each translation unit. Usually, these are written to disk, though that can be optional.

  3. The third and final step is called linking. The primary job of the linker is to resolve symbols in object code. This is where a call to myFunc() in one translation unit will be matched up to the definition of myFunc(), possibly in another translation unit. Importantly, some of those tranlsation units might not be in your own code; the linker will also look in .a library files, which actually are just archives of object code from .o files (like tar or zip, but actually even simpler because they are typically not compressed). The linker of course needs to know in which library files it should look. Most of the C standard library calls, like printf(), are automatically resolved by the liker with no further action requried. One exception is the math library, for calls like sin(). The way you specify a library in which to search is by including a command line argument on the avr-gcc command line that you use to link your code, which for simple projects can also be the same command that does the preprocessing and translation; the details of composing and running these commands are abstracted for you by rules in the provided makefile. The argument has the syntax -lname where name is the library name; for the math library this is simply m. The compiler will then search for a file named libname.a in a set of directories where it normally expects to find libraries. These include system locations, which for avr-gcc on Linux usually includes /usr/lib/avr/lib, as well as directories that are specifically listed on the command line by -L libdir options. There can be multiple -L and -l options; note that a common problem for beginners is that for the -l options, order matters: the linker tries to find remaining unresolved symbols in each library specified on the command line in order. So if you have a library libfoo.a that contains code that calls a standard math function like sin(), make sure that you have command line options -lfoo -lm in that order. Again, the makefiles we provide you will usually relieve you from having to worry about composing avr-gcc command lines.

Making Modifications

Now we’ll challenge you to make some modifications to the provided code.

  1. Change the led program to light the green user LED instead of the red one. You need only make it work when USE_POLOLU_LIB is defined. For an extra challenge, make it work when USE_POLOLU_LIB is not defined, by directly accessing the SFR that controls the green user LED (hint: refer to the org reference diagram and page 12 of the org user manual, which describes how the port pins control the user LEDs).

  2. Modify the blink program so that pressing the top user pushbutton makes the led blink faster, pressing the bottom makes it slower, and pressing the middle resets it to the default speed. More specifically, design the code so that the blink half-cycle period decreases by 10ms when you release the top button, and increases by 10ms when you release the bottom button. Make sure that you don’t allow the user to reduce the period further once it hits zero. You will most likely find the pushbutton functions provided by the Pololu AVR library very useful for this task.

Pololu Example programs For Further Study

The Pololu AVR Library comes with a variety of example programs under libpololu-avr/examples/atmega1284p. We encourage you to check them out and try them on your own time (we will need to collect the orgs at the end of the lab period today, but you’ll get them back next time). Pololu has included a Makefile with each example program that works similarly to our makefile (they use capital M) — you can run make, make program, and make clean like usual (they did not implement make realclean separately; their make clean is like our make realclean). One slight snag is that Pololu’s Makefile assumes you have installed the Pololu AVR Library into the system location (by running sudo make install in the libpololu-avr directory, as discussed above). We have modified their Makefiles so that this should not be required.