Reducing compiled size of Arduino programs

Imagine I have finished my water level sensor and want to start working on my hen house with its self-opening and closing doors which. I want to permanently package one project, and it only requires a few pins. It turns out that a Adafruit Trinket or Trinket Pro will be just fine for my needs. The catch is my sketch has compiled to 28k. This won’t fit on a Trinket, I might have to use a Qduino or something similar instead.

What can I do to reduce the size of this binary? Looking at the compiled components, Wire and SPI are 10k each. Perhaps I could “roll my own” or find more compact variants on GitHub somewhere?

The toolchain is already optimising for minimum size with -Os. Now I have to figure out how to remove stuff without reducing functionality!

Suggestions on the Arduino forums:

  1. Compile with full size optimization.
  2. Use local variables whenever possible.
  3. Use the smallest applicable data type. Use unsigned if applicable.
  4. If a non-local variable is only referenced within one function, it should be declared static.
  5. Collect non-local data in structures whenever natural. This increases the possibility of indirect addressing without pointer reload.
  6. Use pointers with offset or declare structures to access memory mapped I/O.
  7. Use for( ; ; ) { } for eternal loops.
  8. Use do { } while(expression) if applicable.
  9. Use descending loop counters and pre-decrement if applicable.
  10. Access I/O memory directly (i.e., do not use pointers).
  11. Declare main as C_task if not called from anywhere in the program.
  12. Use macros instead of functions for tasks that generates less than 2-3 lines assembly code.
  13. Reduce the size of the Interrupt Vector segment (INTVEC) to what is actually needed by the application. Alternatively, concatenate all the CODE segments into one declaration and it will be done automatically.
  14. Code reuse is intra-modular. Collect several functions in one module (i.e., in one file) to increase code reuse factor.
  15. In some cases, full speed optimization results in lower code size than full size optimization. Compile on a module by module basis to investigate what gives the best result.
  16. Optimize C_startup to not initialize unused segments (i.e., IDATA0 or IDATA1 if all variables are tiny or small).
  17. If possible, avoid calling functions from inside the interrupt routine.
  18. Use the smallest possible memory model.
  19. Declare functions as static where possible to allow the compiler to automatically compile them inline
  20. Use integer arithmetic instead of float
  21. Avoid arithmetic altogether, e.g.: use ADC output as-is (0–1023) rather than converting to something else (e.g.: 18°C)

http://forum.arduino.cc/index.php?topic=112878.0

Alex,
I guess it depends on how much you value your time. The quick solution is to use a bigger CPU, say an Arduino Mega. These top out at 256K flash which will easily store your program code. Not as petite as the Trinket but…

Another option is to move off the Arduino/Sketch platform and go native AVR. You’d need an AVR programmer of some description, Paul G and I have both bought the latest Atmel programmer/debugger dongles. They’re pretty cheap, check the Atmel store. Going native would reduce the SPI and wire library sizes markedly. AVR Studio is GCC based and free. Works pretty well. For comparison purposes I was able to get an ISDN PABX implemented on an AT90S8515, which had 8K flash. I’d be surprised if your hen house monitor tops out above 4k with a native implementation.

Regards,
Steve

Hi Alex,

A few thoughts that are hopefully of some help:

  • 28KB would fit on an Arduino Pro Mini or any other small atmega328p-based Arduino with the 0.5KB serial bootloader, rather than the 4KB VUSB bootloader on the Trinket Pro. You’ll even have a couple of KB of spare space for new features!

  • If you need i2c (Wire), SPI and Serial output concurrently then Trinket is probably not suitable anyhow - the attiny85 MCU can only do one of these 3 at a time via a single Universal Serial Interface.

  • I reckon you’ll be able to squeeze a couple of KB out of the sketch. However not all the items in that list you posted are still true in my experience, the Atmel app note was written when compilers were a bit older and less good at optimisations. A lot of it still is, of course, though. :smile: Yay computers?

  • The two huge space hogs I often find in Arduino sketches are floating point (‘float’ or ‘double’ types), and string processing (formatting strings, converting integers to strings, etc.) If you can cut either of those out entirely you’ll save a lot.

  • If you don’t need Serial debugging for the “production” build then an obvious step is to take all of that out. You can usually compile this out with an #ifdef and a “debug” function that gets turned into a no-op when you don’t need it. Something like this.

  • How are you measuring per-component space used at the moment? The .o files are deceptive, because the linker only takes the parts of the .o files that are used when it links the program. The only way to be sure about space usage is to inspect the contents of the .elf file, probably with avr-objdump.

  • What Arduino version are you using? 1.6 has a much newer/better gcc in it than 1.0.x. I have’t looked but I wouldn’t be surprised if it produces smaller binaries.

  • Are you able to post the sketch online anywhere? I’m sure a few people can give some specific tips based on it.

Cheers,

Angus

Oops, also meant to second Stephen’s “go native C” recommendation, just at the cost of development & debugging time. Or just rewrite a heavy part of the sketch in native C, inside the Arduino IDE, and leave the rest as-is.

Arduino doesn’t necessarily use a newer shinier gcc, but it does default to -Os (optimise for size) and consistently gets 30% reduction in program size over previous versions (with a corresponding speed penalty).

Right now I’m just looking at the size reported by “make size” (which is defined as ‘echo && $(AVRSIZE) --format=avr --mcu=$(BOARD_BUILD_MCU) $(TARGET).elf’ in the Makefile I’m using). Thanks for the pointer about the *.o files being much larger than what will actually be used.

I’ll be going through my sketch and removing the floating point to start with, then I’ll work through the list of things and figure out which ones alter the generated program size.

If I can get this under 8kB, I should be able to squeeze it onto an ATTiny84, which has hardware SPI support and a few more pins than the ATTiny85 so I can put plenty of blinky lights on it.

FWIW, Arduino has used -Os in every version I’ve ever used - definitely before 1.0. The difference I was referring to is that Arduino IDE 1.0.x (and earlier) used gcc 4.3, and in 1.6 they updated to gcc 4.8.

But if you’re actually using a Makefile then none of that’s relevant anyway.

Hope you’re successful in shrinking your program down.

When desperate for space I:

  • remove all unnecessary prints (often leftovers from development). Unattended programs can run with none.
  • remove all checks for “should not happen” cases, specifically the ones testing program correctness rather than data dependent situations.
  • shorten strings where possible, share constants etc. Ugly but the bytes do add up.

Good luck
Eyal

Bizarre. One of the big noises around the 1.6 release was reduced code size.

I am at the space tonight, will spend a little time cleaning up modules and seeing what happens when I make some of these changes.

So far I’ve managed to reduce the program size down to 9kB. The biggest “reduction per line of code” was 10906 bytes down to 9126 by removing 'Serial.println(pressure_sensor.maxPressure()). That’s about 1700 bytes due to removing a method call, and removing the println(float) override of the println function.

Then by commenting out the I2C code in one method, I reduced the program size down to 7180 bytes. So time to tidy up that library to get it into a state fit for public consumption :smile:

This version of the code also has less than 512 bytes of “data”, so it’s about ready to squeeze into an ATTiny84.

Generally “mee too” on Eyal’s “Remove all checks” comment:

Use precompiler assertions for corner-case checks that should not be seen in normal operation. You can build a debug version with the assertions in that you can run on “fat” hardware (and hopefuly reproduce the triggering conditions), but these get stripped-up for the production build.

The biggest thing I’ve found is to allow the compiler to eliminate unused code. Add the following options to the compile line: -ffunction-sections -fdata-sections -fmerge-constants

Then, on your link line, tell the linker to eliminate unused sections:
-Wl,–gc-sections

You can see all the tricks I do in Flame to cut the size down here:
http://git.infernoembedded.com/cgi-bin/gitweb.cgi?p=flame.git;a=blob_plain;f=eclipse+projects/avr/flame-tutorial-PWM-LED/.cproject;hb=HEAD

Also, check the avrfreaks forums.

Of those, the only ones my Arduino.mk file isn’t already using is -fmerge-constants. Unsurprisingly, adding that flag didn’t contribute any change of size to the project (7290 bytes of program, 309 bytes of data).

Current project is here: https://github.com/AlexSatrapa/Arduino – the code that I’m working on right now is the Bubbler submodule.