Monday, August 3, 2009

Hello World, Getting Started with the Atmel AVR AT90USB

Once you've built your AVR tool chain for Linux you're probably chomping at the bit to write your first program. A student's first assignment on any new computer system is usally to construct the proverbial "Hello, world." program. On a micro controller like the AT90USB162 the academic equivalent is manipulating the on board LED.

Our first step is to figure out how the LEDs on your board are wired to the micro controller. In my case I'm using the Olimex AVR-USB-STK dev board sold by Sparkfun Electronics. The data sheet for the board is available on the Sparkfun site and the applicable information is on page 7. You'll notice this board has one LED and it is connected to the pin labeled "PD4". Now we go an look at the micro controller data sheet from Atmel to see how to drive that pin in software. This board has the AT90USB162 variant of the chip so we need to look at the AT90USB162 version of the data sheet. The section we are most interested in is 11.2.1 discussing configuring the pin as a general purpose digital io port. In this case there are two applicable memory mapped io registers, DDRxn and PORTxn, that control the behavior of the pin (Note: a grouping of 8 io pins is referred to as a "port"). This being an 8-bit micro controller each register is 8 bits wide. The "xn" portion of each registers name refers to "port x, bit n". That is to say there are multiple 8 bit wide io registers and each bit of a given register controls a different physical pin. Further more the direction of the pin, input vs. output, is configured with the DDRxn register and the value can be read / written with the PORTxn register.

The next question is how do we access these PORTxn and DDRxn locations. Luckily AVR libc defines constant for each chip so we don't have to look up all the memory locations in the data sheet. If you look in the AVR libc header files (Note: on my system these files are located in /opt/avr/avr/include/avr) you will see a number of .h files corresponding to various AVR families and individual chips. For the AT90USB162 there is a .h file named iousb163.h but if we look in there we find that it also includes the file iousbxx2.h which contains the majority of the definitions. If we seach for "PORT" within iousbxx2.h we eventually find definitions for PORTA, PORTB, PORTC and PORTD. After each port definition are individual pin definitions. Looking at PORTD you may notice pin 4 is defined as "PD4" which matches the name of the LED's pin on the schematic for our board.

So now we know all the basic information we need to write our program lets put it all together.

#include <avr/io.h>

int main(int argc, char **argv) {

DDRD |= _BV(DDD4); /* set pin as output */
PORTD |= _BV(PD4); /* set pin to 1, high */

while(1);

return 0;
}

The _BV() macro is the equivalent of writing, (1<<PD4). Next we'll want to compile our program and prepare it for uploading to the MCU as follows.

avr-gcc -g -Os -mmcu=at90usb162 light.c -o light.elf
avr-objcopy -j .text -j .data -O ihex light.elf light.hex

If you are using a different MCU than the at90usb162 adjust the -mmcu option accordingly. Also of interest is the -Os option passed to gcc, this tells gcc to optimize of program size. The objcopy step takes the gcc output and packages it in ihex format which is suitable for uploading to the MCU via dfu-programmer. This also strips the debugging information, so, even though we passed the -g option to gcc the debug symbols are not making it into the end executable. This will, however, help you later if you want to attached to the chip with a debugger device.

Now to upload the ihex file to our device. You'll want to plug in your board via USB and put it into program mode by holding the HWB button and pushing RST to reset the board (this applies to most board, boards like the Teensy are different). Next run the following commands.

dfu-programmer at90usb162 flash light.hex
dfu-programmer at90usb162 reset

make sure to change the controller type accordingly if you aren't using the 162. I've seen some instructions that also include the command "dfu-programmer at90usb162 erase" which clears the flash before uploading the new code. In my experience this command always fails quietly (returns 1 instead of 0 w/o an error message) and isn't needed except in certain cases when the code protection bit is set.

Once you issue the above commands the LED on your board should turn on and stay on. Except it doesn't, you'll notice the LED is blinking which isn't what we intended. So what's going on here. It turns out the AT90USB MCUs have a watch dog timer that is enabled by default and automatically resets the chip after a set amount of time if it isn't either disabled or reset at regular intervals. If you look at the AVR libc documentation you'll notice a function, wdt_disable(). Which one assumes should disable the watchdog timer, it doesn't, at least not by itself. On the AT90USB you need to zero the WDRF bit in the MCUSR register before calling wdt_disable() to disable the watchdog timer. So lets try our little program again only this time adding code to disable the watch dog timer.

#include <avr/io.h>
#include <avr/wdt.h>

int main(int argc, char **argv) {

MCUSR &= ~_BV(WDRF);
wdt_disable();

DDRD |= _BV(DDD4);
PORTD |= _BV(PD4);

while(1);

return 0;
}

Compile, upload and reset. Your LED should now turn on and stay on. Congratulations you just wrote your first program for the AT90USB162. The complete code example from this post including a makefile is available in theRandomBit repository on GitHub.