Arduino (AVR) programming without the SDK (low level)

This is a well worn path, but I’ve brought together things into a simple 3 example project that can get people up and running quickly.  I’m targeting the Arduino Nano board as the clones are cheap as chips by the handful and readily available, and being a nice small size they are great to hide away inside some project, especially if you don’t solder the pins in…

When I first started using micro controllers I started with PIC’s, Arduino was still in its infancy and the PIC seemed to have some nice features at the time, while I enjoyed using PIC’s one frustration was doing everything low level, at the time I couldn’t seem to find any frameworks or extensive libraries, this lead to the frustration of a road block where you wouldn’t know if its the circuit, your code or even a dodgy sensor that’s making things not work…  It can be very nice to have known working code you can just drop into your project and immediately start to play with work on some new toy sensor.  That said just occasionally you might have a need to do something low level, and in my case I just wanted to see how practical it was to do without the SDK and actually dive into a datasheet and produce something practical.

Naturally you start with the micro controller equivalent of “hello world!” the blinking LED (at least that’s what you’ll call it when you realise for the last 20 minutes you’ve had the LED plugged in to your breadboard the wrong way round! blinking thing!) but before we go there I’ll go through a general purpose Makefile I’ve put together, it can only compile a single C source, but its still a useful starting point, so lets look at the (2!) user editable variables at the top of the Makefile first,

PRG=blink
#PRG=uart
#PRG=interrupt

#BAUD=115200 # optiboot
BAUD=57600 # default arduino bootloader

PRG is set to whichever of the three example’s you want to compile, you might want to run make clean before changing this, just to keep things less cluttered… the next is the baud rate that the boot loader uses, by default the Nano uses 57600 baud with the standard boot loader, but I’d quite recommend trying out optiboot, as its noticeable faster (just put a little label on the board so you know its got a different boot loader, later on down the road!!)

The first target is quite simple

$(PRG).hex: $(PRG)
        avr-objcopy -O ihex -R .eeprom $(PRG) $(PRG).hex

it simply converts a compiled binary into a intel hex format representation that the bootloader needs, note that this rules dependency is the binary so just running make will trigger a complete rebuild if the source is changed (a dependency of the compile rule)

$(PRG): $(PRG).o
        avr-gcc -mmcu=atmega328p $(PRG).o -o $(PRG)

$(PRG).o: $(PRG).c
        avr-gcc -Os -DF_CPU=16000000UL -mmcu=atmega328p -c -o $(PRG).o $(PRG).c

nothing really surprising here obviously if you are targeting a board with a different controller or clock speed you’ll have to alter both of these rules

The only rule left mentioning is one to send the code to the board..

upload: $(PRG).hex
         avrdude -F -V -c arduino -p m328p -P /dev/ttyUSB0 -b $(BAUD) -U flash:w:$(PRG).hex

On linux at least (I don’t know about other OS’s) especially with the Nano – which just works out of the box without need for drivers, the virtual (USB) tty device seems stable, I know some other devices can be a bit temperamental moving about from tty to tty, but in this case I didn’t see the need to use an environment variable for the tty device…

So finally we can get into looking at some simple code, this is really simple so I’ve posted the whole thing here without comments (downloadable version below has comments!)

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

#define BLINK_DELAY_MS 300

int main (void) {
  DDRB |= _BV(DDB5);

  while(1) {
    PORTB |= _BV(PORTB5);
    _delay_ms(BLINK_DELAY_MS);
    
    PORTB &= ~_BV(PORTB5);
    _delay_ms(BLINK_DELAY_MS);
  }
}

The gcc AVR C library comes with a few little goodies, not least defines for various hardware addresses so its kinda handy.   _BV just simply changes a bit value into its binary value so bit 5 for example becomes a value of 32 ( 1 << 5 alternatively 2 ) , it does this at compile time so there no speed overhead for this, there is also a delay macro but it complains about expecting a “compile time integer constant” if you try using a variable, if you want anything more fancy you’d probably be as well implementing a counter with the builtin timer interrupt…

At this point you really want to grab a data sheet for the atmega 328p as its worth getting a good grasp of how the internal registers interact with the hardware registers.  The hardware register DDRB is the Data Direction Register for port B we’re setting pin 5 of port B to an output as that where the built in LED is connected to on the Nano.  When we change the LED’s state its good practice to do it in a way that won’t disturb the other bits, which is why we’re using boolean logic rather than hitting the whole port with a value.

Although more technical, you can see its quite short and once you get used to the various hardware registers its really not much harder, and in fact the AVR C library helps hide some of the complexity. The C library also allows you to provide your own code for get/putchar which in turn means you can redirect stdin and stdout wherever you like, even to a different device.  In the uart example we do just that to make what appears at first sight as being complicated, to actually be rather straight forward.

Because I wanted to have code that wouldn’t need complicated circuits, the interrupt example is a bit silly,  on a change of states of D0-D2 it changes the number of flashes on the built in LED, for something like this polling the state of port D would normally be more than sufficient, a more appropriate example would be using a interrupt to wake the micro controller up from a sleep state, doing this can make a significant difference to battery life verses polling…

So there you have it a quick introduction to low level coding on arduino like hardware, with just enough code to get you in trouble, don’t forget to read the datasheet!

Leave a Reply

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