Program Arduino with AVR-GCC
From JavierValcarce.Es
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.
Contents |
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 */ _delay_loop_2(30000); counter++; } /* 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 */ _delay_loop_2(30000); counter++; } } return 1; }
Compile and upload
After connecting Arduino board to USB port, Linux 2.6 should load automaticaly 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:
The manual of the c library is at:
In /usr/share/doc/avr-libc/avr-libc-user-manual/index.html
It is useful to examine the "hello world!" project explained in:
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:
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, see last section of this page.
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)
Upload without a Bootloader
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)
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)
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[1] 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...).
| File | Description |
|---|---|
| ATmegaBOOT_168_ng.hex | Bootloader for AVR ATmega168 @16MHz, protocol stk500v1, 19200-8N1 serial port. |
The following instructions burn the bootloader and burn it on 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 user-space processes direct access to parallel port under Windows 2000/XP.
- TODO: explain howto compile a bootloader image instead of provide an already compiled one
- connect the Parallel Programmer (
dapa) to parallel port (/dev/parport0) and the ICSP[1] port of Arduino, also to power (USB or external)$ # configure fuse bits, efuse=0x00, hfuse=0xdd, lfuse=0xff, see below$ # configure fuse bits, lock=0x3f (unlock boot section)$ avrdude -c dapa -p m168 -P /dev/parport0 115000 -U flash:w:ATmegaBOOT_168_ng.hex$ # configure 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
The last command is only to make sure that the cables are well connected. The device's signature of ATmega168 is 0x1E, 0x94, 0x06.