1

I'm running into the 32k flash ceiling of my 328p. The code is pretty compact but I need help making it smaller.

Is there an analysis tool (part of avr?) that can tell me which functions take up the most space? Obviously looking at the source code won't help because I don't know how the compiler decides to reorganize things.

I've moved strings to PROGMEM, but I'm still hitting the ceiling.

What are some tips for space saving? What are some ways of seeing what's taking up the most space?

0

2 Answers 2

4

For knowing which functions take the most space, I generally use avr-nm. This command is intended to display the symbol table of a compiled program. It is one of the tools that you get when you install the Arduino environment, so you already have it. avr-nm provides the same information as avr-objdump -t, but it can display it in a nicer format.

As an example, I ran the following command on the basic blinky program:

avr-nm --size-sort -Crtd blink.elf

And this is the output:

00000148 T __vector_16
00000118 T init
00000114 T pinMode
00000108 T digitalWrite
00000082 t turnOffPWM
00000076 T delay
00000070 T micros
00000040 T loop
00000026 T main
00000020 T digital_pin_to_timer_PGM
00000020 T digital_pin_to_port_PGM
00000020 T digital_pin_to_bit_mask_PGM
00000016 T __do_clear_bss
00000010 T port_to_output_PGM
00000010 T port_to_mode_PGM
00000008 T setup
00000004 B timer0_overflow_count
00000004 B timer0_millis
00000001 b timer0_fract

The first column is the size of the object referenced by the symbol. Second column is the type, where ‘t’ (upper or lower case) means “text” (code and PROGMEM data), ‘d’ means “initialized data” (stored in flash and copied to RAM at startup) and ‘b’ means “uninitialized data” (a.k.a. “BSS”, occupies no flash, only RAM). The last column is the symbol's name. In this example you can see that the biggest function is __vector_16 (the ISR for timer 0 overflow), and it takes 148 bytes of flash.

The options I passed to avr-nm are the following:

  • --size-sort, obviously, sorts the symbols by size. It also displays the size, instead of the address, on the first column.
  • -C is for demangling the C++ symbols. For example, instead of displaying “_ZN14HardwareSerial5beginEm”, it would display “HardwareSerial::begin(unsigned long)”.
  • -r means “reverse sort”. This is for having the biggest functions at the top, instead of at the bottom.
  • -td is for displaying the sizes in decimal instead of hexadecimal.

Since you are only interested in flash usage, you can ignore the symbols in BSS. On a unixish OS, you could just pipe into grep -iv ' b '

2

Moving strings to PROGMEM will only exacerbate the problem, since PROGMEM is the 32k flash.

You can easily get your "symbol table" from the compiled .elf file by using avr-objdump -t <.elf file>.

It results in a list something like this:

... scrolls past ...
00000338 g     F .text  00000076 init
000000d4 g       .text  00000000 __ctors_start
000000e2 g       .text  00000000 __do_copy_data
00800110 g       .bss   00000000 __bss_start
000008a2 g     F .text  00000022 main
00000126  w      .text  00000000 __vector_4
00000000  w      *ABS*  00000000 __heap_end
00000126  w      .text  00000000 __vector_9
000003ae g     F .text  000000d0 delay
00000126  w      .text  00000000 __vector_2
00000126  w      .text  00000000 __vector_21
00000126  w      .text  00000000 __vector_15
00000083 g     O .text  0000000a port_to_output_PGM
00000154 g     F .text  0000002e setup
008001c3 g       .stab  00000000 __heap_start
000000d6 g       .text  00000000 __dtors_start
000000d6 g       .text  00000000 __ctors_end
000008ff  w      *ABS*  00000000 __stack
0000008d g     O .text  0000000a port_to_input_PGM
00800110 g       .data  00000000 _edata
008001c3 g       .stab  00000000 _end
00000126  w      .text  00000000 __vector_8
000000bf g     O .text  00000014 digital_pin_to_timer_PGM
00000a08  w      .text  00000000 exit
00000702 g     F .text  00000052 _ZN14HardwareSerial4readEv
000009b8 g       .text  00000000 __udivmodsi4
00000a08 g       .text  00000000 _exit
00000126  w      .text  00000000 __vector_14
00000126  w      .text  00000000 __vector_10
000002a8 g     F .text  00000090 __vector_16
00800100 g       .data  00000000 __data_start
00000480 g     F .text  00000076 __vector_18
00000126  w      .text  00000000 __vector_20
00000108 g       .text  00000000 __do_global_ctors
... etc ...

The lines you are interested in are .text sections. The first big number is the HEX location in memory. The second one is the important one - that is the size.

You can see from that snippet that the function delay is located at address 0x3ae and is 0xd0 bytes in size.

If you're on Unix or OSX you can employ the more advanced searching tools that are at your disposal. For instance, to just list the "global" .text entries:

$ avr-objdump -t <elf file> | grep '\.text' | grep ' g '

Which for my test program results in:

00000079 g     O .text  0000000a port_to_mode_PGM
00000918 g     F .text  00000040 _ZN5Print7printlnEv
00000a00 g       .text  00000000 __tablejump__
000006c2 g     F .text  00000040 _ZN14HardwareSerial4peekEv
000008c4 g     F .text  00000054 _ZN5Print5writeEPKhj
000000d4 g       .text  00000000 __trampolines_start
00000a0c g       .text  00000000 _etext
000006a0 g     F .text  00000022 _ZN14HardwareSerial9availableEv
0000012a g     F .text  0000002a loop
00000774 g     F .text  00000080 _ZN14HardwareSerial5writeEh
00000126 g       .text  00000000 __bad_interrupt
000000d4 g       .text  00000000 __trampolines_end
00000200 g     F .text  000000a8 digitalWrite
00000592 g     F .text  0000010e _ZN14HardwareSerial5beginEm
00000182 g     F .text  0000007e pinMode
00000097 g     O .text  00000014 digital_pin_to_port_PGM
000000d6 g       .text  00000000 __dtors_end
00000754 g     F .text  00000020 _ZN14HardwareSerial5flushEv
0000051c g     F .text  00000076 __vector_19
00000990 g     F .text  00000026 _ZN5Print7printlnEPKc
000000f8 g       .text  00000000 __do_clear_bss
000000ab g     O .text  00000014 digital_pin_to_bit_mask_PGM
00000000 g       .text  00000000 __vectors
000009fc g       .text  00000000 __tablejump2__
00000338 g     F .text  00000076 init
000000d4 g       .text  00000000 __ctors_start
000000e2 g       .text  00000000 __do_copy_data
000008a2 g     F .text  00000022 main
000003ae g     F .text  000000d0 delay
00000083 g     O .text  0000000a port_to_output_PGM
00000154 g     F .text  0000002e setup
000000d6 g       .text  00000000 __dtors_start
000000d6 g       .text  00000000 __ctors_end
0000008d g     O .text  0000000a port_to_input_PGM
000000bf g     O .text  00000014 digital_pin_to_timer_PGM
00000702 g     F .text  00000052 _ZN14HardwareSerial4readEv
000009b8 g       .text  00000000 __udivmodsi4
00000a08 g       .text  00000000 _exit
000002a8 g     F .text  00000090 __vector_16
00000480 g     F .text  00000076 __vector_18
00000108 g       .text  00000000 __do_global_ctors

From that I can see that the function _ZN14HardwareSerial5beginEm is the biggest and takes up 0x10e bytes. That strange named function is a "C++ Munged" name. It actually corresponds to HardwareSerial::begin() and is the parent class name for Serial.begin().

2
  • 1
    Nice answer! But two comments: 1) Moving strings to PROGMEM does not worsen the problem: whereas PROGMEM strings eat flash, non-PROGMEM strings eat both flash and RAM. 2) With avr-objdump you can use the options -t .text to list only the .text section, and -C to demangle the symbols. Commented Jun 14, 2015 at 8:52
  • A number of compilers I use put constant values in .rodata instead of .text, so it is useful to know how to do custom filtering with grep et al - that way you can filter it exactly to your needs. Of course, if you're a wincan't user then you're pretty much screwed...
    – Majenko
    Commented Jun 14, 2015 at 9:44

Not the answer you're looking for? Browse other questions tagged or ask your own question.