Getting Started With AVR C Programming

Some of my friends have been interested lately in how to get started with embedded programming. Their first thought was the Raspberry Pi, but that’s entirely overpowered: it’s basically like programming a computer from a few years ago. My advice is to start with the Arduino, but program it in C / C++ (or Ada!); it’s doable cheaply, and it offers room to grow and plenty of peripherals that are easily attached.

This first tutorial doesn’t go into many of the features; it’s intended to give you a platform to get started in. I might add more articles later about other topics, like peripherals and interrupts and the like. It also assumes that you know some C and are familiar with bitwise logic; I’m writing this because a lot of the other introductions start off with a basic “here’s how to program” as well, and I always found those moved too slowly. This is the article series I wanted to exist when I was getting started.

The first posts in this series will cover more of the programming side; in later articles, I plan to introduce the electrical engineering background pieces as needed.

First steps: what you need

For this post, all you need is an Arduino (such as offered on Sparkfun or adafruit) and a USB A to B cable (the old printer cables). Sparkfun and adafruit carry starter kits for the Arduino that have plenty of components for getting started with; otherwise, we’ll start by using the onboard LED as an indicator. The Arduino toolkit is also useful for quickly validating ideas; I sometimes drop into processing just to make sure something works or test a part (like zeroing a servo), then build out my actual program in C or Ada.

We’ll keep the Arduino’s optiboot bootloader to avoid having to use a dedicated programmer; this reduces the available program size by 2K. For those who do end up using the dedicated programmer, I personally use the AVRISP mk2 (you can find them on DigiKey, among other places), and I’m pretty happy with it. There are plenty of other options for this, though.

I’m going to assume this is on a Linux machine, though most of it should be applicable to OS X too. You’ll need to get a copy of the AVR binutils and libc, as well as avrdude. If you want to use the serial port to communicate with Arduino programs, you’ll need a suitable program. I prefer picocom. You don’t need one if you don’t want the Arduino to send messages over the port or to receive data over the port. On Ubuntu, these packages can be installed with

sudo apt-get install avr-libc binutils-avr avrdude picocom

If you prefer a development environment, Rowley’s Crossworks is an option; I own their MSP430 and ARM versions and have a high opinion of them.

Some background

Programming the Arduino is going to be very different from the normal world of programming. With the Uno R3, your program can’t be larger than 16 KB in size, and only 14K KB with the bootloader. There’s also 2048 bytes of RAM. It gets even more fun with some of the other boards you can use: the MSP430s have 256 or 512 bytes of RAM, for example. The clock speed is much slower; the Arduino has a clock speed of 16 MHz, for example.

Debugging is also harder. Sometimes you can use the serial port to print a message, and other times you use an LED or something similar to see what’s going on. Later on, an oscilloscope will be useful for seeing the signals on the board.

One of the reasons I love embedded programming is that it’s often an entirely different way of thinking. Much like functional programming will make you approach problems from a different perspective than imperative programming, embedded systems add a whole different set of constrains that make you look at the world a little differently.

There are some references you will want to have on hand:

The following books are great references that I’ve found rather useful as well:

The first two books are particularly useful if you don’t have much or any electrical engineering background; they’ll give you a bit of a crash course in the subject.

A heavily annotated Hello, World

Designing and building an embedded program is a process:

  1. First, it’s important to know what you’re trying to build. What do the outputs look like? What are the inputs? What are the characteristics of the system?
  2. From this, you have to develop and build the circuit itself.
  3. The program is written.
  4. The program is uploaded with a tool like avrdude.

Our intro program will just blink the LED built into the Arduino. We don’t need to build anything because it’s already built in. For this, we’ll end up building a program that prints a start up message on the Arduino’s serial port, and then toggles the LED every second. We’ll start by getting the LED blinking, then add in serial output.

The program

Note: in the example code repo, this is example 01_getting_started, although that code includes everything in this article.

The LED is hooked up to digital pin 13 (D13). From the pin mapping, we can see that this corresponds to PB5.

On the AVR chip, pins come in banks of 8, and are addressed as bytes. Port B (PB) is the one that has our pin. PB5 is bit 5 in port B. So if the port has only PB5 set, it is 0b00010000. Ports and registers in the AVR architecture are memory locations that corresponds to some component on the chip; for example, memory location 0x25 corresponds to PORTB in the ATmega328P used in the Arduino Uno. The memory there is updated with the values of the pins. The corresponding data direction register (discussed below) is at 0x24. Reading and writing to these locations is how we’ll interface to the pins and parts of the microcontroller.

As an aside, if you’re not used to working with bits, you’ll have to start getting used to it. Here’s a quick run down of what you need to know:

The AVR libc defines friendly names for our ports when we include avr/io.h. It also includes names for the pins and some useful macros:

The pins on the AVR can be set to either input or output. The data direction register for each port controls whether the pins are input or output, or the direction that data will go. The default is to have all the pins cleared; a clear bit is an input bit. For our LED, we’ll want it to be an output pin. To set it as an output pin, we’ll set DDRB appropriately: DDRB |= (1 << 5).

The final piece that we need to know is that the header file util/delay.h provides us with the _delay_ms function that will delay for a number of milliseconds. It requires that the clock speed of the AVR microcontroller be defined in F_CPU beforehand; in our CFLAGS we’ll add -DF_CPU=16000000 (remember that the Arduino is clocked at 16 MHz).

Here’s the complete hello.c program:

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


#define LED_DDR		DDRB
#define LED_PORT	PORTB
#define LED_PIN		PB5


int
main(void)
{
    /* Set the LED pin as an output pin. */
    LED_DDR |= _BV(LED_PIN);

    while (1) {
        /* Toggle the LED. */
        LED_PORT ^= _BV(LED_PIN);

        /* Sleep for one second. */
        _delay_ms(1000);
    }

    /*
     * This should never be reached, but C requires that
     * main possibly returns *something*.
     */
    return 0;
}

Building the program

Now that the code’s written, it has to be compiled and linked for the AVR. Let’s write a Makefile to compile our program. We’ll extend it in the next section to support uploading code using avrdude.

The first section sets up the toolchain.

#############
# TOOLCHAIN #
#############

CC =		avr-gcc
LD =		avr-ld
STRIP =		avr-strip
OBJCOPY =	avr-objcopy
SIZE =		avr-size

The avr-size program displays how much space the program takes up. It’ll be useful for keeping an eye on how much flash memory your program is using.

Next, let’s set up our target and sources.

######################
# TARGET AND SOURCES #
######################

TARGET =	hello
SOURCES =

This section sets up our target (hello), and defines any additional source code (which we don’t have in this program).

Next is the build parameters used for compiling and linking.

####################
# BUILD PARAMETERS #
####################

MCU =		atmega328
F_CPU =		16000000
CFLAGS =	-Wall -Werror -Os -DF_CPU=$(F_CPU) -I. -mmcu=$(MCU)
BINFORMAT =	ihex

Here’s what the variables mean:

Now we can actually build our program:

.PHONY: all
all: $(TARGET).hex

$(TARGET).hex: $(TARGET).elf
    $(OBJCOPY) -O  $(BINFORMAT) -R .eeprom $(TARGET).elf $(TARGET).hex
    $(STRIP) $(TARGET).elf

$(TARGET).elf: $(TARGET).c $(SOURCES)
    $(CC) $(CFLAGS) -o $@ $(SOURCES) $(TARGET).c
    $(SIZE) -C --mcu $(MCU) $(TARGET).elf

.PHONY: clean
clean:
    rm -f *.hex *.elf *.eeprom

We build our ELF file first; this is the actual executable. We’ll strip it to save on space. It’s the same format used on your machine, with instructions for the AVR CPU instead of an Intel CPU. In order to convert this to an Intel hex file, we need to copy the object data into the hex file. We’ll remove any EEPROM data (which might be covered in a future post) and output the hex file.

After building, we’ll print out the size of the program in AVR format. This shows us how much space we’re using, and what percent of space we’re taking up.

Now let’s build this thing:

 make
avr-gcc -Wall -Werror -Os -DF_CPU=16000000 -I. -mmcu=atmega328 -o hello.elf  hello.c
avr-strip hello.elf
avr-objcopy -O  ihex -R .eeprom hello.elf hello.hex
avr-size -C --mcu=atmega328 hello.elf
AVR Memory Usage
----------------
Device: atmega328

Program:     162 bytes (0.5% Full)
(.text + .data + .bootloader)

Data:          0 bytes (0.0% Full)
(.data + .bss + .noinit)


Our program has 162 bytes of program, and nothing in the data sections.

Programming

Uploading the code to a microcontroller is usually referred to as “programming”, as in programming the device. We’ll program the Arduino using avrdude.

##########################
# PROGRAMMING PARAMETERS #
##########################

PROGRAMMER =	arduino
PART =		m328p
PORT =		$(shell ls /dev/ttyACM? | head -1)
AVRDUDE =	avrdude -v -p $(PART) -c $(PROGRAMMER) -p $(PORT)
AVRDUDE_FLASH =	-U flash:w:$(TARGET).hex

We’re going to keep using the Arduino bootloader to make it easier to program the device.

On Linux, the Arduino shows us as a /dev/ttyACM device, so the PORT variable picks the first available one. This may not work if you have multiple Arduinos plugged in, but that isn’t usually the case. The AVRDUDE_FLASH section tells avrdude to write (w) to the flash space, which is where the programs are stored.

The serial port

Using the serial port is a bit more complex. Serial communications are done using a UART: a universal asynchronous receiver/transmitter.

First, let’s add the util/setbaud.h header. Then, let’s write our initialisation function. We’ll deal a lot with registers, or memory locations in the AVR CPU that control its operation.

This code may look somewhat magic; all of the names below (except UBRRx_VALUE) can be found by reading the section on the UART in the datasheet. If you want to follow along, see chapter 19 (page 176) of the ATmega328 datasheet. I strongly encourage you to do this so you can see where these names and values come from.

static void
init_UART(void)
{
    /* 
     * The UBRR register is a UART baud rate register. We're using
     * UART 0. It's a 16-bit register, and we need to set the high
     * and low bytes to the values automatically provided for us by
     * the util/setbaud.h header.
     */
    UBRR0H = UBRRH_VALUE;
    UBRR0L = UBRRL_VALUE;

    /*
     * UART control status registers (or UCSR0 for UART 0) control
     * the UART's operation. There are three such registers, labeled
     * A, B, and C.
     */

    /*
     * In UCSR0A, we want to disable 2x transmission speed.
     */
    UCSR0A &= ~(1 << U2X0);

    /*
     * Enable the transmitter (transmit enable 0) and receiver
     * (receive enable 0).
     */
    UCSR0B = ((1 << TXEN0)|(1 << RXEN0));

    /*
     * Now, we set the framing to the most common format: 8 data bits,
     * one stop bit.
     */
    UCSR0C = ((1 << UCSZ01)|(1 << UCSZ00));
}

Now, we want to be able to write data to it. The write_string function takes a NULL-terminated string and writes it to the serial port.

static void
write_string(char *s)
{
    int	i = 0;

    /* Loop over the string until we reach the end. */
    while (s[i] != 0) {
        /*
         * The data ready bit in UCRS0A will be set when the UART
         * is ready to send data.
         */
        loop_until_bit_is_set(UCSR0A, UDRE0);

        /*
         * A byte is sent over the serial port by placing it in UDR0.
         */
        UDR0 = s[i];

        i++;
    }
}


static void
newline(void)
{
    loop_until_bit_is_set(UCSR0A, UDRE0);
    UDR0 = '\r';

    loop_until_bit_is_set(UCSR0A, UDRE0);
    UDR0 = '\n';
}

A new line in the serial console is created by writing both a carriage return and line feed character. We also use a new macro, loop_until_bit_is_set: this will block until the bit in the given register or port is set. There’s also an analogous loop_until_bit_is_clear macro.

In the main program, before the while loop, call init_UART. I like to print the string “Boot OK” after all the initialisation code to let me know that the initialisation completed. The main function now looks like

int
main(void)
{
    /* Set the LED pin as an output pin. */
    LED_DDR |= _BV(LED_PIN);

    /* Set up the serial port. */
    init_UART();

    /* The Arduino is now booted up and ready. */
    write_string("Boot OK.");
    newline();

    while (1) {
        /* Toggle the LED. */
        LED_PORT ^= _BV(LED_PIN);

        /* Sleep for one second. */
        _delay_ms(1000);
    }

    /*
     * This should never be reached, but C requires that
     * main possibly returns *something*.
     */
    return 0;
}

Before we can build this, we need to modify the Makefile. In the CFLAGS, add -DBAUD=$(BAUD), and add a BAUD variable above that. For example,

####################
# BUILD PARAMETERS #
####################

MCU =		atmega328
F_CPU =		16000000
BAUD =		9600
CFLAGS =	-Wall -Werror -Os -DF_CPU=$(F_CPU) -I. -mmcu=$(MCU) \
        -DBAUD=$(BAUD)
BINFORMAT =	ihex

Rebuild the program, and attach your terminal emulator (you may need to reset the Arduino to see the boot message).

$ make program && picocom /dev/ttyACM0 
avr-gcc -Wall -Werror -Os -DF_CPU=16000000 -I. -mmcu=atmega328 -DBAUD=9600 -o hello.elf  hello.c
avr-strip hello.elf
avr-objcopy -O  ihex -R .eeprom hello.elf hello.hex
avr-size -C --mcu=atmega328 hello.elf
AVR Memory Usage
----------------
Device: atmega328

Program:     278 bytes (0.8% Full)
(.text + .data + .bootloader)

Data:         10 bytes (0.5% Full)
(.data + .bss + .noinit)


avrdude -v -p m328p -c arduino -P /dev/ttyACM0 -U flash:w:hello.hex

avrdude: Version 6.1, compiled on Oct 24 2014 at 10:33:03
         Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/
         Copyright (c) 2007-2014 Joerg Wunsch

         System wide configuration file is "/etc/avrdude.conf"
         User configuration file is "/home/kyle/.avrduderc"
         User configuration file does not exist or is not a regular file, skipping

         Using Port                    : /dev/ttyACM0
         Using Programmer              : arduino
         AVR Part                      : ATmega328P
         Chip Erase delay              : 9000 us
         PAGEL                         : PD7
         BS2                           : PC2
         RESET disposition             : dedicated
         RETRY pulse                   : SCK
         serial program mode           : yes
         parallel program mode         : yes
         Timeout                       : 200
         StabDelay                     : 100
         CmdexeDelay                   : 25
         SyncLoops                     : 32
         ByteDelay                     : 0
         PollIndex                     : 3
         PollValue                     : 0x53
         Memory Detail                 :

                                  Block Poll               Page                       Polled
           Memory Type Mode Delay Size  Indx Paged  Size   Size #Pages MinW  MaxW   ReadBack
           ----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- ---------
           eeprom        65    20     4    0 no       1024    4      0  3600  3600 0xff 0xff
           flash         65     6   128    0 yes     32768  128    256  4500  4500 0xff 0xff
           lfuse          0     0     0    0 no          1    0      0  4500  4500 0x00 0x00
           hfuse          0     0     0    0 no          1    0      0  4500  4500 0x00 0x00
           efuse          0     0     0    0 no          1    0      0  4500  4500 0x00 0x00
           lock           0     0     0    0 no          1    0      0  4500  4500 0x00 0x00
           calibration    0     0     0    0 no          1    0      0     0     0 0x00 0x00
           signature      0     0     0    0 no          3    0      0     0     0 0x00 0x00

         Programmer Type : Arduino
         Description     : Arduino
         Hardware Version: 3
         Firmware Version: 4.4
         Vtarget         : 0.3 V
         Varef           : 0.3 V
         Oscillator      : 28.800 kHz
         SCK period      : 3.3 us

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.00s

avrdude: Device signature = 0x1e950f
avrdude: safemode: lfuse reads as 0
avrdude: safemode: hfuse reads as 0
avrdude: safemode: efuse reads as 0
avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed
         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "hello.hex"
avrdude: input file hello.hex auto detected as Intel Hex
avrdude: writing flash (278 bytes):

Writing | ################################################## | 100% 0.06s

avrdude: 278 bytes of flash written
avrdude: verifying flash memory against hello.hex:
avrdude: load data flash data from input file hello.hex:
avrdude: input file hello.hex auto detected as Intel Hex
avrdude: input file hello.hex contains 278 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.05s

avrdude: verifying ...
avrdude: 278 bytes of flash verified

avrdude: safemode: lfuse reads as 0
avrdude: safemode: hfuse reads as 0
avrdude: safemode: efuse reads as 0
avrdude: safemode: Fuses OK (E:00, H:00, L:00)

avrdude done.  Thank you.

picocom v1.7

port is        : /dev/ttyACM0
flowcontrol    : none
baudrate is    : 9600
parity is      : none
databits are   : 8
escape is      : C-a
local echo is  : no
noinit is      : no
noreset is     : no
nolock is      : no
send_cmd is    : sz -vv
receive_cmd is : rz -vv
imap is        : 
omap is        : 
emap is        : crcrlf,delbs,

Terminal ready
Boot OK.

Nifty, huh? Notice that we’re now using 10 bytes of data now; part of this is due to our string constant.

What about reading from the serial port?

/*
 * serial_read blocks until a character is available from the UART,
 * returning that character when it is received.
 */
char
serial_read(void)
{
    /*
     * Similar to the write_string function, UCSR0A's RXC0 bit will
     * be set when a byte is ready to be read from the serial port.
     */
    loop_until_bit_is_set(UCSR0A, RXC0);

    /*
     * The contents of the UDR0 register have the byte that was
     * read in.
     */
    return UDR0;
}

We could add this to the program, but where? The problem is that we have a blocking loop in the program, and we won’t be able to read a character. The next post on embedded programming will discuss interrupts and timers, which is a way we can get around this.

As an exercise, write a program that reads a line from the user (i.e., watch for \r\n) and write a string that tells the user how many characters were read. Two of the ways you can do this are to read the whole string and get its length after reading it in, or read the string without storing it.