You've unpacked your Kakapo, and now you'd like to do something with it. The first step most people complete is 'hello world' for microcontrollers - blink an LED.

To get to that point, there's a bit of leg work first to set up the toolchain - these are software tools you will need to compile and upload code into the Kakapo. The toolchain is broadly grouped into three major parts:

  • The compiler: avr-gcc
  • The standard C library: avr-libc
  • The upload tool: avrdude

The first two are provided by Atmel in the form of their "AVR8 GNU Toolchain". You will need to download a copy for your platform from this following link:

Linux: http://www.atmel.com/tools/ATMELAVRTOOLCHAINFORLINUX.aspx
Windows: http://www.atmel.com/tools/ATMELAVRTOOLCHAINFORWINDOWS.aspx

Note: At this time, these instructions are only written for Linux.

The toolchain can be installed anywhere by simply unpacking it. Once unpacked, you should note down where it was placed, as you'll need to know this for later steps.

The next step is installing avrdude. This can be obtained from the official avrdude site.

Once this is installed, you are almost ready to flash a bit of code. Grab a copy of the Makefile and blink.c from the examples in libkakapo. You will need to edit the "TOOLBASE" variable to point to where you installed the Atmel toolchain above, and the AVRDUDE path to where your avrdude is installed.

Almost there! First, let's look at the code in blink.c and go over what it's doing.

#define F_CPU 2000000
This is a C pre-processor macro. It defines a value for "F_CPU" to be 2000000. Many routines in AVR code need to know what the operating frequency of the CPU is, and in this case we've declared it to be 2MHz. (How do we know what value it is? 2MHz is the default for a Kakapo board.)

#include <avr/io.h>
#include <util/delay.h>

These are also C pre-processor macros. In this case we are including two header files: avr/io.h and util/delay.h. As these have angle brackets, they are from the "system" headers. io.h provides us names for the various hardware perpherials features of the MCU, delay.h provides a easy to use delay function.

Now, we're into the meat:

int main(void) {
 PORTE.DIRSET = PIN3_bm;

main() is always the name of the entry function in C, and this is no different for an MCU. Even though we don't actually ever return from main() on an MCU, it's still declared int and should have a return code somewhere, even if it's never reached.

Our next line is our first trip into accessing hardware features. In this case, we're going to be doing some digital IO, raising and lowering a pin attached to a built-in LED. If you look at the markings on the board near the built-in LEDs, you'll see they are named "PE2" and "PE3". This is because they are pin number 2 of Port E, and pin number 3 of Port E.

Every digital pin has a direction, either an input or an output. On our MCU, inputs are the default. When we want to set the pin as an output, we write a 1 to the direction register.

Direction Register
7 6 5 4 3 2 1 0

Each pin has one bit of the direction register. Pin 0 is the 0th bit, pin 5 is the 5th bit. To help remember these mappings, there are friendly names given to pins. "PIN3_bm" is refers to pin 3.

The direction register for Port E is called PORTE.DIR. But if we were to write the following:

PORTE.DIR = PIN3_bm;

then we would be writing 0's to all of the pins except pin 3. Typically we do not want to disturb the other pins, so we need to change just one bit of the register. To do this, we have a couple of options:

PORTE.DIR = PORTE.DIR | PIN3_bm; /* bitwise OR the bit we want */
PORTE.DIRSET = PIN3_bm; /* bitfield access */

In the first case, we're updating DIR to have the same as the existing value, with a bitwise OR of the pin we want. This will force only that bit to a 1, and leave all the other bits unchanged.

Alternatively, some registers on our MCU have "bitfield access". This takes the form of a register that is named with an action as well, like SET or CLR. It interprets any 1 bit set in the value written to it as an instruction to set the corresponding bit in the real register.

Both of these approaches are equally valid, and both are used depending on whether the hardware has a bitfield access available for the specific register. (Aside: Actually, there is a performance difference as well. Bitfield access is a single register write, whereas the bitwise-OR approach involves a read-modify-write taking more execute time.)

Now we've told the pin to be an output, we go into a loop:

 while (1) {
  PORTE.OUTSET = PIN3_bm;

There are several ways to make an infinite loop. Common approaches in C are while (1), as it will always evaluate to true; and for (;;), which is a for loop with no conditions on which it will ever exit. There are no right answers for which one you should choose.

The next line is another access to PORTE, this time we're using OUTSET. Every digital port has a OUT register that tells the hardware if it should be high or low - when configured as an output. If it's configured as an input, OUT does nothing. Like DIR, we have bitfield access to this register.

This line therefore sets pin 3 of Port E to be high. The LED wired to this pin will now light up.

Here's the other half of the loop:

  _delay_ms(500);
  PORTE.OUTCLR = PIN3_bm;
  _delay_ms(500);
 }

_delay_ms() is a function from delay.h. It delays in milliseconds (1/1000th of a second). 500 milliseconds is half a second, so it will wait for that long before moving on.

The next line again changes the OUT register of PORTE, this time we're clearing it. Again, we're using bitfield access here. In this case, the equivilent of doing do without bitfield access would be:

PORTE.OUT = PORTE.OUT & ~(PIN3_bm);

This sets the value of OUT to the current value of OUT, but with a bitwise and of every bit set to 1 except pin 2. This has the effect of forcing only that bit to be 0. With the bit set to 0, OUT is being told to make the pin low, so the LED turns off.

Lastly, we have another delay, and the loop is closed, looping around to the top.

In the place where you unpacked the code, do the following:

# make; make program

This will upload the code. The yellow LED marked PE3 should now be flashing on and off about every second (half a second on, half a second off).

Congratulations, you have completed Hello World for Microcontrollers.