Toolchain setup for AVR microcontroller

Published at Apr 9, 2024

#avr
#ide

I normally use Neovim as IDE, and I want to set the toolchain needed for embedded software development. This time, I will do it for an AVR chip.

Let’s look for toolchain or compiler in manufacturer’s page here I will be using an ATMEGA2560 ( an 8-bit mcu ) then we need to download the AVR 8-bit toolchain. Exploring the folder, I found a pdf manual of AVR Libc. You can also go straight to the online manual here

Toolchain overview

There is not a single tool that provides everything needed to develop software for the AVR. It takes many tools working together to produce the final executable application for the AVR microcontroller.

  1. Library: AVR-Libc provides many of the same functions found in a regular Standard C Library and many additional library functions that is specific to an AVR. Some of the Standard C Library functions that are commonly used on a PC environment have limitations or additional issues that a user needs to be aware of when used on an embedded system. More details of the modules in the library here.

2*. Compiler: GCC (GNU compiler collection) is a highly flexible compiler system. GCC built specifically for AVR target is called “AVR GCC”. It translates high-level language (C,C++,Ada) to the target assembly only. The compiler itself does not assemble or link the final code.

3*. Assembler: GNU binutils contains the GNU assembler (gas) and the GNU linker (ld), but also contains many other utilities. When these tools are built for AVR target, the program names are avr-as (assembler) and avr-ld (linker).

3.5**. Formatter: the assembler may spit something that is not ready to be uploaded due to format reasons. avr-objcopy is the tool to format.

  1. Programmer: AVRDUDE can upload your assembled file to the hardware.

  2. Debugger: GBD (GNU debugger) allows you to see what is going on ‘inside’ another program while it executes. Again GBD configured for AVR are prefixed with the target name : avr-gbd.

*After testing, I use compiler but internally it also assembles the result, so in practical terms compiler and assembler is done in command. There is a flag -S to stop the assembler step. **I would like to know more about requirements of file to be uploaded to chip. Does it have some extra sauce.

Install on Ubuntu linux

sudo apt-get update
sudo apt-get install binutils-avr gcc-avr avr-libc gdb-avr avrdude

Note: even though we install ‘gcc-avr’, the command is avr-gcc f.e. ‘avr-gcc —help’

Let’s C! a blinker

The library (AVR-Libc) web also has some interesting project examples. The simple project which is not so ‘simple’ for me, it is a blink led with PWM and interrupts.

This is also the first time I will dive into C code, so I will take it slowly and find an example somewhere else. I stumbled upon a Youtube video, highly recommended from Mitch Davis.

Registers

I want to blink the built in led, let’s see which IO port is connected to it:

micro atmel mega Arduino mega full [pinout diagram](https://docs.arduino.cc/resources/pinouts/A000067-full-pinout.pdf)

We can find more info by going to the datasheet [1] and looking for “Register description for I/O ports”. The led builtin is connected to PB7 (from pict above), this means the data register of Port B and bit 7.

What does that mean?

If bit 7 of that register is:

  • 1 -> led will turn online
  • 0 -> led will turn off

Where is that data register?

The register is a memory addresss, some registers have specific hardware functions (like the builtin led). And may be read-only or write-only. A led requires to be write-only. The datasheet (page 96) says that PORTB’s address is 0x25.

Where can I set the pin to be read or write?

That is the job of “data direction register”, right after the PORTB’s address in datasheet , we can find DDRB located at 0x24. Its initial value is zero, to set it as write we need to set bit 7 to 1.

micro atmel mega Data register and direction for port B , built in led is in bit 7.

With these values in mind, we can continue to write some code in C.

// direct assignment of memory address

// data register direction (set input or output)
#define DDRB *((volatile unsigned char *)0x24)

// data register (actual on or off)
#define PORTB *((volatile unsigned char *)0x25)
// why volatile? this keyword tells the compiler to don't assume that can change
// this value for optimization reasons.

void setup() {

  // every memory address have 8 bits
  //          0 0 0 0 0 0 0 0
  // bit pos  7 6 5 4 3 2 1 0
  // To turn on built in LED => set bit 7 to 1
  //
  DDRB = 0x80; // hexadecimal
  // Other anotations:
  // DDRB = 0b10000000; // binary;
  // DDRB = 128;       // decimal;
}

int main() {
  setup();
  while (1) {
    PORTB = 128; // enable only bit 7 (TURN ON)
    for (long i = 0; i < 100000; i++) {
      // Make a non empty loop (so compiler does not delete this) to make delay
      PORTB = 128;
    }
    PORTB = 0; // disable only bit 7 (TURN OFF)
    for (long i = 0; i < 100000; i++) {
      // Make a non empty loop (so compiler does not delete this) to make delay
      PORTB = 0;
    }
  }
  // Notes:
  // - delay, I tune it to get a blinking speed OK. Later, I will dive into
  // functions I can use here.
  // - setup function could be set inside main, meaning line of DDRB could
  // be inside main, but I kept it as this to see similarity with Arduino IDE
}

[1] Atmel ATmega640/V-1280/V-1281/V-2560/V-2561/V DATASHEET