<< "Abusing" AVR-GCC for Use with RAM-less AVR ATtiny Parts >>

I've been using "avr-gcc" to program the Atmel ATmega15L microcontroller
for some time.  I love the part, especially its size, features, power
consumption, and price!  I've used "avr-gcc" for quite a number of
ATtiny15L projects, including a tiny RS232 serial port programmer for AVR
in-circuit programming (fits in DB9 connector "hood" and is self-powered
by RS232 voltages), a couple of different DC motor controllers, a air-flow
sensor, a touch sensitive light with dimmer, and an analog data logger.

This use of "avr-gcc" not for everyone!  You need to have a good
understanding of what kind of C statements generate what kind of AVR
assembly code.  If you are a "hard core" embedded programmer like me, that
part comes naturally.

I use avr-gcc version 2.95.2 for ATtiny15L programming, but it is very
likely that avr-gcc version 3 will work as well with a little bit of work
on the "depreciated" header files.  I use the default AVR processor (i.e.,
AT80S8515), but provide my own I/O header definitions for the ATtiny15L.
You *must* use C-compiler optimization (such as -Os) to get AVR code
that does not reference any SRAM.

I really like the ATtiny15L, and am willing to "jump through a few hoops"
in order to make "avr-gcc" work.  If you give avr-gcc's excellent code
generator a chance, it will generate AVR machine code that is as good or
better than hand-generated assembly language, with no need for SRAM.  

Here are a few of the required "tricks"...

(1) Limit the call tree depth to 2 or 3 (e.g., main -> subroutine
-> subroutine), depending up whether you will be using interrupts.
The ATtiny15L has a "hardware" call/return stack of limited depth.

(2) Limit the complexity of your subroutines as to not overflow the
available "scratch" registers.  I found that this is pretty easy to
control, once you have a little bit of experience.

(3) Assign any required global variables to registers as in the following

      register unsigned short v asm("r2");

You can expect a "warning" from the compiler because of this.  Beware,
every "seperately compiled" "leaf" modules needs to be informed of this
"reserved" register", not just the "main" program.

(4) Use a Perl script to check the generated AVR machine code for "illegal
instructions", namely those that reference SRAM (e.g., push/pop/sts/lds)
or use instructions missing from the ATtiny15 instruction set (e.g.,
adiw/sbiw).  My "makefiles" add the following rule...

            $(CC) $(CFLAGS) -c -g -Wa,-almshd=$*.lst -o $*.o $*.c
            perl $*.lst || ( rm $*.o && exit 1 )

...which runs the assembler's "mixed listing" through a Perl script
( which will abort the compile process if "bad stuff" is
detected.  The Perl script prints out an error message showing the
offending AVR assembly code along with the C-code line number and
statement that caused the problem.

(5) Use "static inline" routines and in-line assembly macros as required
to limit call depth (and register usage).  I tend to code up both
"in-line" and "normal" version of many routines.  The in-line version
I typically name with a leading "_".  The "normal" versions simple
reference the "in-line" macro with a wrapper.  Both end up doing exactly
the same thing.

(6) Use my BYTE() in-line assembly macro to avoid C code which generates
"illegal" ATtiny AVR instructions like "sbiw".  (This could be fixed with
a patch to avr-gcc, but I'm too lazy to figure out how.)

    #define BYTE(ch) ({                   \
        uint8_t t;                        \
        asm volatile (                    \
                "ldi %0, %1"              \
                : "=d" (t)                \
                : "M" ((uint8_t)(ch))     \
        );                                \
        t;                                \

For example, instead of using constructs like...

    while (n--)
    for (i = 0; i < 10; ++i)

...use instead...

   while (n -= BYTE(1))
   for (i = 0; i < 10; i += BYTE(1))

(7) If you discover any more "tips", please let me know.

Bruce D. Lightner (