Setting Up avr-gcc, avrdude and USBasp for Bare-Metal AVR Programming

To program an AVR in plain C you need three things: a compiler that produces AVR machine code, a tool that turns the compiler output into a HEX file, and a programmer that writes that file into the chip's flash. The free GNU toolchain covers the first two with avr-gcc and avr-objcopy, and avrdude drives the programmer. The USBasp is the cheapest and most common programmer in India, usually available for a few hundred rupees.

This tutorial sets up the toolchain, wires a USBasp to an ATmega328P, and walks through every command needed to build and flash an LED blink program. The same steps apply to the ATmega16 and ATmega32 with a different -mmcu and -p value.

Installing the Toolchain

Linux (Ubuntu or Debian)

All required packages are in the standard repositories. Install them with one command:

sudo apt install gcc-avr binutils-avr avr-libc avrdude make

Windows

On Windows you have two common choices. Microchip Studio (previously Atmel Studio 7) is a full IDE that already contains avr-gcc and avr-libc, with a project wizard and a simulator, but it does not support the USBasp directly. The second option is to download a prebuilt AVR GCC toolchain from Microchip and a Windows build of avrdude, then add both bin folders to your PATH. Many students use Microchip Studio for editing and building, and call avrdude as an External Tool to flash the HEX file.

The USBasp also needs a USB driver on Windows. The usual method is to run the free Zadig utility, select the USBasp device, and install the libusbK or libusb-win32 driver. After this, avrdude -c usbasp can talk to it.

Wiring the USBasp to an ATmega328P

AVR chips are programmed in-system over SPI using the MOSI, MISO, SCK and RESET pins. The USBasp has a 10-pin ISP header with the standard Atmel pinout. Connect it to a bare ATmega328P on a breadboard as follows:

USBasp 10-pin headerSignalATmega328P DIP pinATmega16/32 DIP pin
1MOSI17 (PB3)6 (PB5)
2VCC7 and 20 (VCC, AVCC)10 and 30 (VCC, AVCC)
4, 6, 8, 10GND8 and 2211 and 31
5RESET19
7SCK19 (PB5)8 (PB7)
9MISO18 (PB4)7 (PB6)

Add a 10 kΩ pull-up from RESET to VCC and a 100 nF decoupling capacitor across each VCC/GND pair, placed close to the chip. For the test program, connect an LED with a 330 Ω series resistor from PB5 (pin 19) to ground. Most USBasp boards have a jumper to select 5 V or 3.3 V target supply; use 5 V for these tutorials.

The First Program

Create a folder named blink and save the following as main.c. F_CPU tells the delay functions the clock speed; it must match the actual clock, which is 1 MHz on a new chip with factory fuses.

main.c: blink an LED on PB5 using util/delay.h
/* F_CPU must be defined before util/delay.h is included.
   A new ATmega328P runs at 1 MHz (8 MHz RC divided by 8). */
#ifndef F_CPU
#define F_CPU 1000000UL
#endif

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

int main(void)
{
    DDRB |= (1 << DDB5);            /* PB5 as output */

    while (1) {
        PORTB ^= (1 << PORTB5);     /* toggle the LED */
        _delay_ms(500);             /* wait half a second */
    }
}

Compile, Convert and Flash

Run these commands in the blink folder. The first compiles and links, the second extracts the flash contents into Intel HEX format, the third shows how much memory the program uses, and the last writes it to the chip.

avr-gcc -mmcu=atmega328p -DF_CPU=1000000UL -Os -Wall -o main.elf main.c
avr-objcopy -O ihex -R .eeprom main.elf main.hex
avr-size main.elf
avrdude -c usbasp -p m328p -U flash:w:main.hex:i

The -Os flag optimises for size and is also required for _delay_ms() to produce accurate delays, because the delay loops are calculated at compile time. In the avrdude command, -c selects the programmer, -p the part (m328p, m16 or m32), and -U flash:w:main.hex:i means write the file to flash in Intel HEX format. avrdude verifies the flash automatically after writing.

A Small Makefile

Typing four commands for every change gets tiring. A Makefile lets you type make to build and make flash to program. Remember that recipe lines in a Makefile must start with a tab character.

Makefile for an ATmega328P project with a USBasp
MCU      = atmega328p
PART     = m328p
F_CPU    = 1000000UL
TARGET   = main
CFLAGS   = -mmcu=$(MCU) -DF_CPU=$(F_CPU) -Os -Wall -std=gnu99

all: $(TARGET).hex

$(TARGET).elf: $(TARGET).c
	avr-gcc $(CFLAGS) -o $@ $<

$(TARGET).hex: $(TARGET).elf
	avr-objcopy -O ihex -R .eeprom $< $@
	avr-size $<

flash: $(TARGET).hex
	avrdude -c usbasp -p $(PART) -U flash:w:$<:i

clean:
	rm -f *.elf *.hex

Troubleshooting

  • could not find USB device with vid=0x16c0 pid=0x5dc: the USBasp driver is not installed or the cable is faulty. Re-run Zadig on Windows; on Linux, run avrdude with sudo or add a udev rule.
  • target doesn't answer or initialization failed, rc=-1: check the six ISP wires, the RESET pull-up and the power supply. Try the slow-SCK jumper or -B 32.
  • Device signature = 0x000000 or 0xffffff: the chip is not responding at all, often because of wrong wiring or a chip whose fuses select an external clock that is not connected.
  • Expected signature for ATmega328P is 1E 95 0F with a different value read: you may have an ATmega328 (without P), which has a different signature. Use the matching -p option.
  • The LED blinks eight times slower or faster than expected: F_CPU does not match the real clock. Check the CKDIV8 fuse, covered later in this series.

Summary

You now have a working command-line AVR toolchain. avr-gcc compiles C for the chip named by -mmcu, avr-objcopy produces the HEX file, avr-size reports memory use, and avrdude with a USBasp writes the program through the ISP header. Keep the Makefile as a template; every later tutorial in this series assumes you can build and flash a program with make flash.