avr-gcc/avr-libc Tutorial

avr-libc is the standard library for AVR micro-controllers. It already contains many functions and header files that are optimised for the target platform.

avr-gcc is an open source cross-compiler for Atmels AVR micro-controllers and part of the so called toolchain, that will produce a ready-to-use binary that can be flashed to a micro-controller. BTW: “cross” means, that the compiler itself will run on another architecture than the binary it is compiling.

avrdude is the tool to actually flash the compiled binary file to a micro-controller. In most cases you will use some kind of programmer, that is connected to your computer on one side and to the micro-controller on the other side.

make is a build automation tool which, well, automates the build process. make will run the avr-gcc compiler and other parts from the toolchain, to produce a binary. It can also be configured to use avrdude to flash the compiled binary to the micro-controller. Using make is not mandatory. But it is a very convenient tool, that will do the trivial and boring stuff for you. make needs a makefile to know what it’s supposed to do.

In almost all cases you can get the whole “stuff” easily by using your Linux distributions package manager, like apt-get for Debian and Ubuntu or pacman for Arch Linux. If you install packages like “avr-libc” and “avr-gcc” the rest of the needed packages (like make) will be installed by dependencies, too.
If you’re using macOS you may want use Homebrew to install the needed packages. For Windows, there is WinAVR.

To check if the needed programs are available on your system, try this:

$ avr-gcc --version
avr-gcc (GCC) 8.2.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ make --version
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

This program built for i386-apple-darwin11.3.0
$ avrdude
Usage: avrdude [options]

(...)

avrdude version 6.3, URL: <http://savannah.nongnu.org/projects/avrdude/>

The next thing you need, is a makefile. There are a number of makefile templates for the use with avr-gcc available. I even found a AVR Makefile Generator online.
The most important setting is the MCU type (i.e. “ATtiny13”) and the type of programmer you want to use (i.e. “USBasp”). Some settings can be provided in the source file or as parameter when running the compiler. I recommend deciding for one way and then stick with it. Important to remember: avrdude and avr-libc use different MCU names (i.e. “t13” and “attiny13a” for the ATtiny13a). Another setting, which can be set inside the source file or at compile time, is the MCU clock. This does not set the clock, it just tells the compiler at what speed the MCU is actually running. To change the clock rate, you need to set or unset the so called Fuses with avrdude – think of them like little hardware switches. There is also a Fuse Calculator online, to find the correct fuse settings for your particular micro-controller type. Most controllers default clock speed is below their maximum setting. Especially if the CKDIV8 fuse is set to zero (which means, it is set), the internal clock rate will be divided by 8.

The following makefile is the one I used for my projects. All the important settings are at the beginning of the file – in this case, the makefile is configured for a ATtiny13a and the source code file is called “main.c”:

MCU = attiny13a
MCU_CLOCK = 9600000
AVRDUDE_PROGRAMMER = usbtiny
AVRDUDE_PORT = usb
AVRDUDE_MCU = t13
FORMAT = ihex
TARGET = main
SRC = $(TARGET).c
ASRC = 
OPT = s

# Name of this Makefile (used for "make depend").
MAKEFILE = Makefile

# Debugging format.
# Native formats for AVR-GCC's -g are stabs [default], or dwarf-2.
# AVR (extended) COFF requires stabs, plus an avr-objcopy run.
DEBUG = stabs

CSTANDARD = -std=gnu99

# Place -D or -U options here
CDEFS =

# Place -I options here
CINCS =

CDEBUG = -g$(DEBUG)
CWARN = -Wall -Wstrict-prototypes
CTUNING = -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
#CEXTRA = -Wa,-adhlns=$(<:.c=.lst)
CFLAGS = $(CDEBUG) $(CDEFS) $(CINCS) -O$(OPT) $(CWARN) $(CSTANDARD) $(CEXTRA)

#ASFLAGS = -Wa,-adhlns=$(<:.S=.lst),-gstabs 

#Additional libraries.

# Minimalistic printf version
PRINTF_LIB_MIN = -Wl,-u,vfprintf -lprintf_min

# Floating point printf version (requires MATH_LIB = -lm below)
PRINTF_LIB_FLOAT = -Wl,-u,vfprintf -lprintf_flt

PRINTF_LIB = 

# Minimalistic scanf version
SCANF_LIB_MIN = -Wl,-u,vfscanf -lscanf_min

# Floating point + %[ scanf version (requires MATH_LIB = -lm below)
SCANF_LIB_FLOAT = -Wl,-u,vfscanf -lscanf_flt

SCANF_LIB = 

MATH_LIB = -lm

# External memory options
EXTMEMOPTS =

#LDMAP = $(LDFLAGS) -Wl,-Map=$(TARGET).map,--cref
LDFLAGS = $(EXTMEMOPTS) $(LDMAP) $(PRINTF_LIB) $(SCANF_LIB) $(MATH_LIB)

AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex
#AVRDUDE_WRITE_EEPROM = -U eeprom:w:$(TARGET).eep

AVRDUDE_BASIC = -p $(AVRDUDE_MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER)
AVRDUDE_FLAGS = $(AVRDUDE_BASIC) $(AVRDUDE_NO_VERIFY) $(AVRDUDE_VERBOSE) $(AVRDUDE_ERASE_COUNTER)

CC = avr-gcc
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
SIZE = avr-size
NM = avr-nm
AVRDUDE = avrdude
REMOVE = rm -f
MV = mv -f

# Define all object files.
OBJ = $(SRC:.c=.o) $(ASRC:.S=.o) 

# Define all listing files.
LST = $(ASRC:.S=.lst) $(SRC:.c=.lst)

# Combine all necessary flags and optional flags.
# Add target processor to flags.
ALL_CFLAGS = -mmcu=$(MCU) -DF_CPU=$(MCU_CLOCK) -I. $(CFLAGS)
ALL_ASFLAGS = -mmcu=$(MCU) -DF_CPU=$(MCU_CLOCK) -I. -x assembler-with-cpp $(ASFLAGS)

# Default target.
all: build

build: elf hex eep

elf: $(TARGET).elf
hex: $(TARGET).hex
eep: $(TARGET).eep
lss: $(TARGET).lss 
sym: $(TARGET).sym

# Program the device.  
program: $(TARGET).hex $(TARGET).eep
	$(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) $(AVRDUDE_WRITE_EEPROM)

# Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB.
COFFCONVERT=$(OBJCOPY) --debugging \
--change-section-address .data-0x800000 \
--change-section-address .bss-0x800000 \
--change-section-address .noinit-0x800000 \
--change-section-address .eeprom-0x810000 

coff: $(TARGET).elf
	$(COFFCONVERT) -O coff-avr $(TARGET).elf $(TARGET).cof


extcoff: $(TARGET).elf
	$(COFFCONVERT) -O coff-ext-avr $(TARGET).elf $(TARGET).cof


.SUFFIXES: .elf .hex .eep .lss .sym

.elf.hex:
	$(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@

.elf.eep:
	-$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \
	--change-section-lma .eeprom=0 -O $(FORMAT) $< $@

# Create extended listing file from ELF output file.
.elf.lss:
	$(OBJDUMP) -h -S $< > $@

# Create a symbol table from ELF output file.
.elf.sym:
	$(NM) -n $< > $@

# Link: create ELF output file from object files.
$(TARGET).elf: $(OBJ)
	$(CC) $(ALL_CFLAGS) $(OBJ) --output $@ $(LDFLAGS)


# Compile: create object files from C source files.
.c.o:
	$(CC) -c $(ALL_CFLAGS) $< -o $@ 


# Compile: create assembler files from C source files.
.c.s:
	$(CC) -S $(ALL_CFLAGS) $< -o $@


# Assemble: create object files from assembler source files.
.S.o:
	$(CC) -c $(ALL_ASFLAGS) $< -o $@

# Target: clean project.
clean:
	$(REMOVE) $(TARGET).hex $(TARGET).eep $(TARGET).cof $(TARGET).elf \
	$(TARGET).map $(TARGET).sym $(TARGET).lss \
	$(OBJ) $(LST) $(SRC:.c=.s) $(SRC:.c=.d)

depend:
	if grep '^# DO NOT DELETE' $(MAKEFILE) >/dev/null; \
	then \
		sed -e '/^# DO NOT DELETE/,$$d' $(MAKEFILE) > \
			$(MAKEFILE).$$$$ && \
		$(MV) $(MAKEFILE).$$$$ $(MAKEFILE); \
	fi
	echo '# DO NOT DELETE THIS LINE -- make depend depends on it.' \
		>> $(MAKEFILE); \
	$(CC) -M -mmcu=$(MCU) $(CDEFS) $(CINCS) $(SRC) $(ASRC) >> $(MAKEFILE)

.PHONY:	all build elf hex eep lss sym program coff extcoff clean depend

The next thing you need, is an actual source code file with the name “main.c”. This example file will simply toggle an LED connected to pin 7 of the controller. Before the loop, the corresponding port-pin will be set to output. In the loop, the state of the port-pin will be constantly toggled between 1 and 0 using the binary XOR operator. The delay of 1 second is there to actually see what’s going on.

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

int main(void) {

  DDRB |= (1 << DD2);

  while (1) {

    PORTB ^= (1 << PORTB2);
    _delay_ms(1000);

  }
}

The io.h header file is the file that contains all the hardware specific macros and constants and will automatically import the portpins.h. This makes it a lot easier to work with the different registers, since they already have a name that corresponds to the name in the datasheet. The datasheet is the most important document, when programming a micro-controller.

Calling make without any option, in the folder where makefile and main.c are stored, should get you output along the following lines:

$ make
avr-gcc -c -mmcu=attiny13a -DF_CPU=9600000 -I. -gstabs   -Os -Wall -Wstrict-prototypes -std=gnu99  main.c -o main.o
avr-gcc -mmcu=attiny13a -DF_CPU=9600000 -I. -gstabs   -Os -Wall -Wstrict-prototypes -std=gnu99  main.o   --output main.elf     -lm
avr-objcopy -O ihex -R .eeprom main.elf main.hex
avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" \
	--change-section-lma .eeprom=0 -O ihex main.elf main.eep
avr-objcopy: --change-section-lma .eeprom=0x0000000000000000 never used
$

As you can see, the MCU type and clock rate is set through parameters, when the compiler is called.

After running make, the same folder should contain a couple of new files. One of them has the .hex extension, which is the one you want to write to you micro-controller. To do this, you could manually call avrdude with the necessary options. Or you could just call make again with the “program” target (“make program”). Or you could delete all the newly generated stuff by calling “make clean”. “make program” on the other hand will use avrdude and the given settings to flash the main.hex file onto the micro-controller. If the source code wasn’t built and linked before, make will do that automatically, before programming.

Leave a Reply

Your email address will not be published. Required fields are marked *