an early AVR victory

For the past few Sundays I’ve been heading over to HacDC for some intro-to-AVR classes run by Nikolas C. It’s something I’ve wanted to learn for a while. Yesterday I actually got the toolchain working and managed to reprogram the Game of Life doohickey that Kriston and I built to do something of my own design. Not anything as cool as the GoL code, admittedly, but still: it’s a start. Now the door’s open.

The AVR is a particular brand of microcontroller (“uCs” for short) — the same one that powers the Arduino, in fact. I should maybe explain what that means. Microcontrollers aren’t very useful by themselves. They need power supplies, things to interact with, and sometimes some supporting components to protect the relatively delicate chip. The Arduino platform adds a bunch of commonly-used circuitry around an AVR chip. It also provides a software environment — both on the chip and on the computer you use to write code — that’s easier to work with than vanilla AVR code. With Arduino you don’t have to worry about binary arithmetic or anything like that — the metaphors in use are similar to the ones employed when programming a full-sized computer system. The software environment abstracts that stuff away. It makes for a much less daunting experience, particularly for those who have taken an introductory CS class.

As you might expect, Arduino has to make some sacrifices to achieve this ease of use. The AVR has some interesting features that aren’t exposed, or aren’t fully exposed, by the Arduino environment. Things like interrupts (a useful way to increase the responsiveness of your application and avoid messy loop structures), variable clock speeds (a good way to save power) and sleep modes (another good way to save power) are more easily worked with through direct manipulation of the AVR. Plus there’s the fact that, for many applications, an Arduino is overkill. It’s physically larger, it has circuitry for features you may not be using, and it costs $15 bucks or so, instead of as little as a dollar or two for an AVR.

The AVR/Arduino system is actually a great example of the power of abstraction: not only does Arduino make AVR development easier, but AVR taken by itself also represented something of a landmark in the simplification of microcontroller development. That’s because you can write programs for the AVR in C, instead of the inscrutable assembly code demanded by most other architectures (the PIC family of uC is also popular with hobbyists because it let them program in a flavor of BASIC — but AVR is supplanting it).

Of course, to a scripting language-oriented CS fraud such as myself, programming in C is still daunting. I haven’t really worked with compiled languages since college, and I certainly wasn’t any good at them — there’s a threshold of understanding that I didn’t cross until years after graduation; I’m still years away from the sophistication on these matters that people like Alex and Tim possess.

But even they would probably find AVR programming to be a bit bizarre. It mostly involves the manipulation of the specific bits located in specific places in memory. There are helpful macros for this stuff exposed by avr-libc, but figuring them out still involves trudging through novel-length PDFs. And since there’s obviously no monitor attached to the chip, debugging presents some unique challenges.

My advice to anyone starting down this path would be to find a class, like I did. Having someone who can supply you with a working Makefile counts for a hell of a lot.

For those curious, here’s what my code looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <avr/io.h>
#include <util/delay.h>
#include <inttypes.h>

// convenience function
int min(uint8_t a, uint8_t b)
{
    return (a<b) ? a : b;
}

int main() {
  DDRB = 0xff; // set data direction on Port B to OUTPUT
  DDRD = 0xff; // set data direction on Port D to OUTPUT
 
  // x goes from 0 to 15, then loops. the same number of LEDs is illuminated (connected to ports B & D)
  uint8_t x = 0;
  while(1)
  {
      x = (x + 1) % 16;

      int y;

      // turn off all LEDs on port B
      PORTB = 0x0;
      for(y=0;y<min(8,x);y++)
      {
          // LEDs are turned on by setting a bit of the port register/byte to 1
          // the _BV() macro returns a byte with a single bit in the specified position turned to 1
          PORTB |= _BV(y);
      }
     
      // do the same thing, but with port D, and with the top half of the 0..15 range
      PORTD = 0x0;
      for(y=8;y<=x;y++)
      {
          PORTD |= _BV(y-8);
      }

      // wait 100 milliseconds
      _delay_ms(100);
  }
 
  return 0;
}

Leave a Reply