AVR Microcontrollers

The AVR family of 8-bit microcontrollers are nice little things. Their instruction set will look familiar to anyone familiar with modern RISC processors, 32 general purpose registers, short and simple instructions. They don't seem to have as many bit-twiddling instructions as, say, the PIC, but they do have continuous unpaged memory. I used a 40-pin mega8535 for this project, though anything with 8K or more of program memory will do, for example, a 28-pin mega8 would do nicely. The best thing about AVRs though is that the full gcc toolchain is available for them (as far as I know, the only 8-bit processor targeted by GCC).

Some AVR features

The mega8535 has an internal RC oscillator which by default is selected, so if you put one in to a board with a 4MHz crystal on it you will be sorely disappointed if you expect it to run at 4MHz (it will in fact be running at approximately 1MHz). Solution is to program the fuse bits, using eg. the uisp programmer:

uisp -v -dprog=stk200 -dpart=ATMEGA8535 -dlpt=0x378 --wr_fuse_l=0xff

See the data sheet for the various fuse bits. This only has to be done once for each microcontroller. (This business is mentioned in the AVR WinC FAQ, which I had read but inconveniently forgotten when I came across this problem).

To clear interrupt flags, don't use the conventional ANDing in bits:

TIFR &= ~_BV(TOV0);  // Wrong, this will clear all the flags but TOV0

The interrupt flags are cleared by writing a one to them and writing a zero leaves them unchanged:

TIFR = _BV(TOV0); // That's better

Shouldn't need saying but do use the predefined macros for bit values etc. Not all register fields are in the same place on different processors.

The code generated by GCC isn't perfect, for example:

unsigned int get16(unsigned char *p)
{
  return (*p << 8) | *(p+1);
}

compiles to:

	movw r30,r24 // Copy parameter p (R24, R25) to Z register (R30 & R31)
	ld r24,Z     // Load R24 indirect from Z
	clr r25      // R25 = 0
	mov r19,r24  // R19 = R24
	clr r18      // R18 = 0
	ldd r24,Z+1  // Load R24 indirect from Z+1
	clr r25      // R25 = 0
	or r18,r24   // R18 = R18 | R24, ie. R18 = R24, since R18 = 0
	or r19,r25   // R19 = R19 | R25, ie. R19 = R19, since R25 = 0
	movw r24,r18 // Move result (in R18/19) to return value	register (R24/25)
	ret

OK, so the copying to Z is necessary, since we only have a limited range of index registers that we can use, and we have to do the load indirects, but we should be able to simplify to something like:

	movw r30,r24 // Copy parameter p (R24, R25) to Z register (R30 & R31)
	ld r24,Z     // Load R24 indirect from Z
	ld r25,Z+1   // Load R24 indirect from Z+1
        ret

The problem, I guess, is that the intermediate values are all 16-bit (since that's the size of an int on the AVR) and presumably are represented as 16-bit values in the RTL, where most of the optimization takes place, but the RTL optimizer doesn't know about the mapping of RTL registers to pairs of 8-bit registers, so we don't get the obvious optimizations happening. I suppose such optimizations could be done after the translation to assembler but gcc doesn't seem to work like that (and who would want it to?)

Of course, given that GCC is, I suspect, somewhat oriented towards more capable architectures than the AVR, and, if it was easy, someone would have already done it, we might have to put up with this. I did try compiling an early version of the code with a commercial AVR compiler (I forget which one) and the code size (even with "code compression" turned on) was significantly larger, so I don't feel too hard done by.