A Simulation Based Absolute Beginnner's Guide To AVR Programming (Part 1)

Mani

5 minutes

Hi! So this is kind of me jotting down my work and progress as I learn how to do AVR programming. This will probably serve as a reference for myself in the future but also just generally serve as a better intro to AVR programming than anything I found. Firstly, I will note this is a "simulation based" introduction, which means no AVR microcontroller will be needed for this section, but we will be flashing one soon, so I recommend grabbing one where you can. I'll be using the atmega328p (the one on Arduinos and Arduino Nanos), since I have a few Arduinos and it's commonly used for educational purposes. Okay, without further ado...

Tooling

The most annoying part of any new setup is making sure you have all the tooling setup. The only things needed for this first part are:

  • avr-gcc
  • simavr
If you are on linux, on Arch you can install both with pacman -S avr-gcc simavr and on Debian based distros similarly apt install avr-gcc simavr, I'm sure these are on brew for MacOS and if you're on Windows then you'll have to use WSL or some alternative because simavr does not work on Windows as far as I know. Using a *nix of some kind is probably going to be significantly easier in the long run anyway. This first post is just making sure the vague idea of everything working is here and getting the "avr" chip to do something.

Hello World!

Alright so now to get a ""simple"" hello world going. I will do this in a literate programming style, so the code will be weaved in this tutorial, but it's pretty short. I recommend you read through with me, I won't post the full file anywhere so...;).

Ok firstly here are the files we need:

 defines.h  Makefile  test.c  uart.c  uart.h  
While the Makefile is not..strictly necessary, it is definitely going to be useful.

So, starting out from main.c, we first include our own defines.h file because it defines F_CPU which is needed for <utils/delay.h>.

 #include "defines.h" // This defines is needed for <utils/delay.h> 

Then, we include stdint.h and stdio.h, which allow us to use C types such as uint8_t that are going to be very useful to make sure we know the width of the types we're using.


#include <stdint.h>
#include <stdio.h>
 

From here, we need avr/io.h and avr/pgmspace.h, the first of which gives us useful avr device-specific IO definitions and the second which lets us use program space, which while not strictly necessary for a bare-bones hello world can be good practice.


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

Finally, we include our own "uart.h" which we will write later, and, finally, we're done with includes.


#include "uart.h"
 

Okay, so now we're going to use FDEV_SETUP_STREAM to create a FILE stream. We will then setup this FILE stream to be the default for stdout, stdin, and stderr (we could do this with fdev_setup_stream instead of FDEV_SETUP_STREAM as well).


FILE uart_str = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
int main(void) {
    stdout = &uart_str;
    stdin = &uart_str;
    stderr = &uart_str;

Then we will call the uart_init() method we will declare in uart.h and write in uart.c.


    uart_init();

From there, we just call printf and fprintf to print to stdout and stderr respectively: printf("Hello stdout\n"); fprintf(stderr, "Hello stderr!\n");

And, as a final extra, here is a print using program space instead of RAM. For more information about this here is a good article on nongnu.org.


    printf_P(PSTR("Hello world from program space!\n"));

And, finally, for main.c, we return 0 and end the function with a closing bracket.


    return 0;
}

I think uart setup is a bit out of scope of this blog post, I have posted the code I used on this Github gist. As you may notice from the license, this was grabbed from the nongnu.org website's stdio avr tutorial, which I also recommend looking at. The only thing to change here is I had to change, for example UBRR to UBRR0 and such for my device because the one on the nongnu.org website is for an ATmega16 while my ATmega328p also has other USART options I presume.

Compiling

Ok so now we need to compile and run it. Here's my makefile for this, if you can't read makefiles, I have a 12 minute tutorial on my youtube channel and there's this great blog post called "Practical Makefiles By Example" to check out. Ok so, here is the make file:


src = $(wildcard *.c)
obj = $(src:.c=.o)
CC=avr-gcc

CFLAGS = -O2 -g -mmcu=atmega328

firmware.elf: $(obj)
	$(CC) $(CFLAGS) -o $@ $^ 

.PHONY: clean
clean:
	rm -f $(obj) firmware.elf

# SIMAVR_UART_XTERM=1 has simavr create a simulated serial at /tmp/simavr-uart0 for me, useful for taking input and output instead of just output (simavr doesn't support input)
run: firmware.elf
	SIMAVR_UART_XTERM=1 simavr -m atmega328 -f 16000000 $^ 

Just running make will compile the firmware.elf file and make run will run it in simavr. It is important to note simavr will not take input but it will print output from uart0 which makes this really easy! You should get something like:


SIMAVR_UART_XTERM=1 simavr -m atmega328 -f 16000000 firmware.elf 
Loaded 2540 .text at address 0x0
Loaded 50 .data
Hello stdout..
Hello world from program space!..
Hello stderr!..

Hope this was somewhat helpful! I'll hopefully see you in part 2 where we interact with some registers and maybe even flash a real chip ;).