Friday, August 23, 2013

AVR Code Size Reduction

Atmel AVR035 (pdf)) has some tips on code size reduction. Here's what I learned in the real world, reducing my flash-constrained A2D code from 4k down to 1.4k, great because the onboard ATtiny44As only have 4K of flash.

Side note: My A2D boards convert Sharp Rangers or other analog signals to digital -- I2C, and soon SPI and Serial, with oversampling, decimation, and filtering to reduce noise and increase resolution.

The biggest saving by far? Well, sorry, you have to read all the way to the end to find out. The suspense must be killing you... ;) 

Meanwhile here are a subset of the AVR035 tips that helped me save some code space, from a gcc, real world perspective, reproduced here for educational purposes. I use avr-gcc rather than the IAR toolchain that AVR035 (pdf) was written for and avr-gcc provides a few automatic optimizations.
  1. Compile with full size optimization. In gcc, use -Os
  2. Use local variables whenever possible. This saved a few bytes here and there.
  3. Use the smallest applicable data type. Use unsigned if applicable. This definitely helped shave some space. With an 8-bit micro it takes more instructions to deal with > 8-bit data.
  4. Use for(;;) { } for eternal loops. No effect. Apparently gcc knows how to optimize while(1){}
  5. Use do { } while(expression) if applicable. Didn't seem to matter in gcc the one time I tried this.
  6. Use descending loop counters and pre-decrement if applicable. Didn't seem to matter in gcc the one time I tried this.
  7. Declare main as C_task if not called from anywhere in the program. This is an IAR-ism. The avr-gcc compiler automatically ensures that main doesn't return a value.
  8. Use macros instead of functions for tasks that generates less than 2-3 lines assembly code. They're right. I tried converting some macros to functions in the TWI slave library I was using. It added size.
  9. Code reuse is intra-modular. Collect several functions in one module (i.e., in one file) to increase code reuse factor. I don't know for sure if this helped but I program this way normally for maintainability.
I started out at 4.2k and doing these things reduced code size to around 3.1k.

I encourage you to read the document as some of the tips I didn't mention may also help you.

Biggest Savings?

The biggest savings by far was to avoid floating point operations at all costs. This saved me in a prior project as well. Floating point on a Tiny is just murder on flash memory.

Take a look at your map file (avr-gcc -Wl,-Map,MyProject.map in your makefile) and find any unnecessary or unexpected library calls. In my case I saw stuff like this:


...
                              A2Di2c.o (usiTwiSlaveInit)
/usr/local/lib/gcc/avr/4.7.2/../../../../avr/lib/avr25/libm.a(floatsisf.o)
                              ../A2D/libA2D.a(adc.o) (__floatunsisf)
/usr/local/lib/gcc/avr/4.7.2/../../../../avr/lib/avr25/libm.a(pow.o)
                              ../A2D/libA2D.a(adc.o) (pow)
/usr/local/lib/gcc/avr/4.7.2/../../../../avr/lib/avr25/libm.a(exp.o)
                              /usr/local/lib/gcc/avr/4.7.2/../../../../avr/lib/avr25/libm.a(pow.o) (exp)
/usr/local/lib/gcc/avr/4.7.2/../../../../avr/lib/avr25/libm.a(fp_inf.o)
                              /usr/local/lib/gcc/avr/4.7.2/../../../../avr/lib/avr25/libm.a(exp.o) (__fp_inf)
/usr/local/lib/gcc/avr/4.7.2/../../../../avr/lib/avr25/libm.a(fp_nan.o)
                              /usr/local/lib/gcc/avr/4.7.2/../../../../avr/lib/avr25/libm.a(pow.o) (__fp_nan)
/usr/local/lib/gcc/avr/4.7.2/../../../../avr/lib/avr25/libm.a(fp_powser.o)
                              /usr/local/lib/gcc/avr/4.7.2/../../../../avr/lib/avr25/libm.a(exp.o) (__fp_powser)
...


Lots of libm (math library) calls. I had carelessly made a call to pow() which converts to floating point and thus adds a ton of space. Instead I converted to a simple multiplication loop using integer math. Size dropped from 3.1k down to 1.4k. Wow.

Did this help you? If so, do me a favor and share via redit, twitter, Google+, etc. Thanks!

3 comments:

  1. I have some more ideas described here:
    http://nerdralph.blogspot.ca/2013/12/trimming-fat-from-avr-gcc-code.html

    ReplyDelete
  2. p.s. it would be nice if you turned off captcha for comments from non-anonymous posters.

    ReplyDelete
  3. On my project nearing 16K on an ATMega16, I had a line stating:
    OCR1A=duty*2.55;

    After reading your article, I have changed the code to:
    OCR1A=duty*255/100;

    The hex file dropped to 6K. Whow!

    ReplyDelete

Note: Only a member of this blog may post a comment.