Arduino and AVR introduction

Micro controllers
Vector graphics

Arduino board



Arduino is an affordable and easy to work with development board based on Atmel Atmega microprocessors. The Arduino has an easy to use IDE with its own programming language. However, there are reasons why you may want to program your Arduino using avr-c instead. For example,
Please note that I am using an old Arduino USB v2.0 board (with an Atmega8 MCU), in the code examples and makefiles. Some details will be different on newer boards. The old Arduino USB v2.0 originally came with an ATMega8 MCU, but even old boards like that can be upgraded to ATMega328p instead.

MCU reference : Atmega8 data sheet
Hardware resources in avr have names that are different from what is printed on the Arduino board. For example: More details about how Ardunino names map to MCU resources can be found at the Arduino site.

This page is also assuming that you are using a Linux distro of some kind. My own experiments have been run on Fedora and/or Ubuntu.

Installing AVR

You need to install
  • uisp
  • avr-binutils
  • avr-gcc
  • avr-libc
Installation with yum
As root, issue
yum install uisp avr-binutils avr-gcc avr-libc
That was all that was needed on the Fedora system I tested it on, but I'm sure it will work similarly on other distros too. Avrdude, minicom and pyserial may also be useful. Install those with
yum install avrdude minicom pyserial
Installation with apt-get
On Ubuntu:
sudo apt-get install avr-libc avrdude binutils-avr gcc-avr uisp 
sudo apt-get install avrdude python-serial minicom

Programming using the bootloader

If you have left the bootloader in place it is possible upload your own compiled avr code straight to the arduino, without a programmer (this is acutally what the arduino IDE does behind the scenes). Using avrdude on an atmega8 arduino you can upload .hex files (in this case print.hex) by pressing the reset button on the arduino, and running :
avrdude -V -F -b 19200 -c stk500v1 -p ATMEGA8 -P /dev/ttyUSB0 -U flash:w:print.hex
Program upload using this option is typically available as
make arduinoload
in the Makefiles given here. On other arduinos you would need to change the MCU type (-p option) and possibly the transfer speed (-b option). Refer to hardware/arduino/boards.txt for suitable settings for other arduino types. On non-ancient arduinos it is also possible to reset the board using the serial connection. When the serial signal DTR goes to ground the board is reset. This makes it possible for the bootloader to be ready to accept a new upload, without having to actually press the reset button (this is done automatically when you use the IDE). If you search for it, you can find python snippets that toggles DTR for you in order to do a reset.

Programming using an external programmer

stk500 programmer and arduino
stk500 programmer

Programming using the bootloader is convenient, but there are reasons why you may want to get rid of it. For example, the bootloader takes up space (can easily become an issue on a small MCU like Atmega8), and the bootloader also means it take s longer before the MCU starts executing your code after a reset (the bootloader waits to see if there is a new incoming program upload before starting to execute the currently loaded code). Instead of using the bootloader you can use the 2 by 3 ICSP (In-Circuit Serial Programming) pins on the Arduino board, and for example a stk200 or stk500 programmer. Typically the programmer has a ribbon cable. The red edge of the ribbon cable should line up with pin 1 on the ICSP connector. The makefiles given later on this page typically offer stk200 (assuming a parallel port programmer) upload as
make load
, while stk500 (assuming a USB programmer) upload is typically offered as
make loadusb
. The stk500 programmer I have will turn up as /dev/ttyACMx, while Arduinos using FTDI chips for the USB connection will turn up as /dev/ttyUSBx. In this case there is no risk of confusing which device is the programmer, and which device is the arduino. The makefiles will attempt to do stk500 upload on port /dev/ttyACM0, which is fine provided that no other device is also identified as a ttyACM-port. However, some Arduinos (like the Arduino UNO) uses an atmel chip for its serial connection instead, and those Arduinos will also turn up as ttyACM-ports. In this case you will have to find out which device is the programmer and which device is the Arduino prior to doing the upload. It should be possible to
write udev rules that does this automatically for you. With udev rules you can also do things like for example telling two different Arduinos apart.

Using ICSP will remove the bootloader. If you later want to take advantage of bootloader uploads again the bootloader will have to be restored first.

Restoring the bootloader

If you have wiped out the bootloader there are instructions on
how to restore the Arduino bootloader at the Arduino site. There's a burn bootloader menu item under the tools menu that you can use. Unfortunately that method did not work on my machine. If you also have trouble reloading the Arduino bootloader try the following instead (assuming an atmega8 MCU and a stk200 parallel programmer):
uisp -dprog=stk200 -dlpt=/dev/parport0 --erase
uisp -dprog=stk200 -dlpt=/dev/parport0 --upload if=ATmegaBOOT.hex -v=3 --hash=32
In order for this to work you need uisp installed and uisp needs to be in your path. You also need the ATmegaBOOT.hex which can be found in the drivers folder of your Arduino installation. Adapting this process to newer Arduinos with better MCUs is just a matter of locating the right bootloader .hex file (it will be somewhere in your Arduino folder) for your MCU.

Blink example

This section contains the standard LED-blinking test program, but implemented in avr-c instead. Connect the LED between pin 13 and GND. In the terminology of the MCU, pin 13 is the 5th bit in port B (see the top comment in the code). Nothing terribly exciting goes on here - Setting up is just a matter of configuring PB5 as output (DDRB - Data Direction Register for port B), while the main loop toggles PB5 in PORTB.

Copy the code and the Makefile, and use
to compile the program. To upload, use one of
  • make arduinoload (upload using bootloader)
  • make load (upload using stk200 programmer)
  • make loadusb (upload using stk500 programer)
The same procedure (make to compile, make <loadvariant> to upload) will be used will be used in all of the examples here.

Code (blink.c):
#include <avr/io.h>
#include <util/delay.h>

  Atmega8 ports & Arduino pins:
  port B -> digital pins 8-13
  port C -> analog input pins 0-5
  port D -> digital pins 0-7

  Connect a LED between pin 13 and GND.

void delay()
  unsigned char counter = 0;
  while (counter != 50)
    /* wait (30000 x 4) cycles = wait 120000 cycles */

int main(void)
  /* Initialization, set PB5 (arduino digital pin 13) as output */
  DDRB |= (1<<PB5);

  while (1) 
    PORTB |= (1<<PB5);  //arduino digital pin 5 -> 5V
    PORTB &= ~(1<<PB5);  //arduino digital pin 5 -> GND   
  return 0;

Serial transmission example

The second code example trasmits 'Hello world!' over the usb serial connection (I suggest that you use minicom to view the serial output from this program. Set minicom to use 8N1 at 9600 baud and turn both hardware and software flow control off). In this example the setup is a bit more involved. With the
UART Control and Status Registers UCSRA, UCSRB, and UCSRC, you set how you want the serial port to work. These registers are also used to signal the status of the port (e.g. The UDRE flag in UCSRA signals if the port is ready to write). In this particular example the port is set up for asychronous 8N1 (8 databits, No parity, 1 stop bit) communication, but there are many other possible setups. Please refer to the MCU data sheet for a complete listing of the available UCSR flags, and what they do. The baud rate is set in the registers UBRRL and UBRRH, while the UDR register is the actual serial data read or written.

Code (print.c):
#include <avr/io.h>

  Atmega8 datasheet:

  Atmega8 ports & Arduino pins:
  port B -> digital pins 8-13
  port C -> analog input pins 0-5
  port D -> digital pins 0-7

  PD0 -> rx
  PD1 -> tx

  UBRRL  -  UART baud rate register, low byte
  UBRRH  -  UART baud rate register, high byte
  UCSRA  -  UART Control & Status Register A
  UCSRB  -  UART Control & Status Register B 
  UCSRC  -  UART Control & Status Register C 
  UDR    -  serial data  
#ifndef F_CPU
#define F_CPU 			16000000 // Mhz 
#define UART_BAUD_RATE		9600 // 9600 baud

void uart_init()
  uint16_t ubbr = F_CPU/((UART_BAUD_RATE)*16L) - 1;  

  // asynchronous 8N1
  // set baud rate
  UBRRL = ubbr;  // low byte
  UBRRH = (ubbr >> 8); // high byte
  // enable rx and tx
  UCSRB = (1<<RXEN) | (1<<TXEN);

void uart_write_char(char c)
  while (!(UCSRA & (1<<UDRE))); // wait until buffer is ready
  UDR = c;

void uart_write_string(char *c)
  while (*c != '\0')

void delay()
{  // this type of delay loop may be removed by the optimizer,
   // so don't use the -O flag when compiling. 
  uint16_t i, j;
  for (i = 0; i < 65500; i++) for (j = 0; j < 40; j++);

int main(void)
  while (1)  // loop forever
    uart_write_string("Hello world!\r\n");

Further examples

I have
upgraded my own ancient arduino to use an atmega328, and I have also bought an arduino uno (which also uses atmega328), so future examples will have been tested with an atmega328 based arduino instead. This should not make any difference. Atmega8, atmega168 and atmega328 work in exactly the same way, they just have different amount of flash, RAM, and EEPROM capacity.