Skip to content

Instantly share code, notes, and snippets.



Last active Jun 5, 2019
What would you like to do?
AVR basics

AVR 8-bit microcontroller

This is Chinese a clone of an Arduino Pro Mini dev board. It uses the 8-bit Atmel AVR ATmega328p microcontroller. Like the STM32 this is a harvard architecture, but it's a much simpler, older and slower microcontroller. It has a 16MHz clock (in this case), 32KB of program memory, 2KB of SRAM and 1KB of EEPROM for persistent storage.

The datasheet is much nicer than the STMicro ones (in my opinion anyway). All of the registers are described fully along with the electrical details in this single datasheet, and it's very well written. Anything you can do with this micro is described in there.

Atmel was founded by some guys who wanted to make microcontrollers easier to use (iirc), and it shows.

This particular board

This is an Arduino IDE compatible board loaded with the Arudino bootloader that allows programming over serial (USART). Normally this chip needs an AVR ISP external programmer like the STM32's STLink, but this bootloader allows programming without one. This bootloader adds a 1-2 second pause after reset, then your program runs normally.

Since this is an Arduino board, you can generally Google "Arduino [do thing]" and you'll usually find example code, videos, demos etc. It's a very popular platform for people new to hardware, but the editor is pretty limited; it is great to grab examples, test them and fiddle with parameters though.

PlatformIO is an alternative ecosystem which looks like it has nicer tools, or you can just write in plain C or C++ using the AVR headers.

Getting started under Linux

Unlike the STM32 board, the AVR headers can be installed from the package manager along with the toolchain. This should be available in most distros:

# Debian
sudo apt-get install avrdude gcc-avr avr-libc


This is a much simpler device (plus the AVRC library is very good), so blinky is simple:

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

int main(void)
    // PB5 as output
    DDRB |= (1<<5);

    for (;;) {
        // Toggle the LED pin
        // _BV here is a macro from the AVR library equivalent to (1<<PB5)
        // PB5 is just naming helper defined as 5
        PORTB ^= _BV(PB5);


Manually building and flashing looks like this:

# Compile
avr-gcc -g -Wall -DF_CPU=16000000 -mmcu=atmega328p -Os -o main.o -c main.c
avr-gcc -g -mmcu=atmega328p -o main.elf main.o
rm -f main.hex
avr-objcopy -j .text -j .data -O ihex main.elf main.hex

# Show used program and RAM (data)
avr-size --format=avr --mcu=atmega328p main.elf

# Disassemble
avr-objdump -d main.elf | less

# Program
avrdude -c arduino -P /dev/ttyUSB0 -b57600 -p atmega328p -U flash:w:main.hex:i

The normal Makefile I use for AVR projects is below. I've set this up to work with the Arudino-style program-over-serial bootloader:

make flash

Printing over serial

Here's an example of printing text over the serial port (without printf):

#include <avr/io.h>
#include <avr/interrupt.h>

void init_usart(void)
    // Disable interrupts

    // Set baud rate to 9600 @ 16MHz clock
    // Based on "Examples of Baud Rate Setting" (page 243 in the datasheet)
    UBRR0H = (uint8_t)(103 >> 8);
    UBRR0L = (uint8_t)(103);

    // Enable receiver and transmitter
    UCSR0B = _BV(RXEN0) | _BV(TXEN0);

    // Set frame format: 8 data, 1 stop bit
    UCSR0C = (3<<UCSZ00);
    UCSR0C &= ~(1<<USBS0);

    // Enable interrupts

void usart_transmit(uint8_t data)
    // Wait for empty transmit buffer
    while ( !( UCSR0A & (1<<UDRE0)) );

    // Put data into buffer, sends the data
    UDR0 = data;

uint8_t usart_receive(void)
    // Wait for data to be received
    while ( !(UCSR0A & (1<<RXC0)) );

    // Get and return received data from buffer
    return UDR0;

void print(char* string)
    uint8_t i = 0;
    while (string[i] != '\0') {

void println(char* string)

int main(void)
    // Select minimal prescaler (max system speed)
    CLKPR = 0x80;
    CLKPR = 0x00;


    println("Hello world");

    return 0;

Under Linux you can use screen or socat (among others) to view the serial port:

socat /dev/ttyUSB0,raw,b9600 stdout
DEVICE = atmega328p
CLOCK = 16000000
PROGRAMMER = -c arduino -P /dev/ttyUSB0 -b57600
SOURCES = $(shell find . -name '*.c')
# Automatic dependency resolution
DEPDIR := .d
$(shell mkdir -p $(DEPDIR) >/dev/null)
# Compiler flags
CFLAGS = -Wall -Os -DF_CPU=$(CLOCK) -mmcu=$(DEVICE)
CFLAGS += -I -I. -I$(shell pwd)
CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
CFLAGS += -ffunction-sections -fdata-sections -Wl,--gc-sections
CFLAGS += -Wl,--relax -mcall-prologues
CFLAGS += -std=gnu11 -Wstrict-prototypes
# Specfic warnings as errors
CFLAGS += -Werror=return-type
# Enable coloured output from avr-gcc
CFLAGS += -fdiagnostics-color=always
POSTCOMPILE = @mv -f $(DEPDIR)/$*.Td $(DEPDIR)/$*.d && touch $@
# symbolic targets:
all: $(SOURCES) main.hex
.c.o: $(DEPDIR)/%.d
@mkdir -p "$(DEPDIR)/$(shell dirname $@)"
$(COMPILE) -c $< -o $@
$(COMPILE) -x assembler-with-cpp -c $< -o $@
# "-x assembler-with-cpp" should not be necessary since this is the default
# file type for the .S (with capital S) extension. However, upper case
# characters are not always preserved on Windows. To ensure WinAVR
# compatibility define the file type manually.
$(COMPILE) -S $< -o $@
flash: all
$(AVRDUDE) -U flash:w:main.hex:i
# Xcode uses the Makefile targets "", "clean" and "install"
install: flash
# Remove intermediates
find . -name '*.d' -or -name '*.o' -exec rm {} +
# Remove binaries
rm -f main.hex main.elf
# Remove dependency tracking files
rm -rf "$(DEPDIR)"
main.elf: $(OBJECTS)
$(COMPILE) -o main.elf $(OBJECTS)
main.hex: main.elf
rm -f main.hex
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avr-size --format=avr --mcu=$(DEVICE) main.elf
disasm: main.elf
avr-objdump -d main.elf
$(DEPDIR)/%.d: ;
include $(wildcard $(patsubst %,$(DEPDIR)/%.d,$(basename $(SOURCES))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.