README.txt


------------------------------------------------------------------------
     << "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
example:

      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...

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

...which runs the assembler's "mixed listing" through a Perl script
(chkinstr.pl) 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 (lightner@lightner.net)
------------------------------------------------------------------------

Back