6
\$\begingroup\$

I am currently writing a program in AVR Studio, here is the build *Memory Usage : *

Device: atmega32
    Program:    9304 bytes (28.4% Full)
    (.text + .data + .bootloader)
    Data:        334 bytes (16.3% Full)
    (.data + .bss + .noinit)

As I'm using an ATmega32 micro-controller, it seems not to be a problem, but I want to use this same code and use for a ATmega8 micro-controller.

So I want to reduce the program size less than 8192 bytes.

How can I do this?

\$\endgroup\$
  • 1
    \$\begingroup\$ Are you using the floating point library? \$\endgroup\$ – Jeroen3 Oct 12 '19 at 15:40
  • 8
    \$\begingroup\$ @Danial this doesn't solve your problem, but I'll just note that the .hex file is just a format to store the program code in. They're just text, so they would compress nicely. But it's not what ends up in the MCU's flash, the actual program code is. So that's what you want to make smaller, not the .hex file as such. \$\endgroup\$ – ilkkachu Oct 13 '19 at 13:38
15
\$\begingroup\$

You can not compress the hex code, you can only try to reduce it.

  • Try different? compiler settings (maximum optimization and optimize for size)
  • Pick your way through the source code and see what can be optimized or omitted.
  • See if any unnecessary library code is pulled in. (It should not be, but who knows)

Good point from Jeroen3: Check if you need/have floating point. Especially functions like printf sometimes pull in the floating point code to be able to cope with printf("%f".... If you never use floating point numbers you don't need that.

\$\endgroup\$
11
\$\begingroup\$

The MCU cannot execute compressed code.

However, there are some things you can do:

  • Instead of using full fledged library functions, create some or all of the functions yourself; this way you can optimize library functions that mostly are (too) flexible for your specific need.
  • Remove duplicated code in your own code. Use parameters for almost duplicated code to make it flexible.
  • Use the smallest type for constants, and especially constant arrays.
  • Remove 'obvious' dead code (code which can never executed), see remark of Jeroen3 and Dakkaron below. (check if this occurs for full functions or part of functions). See also this link.
  • Decrease strings (in case you use a lot of print statements, minimize these constant strings if possible).
\$\endgroup\$
  • 10
    \$\begingroup\$ Removing dead code is a linker feature: -Wl, --gc-sections which also applies to unused library functions. \$\endgroup\$ – Jeroen3 Oct 12 '19 at 15:39
  • \$\begingroup\$ @Jeroen3 Thanks, I did not know it was so smart. I updated my answer (with a comment to your name). \$\endgroup\$ – Michel Keijzers Oct 12 '19 at 18:37
  • 2
    \$\begingroup\$ @Jeroen3 This only works for "hard" dead code, so code where the compiler can infer that it is dead. It does not work for dead code that the compiler cannot determine, e.g. some code that is only executed if the function is called with a specific parameter, but the function is never called with that parameter. So it is definitely useful to use the linker check, but it is also useful to analyse the code by hand for dead code. \$\endgroup\$ – Dakkaron Oct 14 '19 at 14:23
  • 1
    \$\begingroup\$ @Dakkaron Thanks ... I added your name to to the comment (actually I had something like what you wrote before). \$\endgroup\$ – Michel Keijzers Oct 14 '19 at 14:26
8
\$\begingroup\$

The first step for any kind of optimisation is find out what's doing it.

Your first move should be to get the linker to dump the address of every identifier in the build. That's all functions and all variables. Your linker should also be able to report the sizes of functions; it probably won't do variable sizes, but you can infer those from the address of the next variable in the list.

Once you know where your space is going, you can do something about it. Chances are pretty good the solution will be obvious when you start looking at what's biggest.

Until you know though, you're just shotgunning blind, and that's never a good plan.

\$\endgroup\$
8
\$\begingroup\$

In addition to the excellent suggestions provided in the other answers here I want to comment that there can be a huge difference in how much compilers (and linkers) can optimize code.

I worked at a company some years back where the product was using the ATMega8. When I arrived on the scene this product had three different source code builds to provide separate sets of features for various product configurations. The source code was compiled using a low cost C compiler and each code set barely fit in the 8K bytes of the device FLASH memory.

I had the company purchase a high end compiler from a company well known in the AVR community. I then went to work on the software source codes and set the compiler options for maximum optimization.

When I finished the effort all of the product options fit in a single image that was less than the 8K bytes. In fact there was space enough for me to add a transmit only software UART to the code that the software could spill out internal information that was used to help calibrate the product parameters. The UART output was triggered when a 28V signal was applied to one of the A/D channels through a voltage divider. The trigger was needed because the software UART output used a GPIO that was normally a signal that was an output from the product.

\$\endgroup\$
  • 1
    \$\begingroup\$ Good story. Compilers can do some good work, but human effort can help to. In a similar way I took over a project usin an 8-bit PIC processor from a customer where different versions were separated in three similar source codes - program memory was pretty much full for each version (8k program memory, about 85% full). I merged the codes, optimized the C code itself by checking the generated assembly code and added many functions over time (about 98% full). Throwing a better compiler at it could have helped bit, but not as much as the code optimisations. \$\endgroup\$ – le_top Oct 12 '19 at 19:45
6
\$\begingroup\$

There is no "practical" way to run compressed code on an AVR so your problem becomes "how do I optimize the size of my firmware".

Toolchain tricks (ie. you don't have to modify your code):

  1. What is the compiler optimization level? In gcc the option to optimize for minimum size is called -Os

  2. There is a facility called link time optimization that can further optimize for size. It is already mentioned in this answer.

  3. The linker can optimize out unused data and symbols. It is enabled using -Wl,--gc-sections -ffunction-sections -fdata-sections

  4. Enable -mcall-prologues. Explained here.

Generic code tricks:

  1. Run nm -S --size-sort -t d your_output_file.elf. This command will show how big each symbol is (symbol in linker-speak means data or code). You can then find out where the biggest opportunity for optimization is.

  2. Try to find pieces of code that could become functions on their own.

  3. Avoid using printf. If you just need to convert integers to strings, then itoa is an option. You can also check out xprintf which is a lighter alternative to standard printf.

  4. If you are using floating point numbers (float, double) - try to convert the code to use integers. For example if you need 2 decimal places you could use simple scaling by 100 (ie. 2.5 becomes 250). Any floating point operation (as simple as float x = a + b) on an AVR pulls in a pile of library code, because the CPU does not support such operations in hardware.

\$\endgroup\$
4
\$\begingroup\$

If you are looking for ways to reduce the size of your program code - besides having the optimizing compiler & linker chopping away some, and not using standard library functions, as others have noted, it also depends on the composition of program code how big it will be.

  • first, try to find a way to show you what parts of your program is most offending with regards to size. Alas I'm not familiar with AVR studio, but here, user skeeve, post #7 lists the sizes of single object files. If your program has several modules, you'll at least know which ones of those are the largest, and good candidates for manual optimization.
    • why manual code refactoring, you say? If you can get the code smaller that way, as opposed to using the compiler's optimization for that, debugging (as in stepping through the code with GDB or such) will work much nicer than when the optimizer creates a stark difference between real code executed and your source code, making things less easy to follow
  • here are some tips: see chapter "3 Tips and tricks to reduce code size"
  • avoid "copy & paste" code
    • i.e. use a routine for a sequence of actions you need done in more than one place, and then call it from those places
    • this may sound obvious ("I have used functions, duh!"), but it can be worthwhile looking for places in code that do almost exactly the same thing, take several steps each time, and all that's different depends only on a couple parameters that could be function arguments
    • Side note: there are also other reasons to do this: use routines routinely
  • (more desperate, fighting for a couple extra bytes...) consider converting bigger switch/case or if..else..if..else... blocks (from which functions are called) to a const array with function pointers. Works only if all functions have the same signature, i.e. same pointer type, and computing an array index from the value deciding what gets called doesn't add a lot of code on its own. Indexing an array and calling on a function pointer may produce smaller code than lots of switch/case or if/else for the same scenario. (it did for me - on a 32bit Microblaze, though. YMMV)
\$\endgroup\$
2
\$\begingroup\$

If your project consists of multiple source files (as most projects do), you may also look at Link Time Optimization (commonly abbreviated LTO).

It makes extra optimizations between objects (at link time, as the name suggests).

You may look for a specific "Link time optimization" / "LTO" option in your IDE, or look for a place to add compiler flags.

If your compiler is GCC or Clang based, you may add the -flto flag during both compilation and link time (Both CFLAGS and LDFLAGS if relevant).

This can optimize out whole blocks of code (inside functions) which aren't called, or optimize them for a specific input pattern.

Note that if you use the standard library, you need to make sure you compile that with LTO to get big savings.

You may read more about LTO here: https://en.wikipedia.org/wiki/Interprocedural_optimization
And an example from the Linux kernel here: https://lwn.net/Articles/744507/

\$\endgroup\$

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