Javier Valcarce's Homepage

Program Arduino with AVR-GCC

This article shows how to program an AVR (Arduino) directly in C with AVR-GCC toolchain outside the Arduino IDE. It covers also how to burn a bootloader and setup fuse bits.

Arduino is both an IDE and a hardware platform. The IDE is written in Java and uses a language called Processing that is a very thin software layer on top of C plus pre-packaged libraries for an easy access to UART port, SPI port, etc.

This is a good idea because it simplifies ATmega8/168 software development for newbies but, by other hand, this scheme has a few disadvantages compared with pure (and raw) C programs:

  • C lets a more accurate time and execution control. No hidden code, "What You Type Is What Is Executed" (tm)
  • C lets simple and direct access to hardware and interrupts
  • C lets you make ports to other MCUs apart than Arduino (ATmega168)

In this page I compile and upload a simple program written in pure C (with avr-libc) without Arduino IDE. Only a terminal, a text editor and the AVR-GCC toolchain (avr-as, avr-gcc, make, avrdude, etc) are needed.

Blinking LED Example

Let's start with a trivial example: a led blinking on Arduino pin 13 (actually, blinks all bits of PORTB). Create a folder for your project and copy the following example program in a file called blink.c

/* Hi Emacs, this is -*- c-mode -*- */
#include <avr/io.h>
#include <util/delay.h>
int main (void)
  unsigned char counter;
  /* set PORTB for output*/
  DDRB = 0xFF;
  while (1)
      /* set PORTB.2 high */
      PORTB = 0xFF;
      /* wait (10 * 120000) cycles = wait 1200000 cycles */
      counter = 0;
      while (counter != 50)
	  /* wait (30000 x 4) cycles = wait 120000 cycles */
      /* set PORTB.2 low */
      PORTB = 0x00;
      /* wait (10 * 120000) cycles = wait 1200000 cycles */
      counter = 0;
      while (counter != 50)
	  /* wait (30000 x 4) cycles = wait 120000 cycles */
  return 1;

Compile and upload

After connecting Arduino board to USB port, Linux 2.6 should automaticaly load FTDI driver (ftdi_sio.ko)

$ dmesg
usb 3-2: FTDI USB Serial Device converter now attached to ttyUSB0
usbcore: registered new interface driver ftdi_sio
drivers/usb/serial/ftdi_sio.c: v1.4.3:USB FTDI Serial Converters Driver

The toolchain (compiler/linker/assembler, standard C library and a programming utility) are contained in this three packages:

$ apt-get install gcc-avr avr-libc avrdude

The manual of the c library is at /usr/share/doc/avr-libc/avr-libc-user-manual/index.html

It is useful to examine the "Hello world!" project explained at file:///usr/share/doc/avr-libc/avr-libc-user-manual/group__demo__project.html. At the end of this page there is a Makefile you can customize to your own needs. Change program's name to blink and compile it:

$ make

this generates blink.hex, the firmware's image to upload. There is basically two methods for upload it to Arduino

  • using ICSP (In-Circuit Serial Programming)
  • using a bootloader stored in program (flash) memory, consumes about 2KB of program memory, see last section of this page for information about how to burn it if you've a "blank" device.

The second option is not strictly required. In fact, it has no decisive advantages respect to the first option except that you may only need one USB cable instead of two (USB for communication and Parallel for uploading).

Upload with a Bootloader

In case there is a bootloader already burned on AVR's program memory, flash blink.hex using the bootloader. Be sure that the fuse BOOTRST=0, if not, bootloader won't execute after reset (see below about fuse bits)

$ avrdude -p m168 -P /dev/ttyUSB0 -c stk500v1 -b 19200 -F -u -U flash:w:blink.hex

Upload without a Bootloader with Parallel Port programmer

If you don't want to use a bootloader, burn directly blink.hex with the parallel programmer. Be sure that the fuse BOOTRST=1, if not, the program won't execute after reset (see below about fuse bits)

$ avrdude -p m168 -P /dev/parport0 -c dapa -b 115000 -F -u -U flash:w:blink.hex

use -p m8 in case you are using atmega8 (not atmega168).

Upload without a Bootloader with AVR ISP mkII programmer from AVR Studio

To use this method, you'll need to purchase a mkII programmer (30€ aprox) and connect it to Arduino using its ICSP connector. In AVR Studio IDE, program Arduino is a completely graphical process. Click on Tool -> Program AVR -> Connect.... Select AVR ISP mkII programmer, USB link, and select as flash image the generated *.hex file. Click on Program button et voilà!

Note that...

  • Now, when you write software, pin numbers are different from those that we used to use in Arduino IDE
  • You access ports and other hardware using AVR-GCC notation, PORTB, PORTD, etc see the ATmega8/168 datasheet for details about the name of SFRs (Special Function Registers). A few atmega8's SFR names differ from those in atmega168/328p
  • If you use another part (atmega8, atmega168, atmega328, etc) modify the MCU variable in the Makefile.
  • Advice in case you encounter this problem: recently arduino ships with atmega328p with is identical to atmega168 but with more memory. The current avr-libc library (01-01-2009) doesn't support well atmega328. In particular, the utility for serial port setup doesn't work:
#define BAUD 19200
#include <util/setbaud.h>
#if USE_2X
        UCSR0A |=  (1 << U2X0);
        UCSR0A &= ~(1 << U2X0);

you should use instead:

#define BAUD_RATE 19200
UBRR0L = (uint8_t)(F_CPU/(BAUD_RATE*16L)-1);
UBRR0H = (F_CPU/(BAUD_RATE*16L)-1) >> 8;
UCSR0B = (1<<RXEN0) | (1<<TXEN0);
UCSR0C = (1<<UCSZ00) | (1<<UCSZ01);
/* Enable internal pull-up resistor on pin D0 (RX), in order
to supress line noise */
DDRD &= ~_BV(PIND0);

Burn a bootloader using ICSP

This section is only relevant if you have a "blank" device, with no bootloader burned in its flash memory. The part that comes with Arduino already has a bootloader so you can skip this section. An easy check to see whether bootloader is installed or not is that, if installed, a led at pin 13 must blink 3 times after reset.

What is a bootloader?
A bootloader is a small program which resides in a special AVR memory section (the bootloader section), its basic mission is to receive new firmware uploaded thought serial port<ref>Or USB port, or I2C port, or SPI port, etc. It depends on the bootloader type</ref> and store it in AVR's flash memory (program memory). Each bootloader is AVR device specific, talks a specific protocol and uses a concrete serial port baud rate. All these configuration parameters must match the ones used with the host programming utility: avrdude. This utility can talk many types of protocols and supports many types of links (serial, parallel, usb...).

AVR bootloaders
File Description
ATmegaBOOT_168_ng.hex Bootloader for AVR ATmega168 @16MHz, protocol stk500v1, 19200-8N1 serial port.

The following instructions burn the bootloader on the AVR device. I've written them with Linux in mind but they works also on Windows (checked!) replacing /dev/parport0 with LPT1 and installing giveio.sys to give to user-space processes direct access to parallel port under Windows 2000/XP.

  1. (to do: explain howto compile a bootloader image instead of provide an already compiled one)
  2. connect the Parallel Programmer (dapa) to parallel port (/dev/parport0) and the ICSP<ref>ICSP (In-Circuit Serial Programming) makes reference the mode in which microcontroller is programmed, it no means that the cable must be serial</ref> port of Arduino, also to power (USB or external)
    $ # write the following fuse bits: efuse=0x00, hfuse=0xdd, lfuse=0xff, see below
    $ # write the following fuse bits: lock=0x3f (unlock boot section)
    $ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U flash:w:ATmegaBOOT_168_ng.hex
    $ # write the following fuse bits: lock=0x0f (lock boot section)

Explanation of above commands: prior to burn the image, it changes the ATmega88/168 fuse bits to setup the following configuration: external crystal oscillator, disable clock dividers, maximum size for bootloader section, etc. And then, flashes ATmegaBOOT_168_ng.hex in AVR. Please, refer to the manual to get more information about this (very important) fuse bits.

To have access to parallel port, your user must be part of lp group, edit /etc/group and quit session to let changes take effect.

Read fuse bits

$ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U efuse:r:-:h # reads efuse
$ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U hfuse:r:-:h # reads hfuse
$ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U lfuse:r:-:h # reads lfuse
$ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U signature:r:-:h # reads device signature

The last command is only to make sure that the cables are well connected. The device's signature of ATmega168 is 0x1E, 0x94, 0x06.

Write fuse bits

$ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U efuse:w:0xFF:m # writes 0xFF to efuse
$ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U hfuse:w:0xFF:m # writes 0xFF to hfuse
$ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U lfuse:w:0xFF:m # writes 0xFF to lfuse

This page forms part of website https://javiervalcarce.eu