Monday, July 13, 2009

Atmel AVR Development in Linux

I recently acquired a AT90USB162 development board from SparkFun Electronics, a most excellent online electronics shop that caters primarily to hobbyists. For those not familiar, the AT90USB162 is one in a line of low cost 8-bit AVR micro controllers sold by Atmel that supports USB in addition to a host of other basic peripherals such as MMC/SD, audio, serial, and a joystick, to name a few. For those, like myself, that normally deal in the realm of more powerful embedded devices like the Gumstix or BeagleBoard it can take a little getting used to.

The main difference between development for the AT90USB and a more powerful chip is that the AT90USB doesn't run what most people would consider an operating system. This presents some challenges to those not familiar with this arena because things which you would normally rely on say Linux to do, for example, you now have to do yourself. For instance, the "schedular" on these chips is usually nothing more than an infinite loop with a small set of hard coded functions acting as the "tasks" that get called over and over again. Likewise, there are no drivers to abstract away hardware details. A far cry to be sure from the preemptive multi tasking operating systems to which most of us are a custom.

While we can't use Linux on these devices we can still enlist the help of GCC (targeted for AVR) along with a host of other open source software to speed up the whole process. Perhaps the most popular tool chain for working with AVR devices is a free development environment called "WinAVR". WinAVR is a GCC / Bin Utils / AVRlibC based distribution with an IDE thrown in that, as you might guess, runs on windows. Alternatively Atmel distributes their own environment, AVR Studio, which also runs on windows. While these seem to be the most popular judging from posts on internet message boards they aren't the only option. It is possible to do development directly under Linux without depending on Windows or any other non-free software. What follows are my notes on how I got my board up and running using only free software running on Linux.

The main thing I found is that while there are many good resources out there that provide hints no one site contained all the correct information in the same place and many omitted a few critical less than obvious details. Hence my motivation to document it here. Before you start you are going to want to gather recent versions of at least the following packages...
Since your system will already have native versions of GCC and Binutils installed we want to be careful not to clobber the native versions. We do this in two ways. First we keep out AVR tool chain separate from our systems by installing to a separate path and secondly all of the AVR versions of the tools will have the prefix "avr-" perpended to them.

To get started create a directory in your home directory where we will unpack all of the source code and do the compilation. I called my "avr-src". The first package we need to build is Binutils. Unpack, configure, build and install as follows.

tar -xzvf binutils-2.19.1.tar.gz -C ~/avr-scr/
cd ~/avr-src/binutils-1.19.1
./configure --target=avr --program-prefix=avr- --prefix=/opt/avr
make
sudo make install


Notice above when we invoke configure the "--program-prefix" and "--prefix" options. These are the parameters that keep our AVR tools from clobbering our system's tools. You can chose a different path than "/opt/avr" such as "/usr/local/avr" depending on your preference so long as you are consistent.

Next we'll install GCC. GCC doesn't support building within the source directory so we need to create a separate build directory to avoid errors.

tar -xjvf gcc-4.4.0.tar.bz2 -C ~/avr-src/
cd ~/avr-src/
mkdir avr-gcc-build
cd avr-gcc-build
../gcc-4.4.0/configure --target=avr --program-prefix=avr- --prefix=/opt/avr --enable-languages="c, c++" --disable-nls
make
sudo make install


A gotcha I encountered here is is not having the right libraries and headers installed. GCC requires the GNU MP and GNU MPFR libraries be installed on your host system. The easiest way to fix this is to use your Linux distribution's package system to find and install these packages. You will need both the libraries themselves as well as the "devel" versions of the package which install the header files. If your system doesn't provide packages or they are to far out of date you can google for the respective packages and build them from source. If you build from source be sure to run the ldconfig command as root so your system picks up the updated libraries before proceeding with building your AVR tools.

Two notes on the GCC configure options. --disable-nls disables features dealing with internationalization which you likely won't need on a micro controller target. --enable-languages tells configure what languages to build compilers for. In this case C and C++ will be supported on our AVR target.

Before going further we need to add out newly built tool chain to our path. If you use a Bash shell you can edit your .bashrc file and add the line "export PATH=$PATH:/opt/avr/bin" to it. Then source the .bashrc file or open a new shell so the path changes take effect.

Now we will build AVR Lib C. AVR Lib C is a stripped down standard C library targeted specifically for AVR's. It also includes a lot of AVR specific definitions for hardware registers and a handful of helpful functions to do things like manipulate watchdog timers and configure the interrupts. Build it as follows.

tar -xjvf avr-libc-1.6.7.tar.bz2 -C ~/avr-src/
cd ~/avr-src/avr-libc-1.6.7
./configure --build=`./config.guess` --host=avr --prefix=/opt/avr
make
su
make install
ldconfig
exit

Note those are back ticks surrounding "./config.guess" also if you use "sudo" or "su -" when installing you may experience errors to the effect of "can't find avr-ranlib". This has to do with the path being incorrect. Using plain su will preserve your path and avoids the error.

You now have all the tools you need to build software for the AVR target but you still need to be able to upload the software to the target device to run. Most AVR's in general require an In System Programmer (ISP) or similar special programming device. Luckily if you are using an AT90USBxxx based board it contains an on board boot loader that allows you to upload new images to it via USB using either Atmel's "FLIP" software or the open source DFU Programmer. (Note: dfu is the technical term for the standard the FLIP software implements.) So lets go ahead and install dfu-programmer.

tar -xzvf dfu-programmer-0.5.1.tar.gz -C ~/avr-src/
cd ~/avr-src/dfu-programmer-0.5.1
./configure --prefix=/opt/avr
make
sudo make install


Now we need to configure udev to allow access to your device when it is plugged in. This is controlled in /etc/udev/rules.d/ you need to create a new file in that directory called "99-dfu-programmer.rules" and add the following to it...

"""
SUBSYSTEM=="usb", ACTION=="add", SYSFS{idVendor}=="03eb", SYSFS{idProduct}=="2ffa", MODE="660", GROUP="uucp", SYMLINK+="at90usb-%k"
BUS=="usb", ACTION=="add", SYSFS{idVendor}=="03eb", SYSFS{idProduct}=="2ffa", MODE="660", GROUP="uucp"

SUBSYSTEM=="usb", ACTION=="add", SYSFS{idVendor}=="03eb", SYSFS{idProduct}=="2ffb", MODE="660", GROUP="uucp", SYMLINK+="at90usb-%k"
BUS=="usb", ACTION=="add", SYSFS{idVendor}=="03eb", SYSFS{idProduct}=="2ffb", MODE="660", GROUP="uucp"

SUBSYSTEM=="usb", ACTION=="add", SYSFS{idVendor}=="03eb", SYSFS{idProduct}=="2ff9", MODE="660", GROUP="uucp", SYMLINK+="at90usb-%k"
BUS=="usb", ACTION=="add", SYSFS{idVendor}=="03eb", SYSFS{idProduct}=="2ff9", MODE="660", GROUP="uucp"

SUBSYSTEM=="usb", ACTION=="add", SYSFS{idVendor}=="03eb", SYSFS{idProduct}=="2ff7", MODE="660", GROUP="uucp", SYMLINK+="at90usb-%k"
BUS=="usb", ACTION=="add", SYSFS{idVendor}=="03eb", SYSFS{idProduct}=="2ff7", MODE="660", GROUP="uucp"

SUBSYSTEM=="usb", ACTION=="add", SYSFS{idVendor}=="03eb", SYSFS{idProduct}=="2ff4", MODE="660", GROUP="uucp", SYMLINK+="at90usb-%k"
BUS=="usb", ACTION=="add", SYSFS{idVendor}=="03eb", SYSFS{idProduct}=="2ff4", MODE="660", GROUP="uucp"

SUBSYSTEM=="usb", ACTION=="add", SYSFS{idVendor}=="03eb", SYSFS{idProduct}=="2ff3", MODE="660", GROUP="uucp", SYMLINK+="at90usb-%k"
BUS=="usb", ACTION=="add", SYSFS{idVendor}=="03eb", SYSFS{idProduct}=="2ff3", MODE="660", GROUP="uucp"

"""

You may need to change the GROUP setting to be a group you are a part of instead of uucp. What this file does is tell udev how to present your device within sysfs and what permissions to assign to it. This file will cover a number of Atmel AT90USB boards based on their vendor and product id's. You can figure out the vendor and product id by using the lsusb command when your board is plugged in and in program mode. If your board isn't covered by the above just add another section with the correct vendor and product ids.

Once you've added the udev configuration file as root run the command "udevcontrol reload_rules" to pickup the changes.

To test that this worked plug your board in via USB and put it in programming mode. On my board that means holding down the "HWB" button and then pushing "RST" to reset the board. You will see a new device connected in the dmesg output and lsusb will also show the connected device. Now you should be ready to run dfu-programmer. We'll query the board for it's bootloader-version to test things out.

dfu-programmer at90usb162 get bootloader-version

Replace "at90usb162" with what ever chip your board has. If you get a error message like "dfu:programmer: no device present" then your udev configuration is likely incorrect or you aren't part of the right group to access the device.

At this point you have everything you need to start developing. Good reference resources can be found at Atmel's website in the form of data sheets, and the AVR Lib C site. There is also a Freely available USB stack called LUFA that has a lot of good demo code. Atmel provides some example code but it's a bit of a mess.