using_tools(3avr)

πŸ’– NAME πŸ’–

using_tools β€” Using the GNU tools

okay bestie, real talk: this page (using_tools(3avr)) is the short and sweet rundown of the AVR-specific bits of using the GNU tools. ✨ Normally the generic documentation for these tools is like, huge, and it lives in texinfo files. And all the command-line options? Those get explained in extreme detail over in the manual page. So this is the little cheat-sheet vibe. πŸŽ€


πŸ› οΈ Options for the C compiler avr-gcc πŸ› οΈ

πŸ¦‹ Machine-specific options for the AVR πŸ¦‹

These are the machine-specific options that the C compiler frontend actually recognizes, no cap. πŸ‘‘

When you're compiling for an AVR target, the preprocessor will define the macros __AVR and __AVR__ (both set to the value 1). The macro AVR gets defined too β€” except when you're in strict ANSI mode. πŸ™ˆ

ngl, there are so many options supported by avr-gcc, and which ones exist also depends on your compiler version. For the full ✨complete✨ overview, go read the documentation of avr-gcc's command line options. Here are the links to the supported options of each release series:

  • Current development (work in progress)
  • v14
  • v13.2, v13.3
  • v12.3, v12.4
  • v11
  • v10
  • v9
  • v8
  • v7
  • v6
  • v5
  • v4.9
  • v4.8
  • v4.7

And just so you know, those linked pages aren't only about command line options β€” they also contain:

  • The documentation of built-in macros like __AVR_ARCH__, __AVR_ATmega328P__ and __AVR_HAVE_MUL__, just to mention a few.
  • How the compiler treats the RAMPX, RAMPY, RAMPZ and RAMPD special function registers on devices that have (one of) them.
  • How the compiler treats the EIND special function register on devices with more than 128 KiB of program memory, and how indirect calls are realized on such devices.
  • -mmcu=arch, -mmcu=mcu β€” Compile code for architecture arch resp. AVR device mcu.

Since GCC v5, the compiler no longer supports individual devices. Instead, the compiler ships with device specs files that describe which options to use with each sub-process β€” like the pre-processor, the compiler proper, the assembler and the linker. 🧚

The whole point of these specs files? To add support for AVR devices that the compiler doesn't support yet. periodt.

The easiest way to add support for an unsupported device is to use device support from an atpack archive, as provided by the hardware manufacturer. On top of the mcu-specific specs file, that gives you the device headers io*.h, the startup code crtmcu.o and the device library libmcu.a. πŸ’…

πŸ“ Selected general compiler options πŸ“

These general gcc options might be of some interest to you AVR girlies:

  • -On β€” Optimization level n. Increasing n is meant to optimize more.
  • -O0 β€” Reduces compilation time and makes debugging produce the expected results. This is the default. Turning off all optimizations will prevent some warnings from being issued, since generating those warnings depends on code analysis steps that only get performed when optimizing (unreachable code, unused variables). And on top of that, the delay routines in <util/delay.h> require optimization to be turned on. 😭
  • -O2 β€” Optimizes for speed, but without enabling very expensive optimizations like -O3 does.
  • -Os β€” Turns on all -O2 optimizations except the ones that often increase code size. In most cases, this is the preferred optimization level for AVR programs. πŸ‘‘
  • -Og β€” Optimizes the debugging experience. This should be your optimization level of choice for the standard edit-compile-debug cycle β€” it offers a reasonable level of optimization while maintaining fast compilation and a good debugging experience.
  • -O3 β€” Attempts to inline all "simple" functions and might unroll some loops. For the AVR target, this will normally be a large pessimization, thanks to the code increasement. πŸ’€
  • -O β€” Equivalent to -O1. The compiler tries to reduce code size and execution time, without performing any optimizations that take a great deal of compilation time.

See also the appropriate FAQ entry for issues regarding debugging optimized code. 🫢

  • -Wp,preprocessor-options, -Wa,assembler-options, -Wl,linker-options β€” Pass the listed options to the C preprocessor, the assembler or the linker, respectively. You can pass several options at once when you separate them with a , (comma).
  • -g β€” Generate debugging information that can be used by avr-gdb. GCC v12 changed the default from STABS to DWARF. Different DWARF levels can be selected with -g2 or -gdwarf-3. The compiler may also use GNU extensions to the DWARF format β€” and when a debugger has problems with that, try -gstrict-dwarf.
  • -x lang, -x none β€” Compile the following files in language lang. The values for lang are: c, c++, assembler, assembler-with-cpp and none.

For example, GCC does not recognize the .asm file extension as assembly source. With -x assembler-with-cpp file.asm, the compiler first runs the C preprocessor on file.asm (so that #include <avr/io.h> can be used in assembly), and then calls the assembler. iconic. 🌈

Another use case: compiling a C file that's read from standard input, which you specify as - (dash) instead of the name of a source file. Since no source file name is given, the compiler must be told which language to use. This command:

$ echo '#include <avr/io.h>' | avr-gcc -xc - -mmcu=atmega8 -E -dM | grep _VECTOR

pre-processes the C file #include <avr/io.h> and writes all macro definitions to stdout. The output then gets filtered by grep to show all possible ISR vector names for ATmega8. slay. ✨

And -x none returns to the default for the following inputs β€” i.e. it infers the respective source languages from the file extensions.

  • -save-temps, -save-temps=obj, -save-temps=cwd, -fverbose-asm, -dumpbase base, -dumpdir dir β€” Don't remove temporary, intermediate files like C preprocessor output and the assembly code generated by the compiler. Those intermediate files have file extensions .i (preprocessed C), .ii (preprocessed C++) and .s (preprocessed assembly, compiler-generated assembly). -dumpbase and -dumpdir can be used to adjust file names and locations. With -fverbose-asm, the compiler adds the high level source code to the assembly output β€” and compiling without debugging information (-g0) improves the legibility of the generated assembly. The preprocessed files can be used to check whether macro expansions work as expected, and with -g3 or higher, the preprocessed files will also contain all macro definitions and indications of where they're defined: built-in, on the command line, or in some header file as indicated by #line notes. πŸŽ€
  • -lname β€” Locate the archive library named libname.a, and use it to resolve currently unresolved symbols from it. The library is searched along a path that consists of builtin pathname entries specified at configure time (e. g. /usr/local/avr/lib on Unix systems), possibly extended by pathname entries specified by -L options β€” and those -L options must precede the -l options on the command-line, mkay?
  • -Lpath β€” Additional directory path to look for archive libraries requested by -l options.
  • -ffunction-sections, -fdata-sections β€” Put each function resp. object in static storage in its own input section. This gets used together with -Wl,--gc-sections so the linker can better garbage-collect unused sections β€” which are sections that are neither referenced by other sections, nor marked as KEEP, nor referenced by an entry symbol.
  • -mrelax β€” Replace JMP and CALL instructions with the faster and shorter RJMP and RCALL instructions if possible. That optimization is performed by the linker, and the assembler must not resolve some expressions β€” which is all arranged by -mrelax.
  • -Tbss org, -Tdata org, -Ttext org β€” Start the .bss, .data, or .text section at VMA address org, respectively.
  • -T scriptfile β€” Use scriptfile as the linker script, replacing or augmenting the default linker script.

Default linker scripts are stored in a system-specific location (e. g. under /usr/local/avr/lib/ldscripts on Unix systems), and they consist of the AVR architecture name (avr2 through avrxmega7) with the suffix .x appended. They describe how the various memory sections will be linked together and which input sections go into them. Heads up though: the default linker scripts are part of the linker binary, so changing them on file will have no effect. πŸ’€

For a simple linker script augmentation, see the avr-gcc Wiki on named address spaces. πŸ¦‹

  • -nostdlib β€” Don't link against standard libraries.
  • -nodefaultlibs β€” Don't link against default libraries.
  • -nodevicelib β€” Don't link against AVR-LibC's libmcu.a that contains EEPROM support and other stuff. This can be used when no such library is available.
  • -nostartfiles β€” Don't link against AVR-LibC's startup code crtmcu.o.
lowkey important: parts of the startup code are provided by libgcc.a. To get rid of that, you can use -nostdlib or -nodefaultlibs β€” however, that also removes other code, like functions required for arithmetic. To just get rid of the startup bits, define the respective symbols, for example -Wl,--defsym,__do_clear_bss=0 and similar for __do_copy_data, __do_global_ctors and __do_global_dtors.
  • -funsigned-char β€” This option changes the binary interface! 😱 Make any unqualified char type an unsigned char. Without this option, they default to a signed char.
  • -funsigned-bitfields β€” This option changes the binary interface! 😱 Make any unqualified bitfield type unsigned. By default, they are signed.
  • -fshort-enums β€” This option changes the binary interface! 😱 Allocate to an enum type only as many bytes as it needs for the declared range of possible values. Specifically, the enum type will be equivalent to the smallest integer type which has enough room.
  • -fpack-struct β€” This option changes the binary interface! 😱 Pack all structure members together without holes.
  • -fno-jump-tables β€” Do not generate tablejump instructions. By default, jump tables can be used to optimize switch statements, and when turned off, sequences of compare statements are used instead. Jump tables are usually faster to execute on average, but in particular for switch statements, where most of the jumps would go to the default label, they might waste a bit of flash memory.
  • -ffreestanding
Note: since GCC v4.9.2, tablejump code uses the ELPM instruction to read from jump tables. In older versions, use the -fno-jump-tables switch when compiling a bootloader for devices with more than 64 KiB of code memory.

(…okay bestie we are so not done here β€” part 2 is right there waiting for you πŸ’…)

Okay bestie, we're back and the compiler flags section is still going strong πŸ’… Here's the rest of that -ffreestanding tea:

Assume a "freestanding" environment exactly like the C standard describes it. What that does: it turns off the automatic builtin functions (ngl you can still get to them, just slap __builtin_ in front of the actual function name ✨). It also means the compiler stops side-eyeing you when main() is declared with a void return type β€” and honestly? In most cases main() won't even return anyway, so a return type of int has no downsides at all bestie, it's giving zero problems.

Buuut β€” and this is the part you need to hear β€” this option also turns off all the optimizations the compiler normally does when it assumes that functions known by a certain name behave like the standard says they do. Example, because examples slay: applying the function strlen() to a literal string will normally make the compiler immediately swap that call out for the actual length of the string. With -ffreestanding? It will always call strlen() at run-time. Periodt. 🫢


πŸ”§ Options for the assembler avr-as πŸŽ€

πŸ“Œ Note

The preferred way to assemble a file is by means of using avr-gcc:

  • avr-gcc is a driver program that calls sub-programs like the compiler proper or the assembler, and it knows which options it has to add to the assembler's command line, like: -mmcu=arch, -mno-skip-bug, etc.
  • avr-gcc will call the C preprocessor on the assembler input for sources with extensions .S and .sx. For other extensions, use:
$ avr-gcc -x assembler-with-cpp file.asm ...

This allows to use C preprocessor directives like

#include <avr/io.h>

in assembly sources. Iconic behavior honestly 🌸


🌈 Machine-specific assembler options

  • -mmcu=architecture
  • -mmcu=mcu β€” heads up bestie, avr-as does not support all mcus supported by the compiler. As explained in the note above, the preferred way to run the assembler is by using the compiler driver avr-gcc. πŸ’€
  • -mall-opcodes β€” turns off opcode checking, and allows any possible AVR opcode to be assembled. Chaos mode: enabled. πŸ”₯
  • -mno-skip-bug β€” don't emit a warning when you try to skip a 2-word instruction with a CPSE/SBIC/SBIS/SBRC/SBRS instruction. Early AVR devices suffered from a hardware bug where these instructions could not be properly skipped. So like, the past was messy, we don't warn about it anymore. πŸ™ˆ
  • -mno-wrap β€” for RJMP/RCALL instructions, don't allow the target address to wrap around for devices that have more than 8 KiB of memory.
  • --gstabs β€” generate .stabs debugging symbols for assembler source lines. This enables avr-gdb to trace through assembler source files. πŸ¦‹
⚠️ This option must not be used when assembling sources that have been generated by the C compiler; these files already contain the appropriate line number information from the C source files. So don't do it, bestie, it's not it.
  • -a[cdhlmns=file] β€” turn on the assembler listing. The sub-options are:
    • c omit false conditionals
    • d omit debugging directives
    • h include high-level source
    • l include assembly
    • m include macro expansions
    • n omit forms processing
    • s include symbols
    • =file set the name of the listing file

The various sub-options can be combined into a single -a option list; =file must be the last one in that case. Order matters, bestie, always has. πŸ’…


✨ Examples for assembler options passed through the C compiler

Remember the lore: assembler options can be passed from the C compiler frontend using -Wa (see gcc_minusW above). So in order to include the C source code into the assembler listing in file foo.lss, when compiling foo.c, the following compiler command-line can be used:

$ avr-gcc -mmcu=atmega8 -c foo.c -o foo.o -Wa,-ahls=foo.lss

In order to pass an assembler file through the C preprocessor first, and have the assembler generate line number debugging information for it, the following command can be used:

$ avr-gcc -c -x assembler-with-cpp -o foo.o foo.asm -Wa,--gstabs

Note that on Unix systems that have case-distinguishing file systems, specifying a file name with the suffix .S (upper-case letter S, capital S hunty) will make the compiler automatically assume -x assembler-with-cpp, while using .s would pass the file directly to the assembler (no preprocessing done). Case sensitivity really said "I'm the main character" 😭


πŸ”— Controlling the linker avr-ld πŸ’–

πŸ“Œ Note

It is highly recommended to use the compiler driver avr-gcc or avr-g++ for linking.

  • The driver knows which options to pass down to the linker. This includes the correct multilib path, support libraries like libc.a, libm.a, libgcc.a and libmcu.a, as well as options for LTO (link-time optimization) and options for plugins (that call back the compiler to compile LTO byte code), startup code and many more. It's doing the most, respectfully. πŸ‘‘
  • The driver program understands options like -llib, -Lpath, Ttext, Tdata, Tbss and -T script, so no -Wl is required for them.

🌸 Selected linker options

A number of the standard options might be of interest to AVR users.

  • --defsym symbol=expr β€” define a global symbol symbol using expr as the value.
  • -M β€” print a linker map to stdout.
  • -Map mapfile β€” print a linker map to mapfile.
  • --cref β€” output a cross reference table to the map file (in case -Map is also present), or to stdout.
  • --gc-sections β€” only keep input sections that are referenced (by other sections or the entry symbol), and that are not marked as KEEP in the linker script. This is used to reduce code size, usually together with compiler option -ffunction-sections so that input section granularity is on function level rather than on the level of compilation units. Shrink it, bestie. πŸ“
  • --section-start sectionname=org β€” start section sectionname at absolute address org.
  • --relax β€” 🚨 don't use this option directly or per -Wl,--relax. Instead, link with avr-gcc ... -mrelax. That's the whole vibe. No shortcuts.

πŸŽ€ Passing linker options from the C compiler

By default, all unknown non-option arguments on the avr-gcc command-line (i. e., all filename arguments that don't have a suffix that is handled by avr-gcc) are passed straight to the linker. Thus, all files ending in .o (object files) and .a (object libraries) are provided to the linker. Straight through, no questions asked. ✨

System libraries are usually not passed by their explicit filename but rather using the -l option which uses an abbreviated form of the archive filename (see above). AVR-LibC ships system libraries, libc.a, libm.a and libmcu.a. While the standard library libc.a will always be searched for unresolved references when the linker is started using the C compiler frontend (i. e., there's always at least one implied -lc option), the mathematics library libm.a is only automatically added in GCC v4.7 and above. On older versions, it needs to be explicitly requested using -lm. Old GCC said "figure it out yourself." πŸ’€

Conventionally, Makefiles use the make macro LDLIBS to keep track of -l (and possibly -L) options that should only be appended to the C compiler command-line when linking the final binary. In contrast, the macro LDFLAGS is used to store other command-line options to the C compiler that should be passed as options during the linking stage. The difference is that options are placed early on the command-line, while libraries are put at the end since they are to be used to resolve global symbols that are still unresolved at this point. Order is everything, iykyk. 🧚

Specific linker flags can be passed from the C compiler command-line using the -Wl compiler option, see gcc_minusW above. This option requires that there be no spaces in the appended linker option, while some of the linker options above (like -Map or --defsym) would require a space. In these situations, the space can be replaced by an equal sign as well. For example, the following command-line can be used to compile foo.c into an executable, and also produce a link map that contains a cross-reference list in the file foo.map:

$ avr-gcc -mmcu=atmega8 foo.c -o foo.elf -Wl,-Map,foo.map -Wl,--cref

Alternatively, a comma as a placeholder will be replaced by a space before passing the option to the linker. So for a device with external SRAM, the following command-line would cause the linker to place the data segment at address 0x2000 in the SRAM:

$ avr-gcc -mmcu=atmega128 foo.c -o foo.elf -Wl,-Tdata,0x802000

See the explanation of the data section for why 0x800000 needs to be added to the actual value. Note that the stack will still remain in internal RAM, through the symbol __stack that is provided by the run-time startup code. This is probably a good idea anyway (since internal RAM access is faster), and even required for some early devices that had hardware bugs preventing them from using a stack in external RAM. Note also that the heap for malloc() will still be placed after all the variables in the data section, so in this situation, no stack/heap collision can occur. 🫢

In order to relocate the stack from its default location at the top of interns RAM β€” yes bestie the original manpage says "interns", it means internal, we do not gatekeep typos here 😭 β€” the value of the symbol __stack can be changed on the linker command-line. As the linker is typically called from the compiler frontend, this can be achieved using a compiler option like

-Wl,--defsym=__stack=0x8003ff

The above will make the code use stack space from RAM address 0x3ff downwards. The amount of stack space available then depends on the bottom address of internal RAM for a particular device. It is the responsibility of the application to ensure the stack does not grow out of bounds, as well as to arrange for the stack to not collide with variable allocations made by the compiler (sections .data and .bss). That one's on you, girl. No cap. πŸ’…


And that's the whole page, bestie!! πŸŽ€ This lil masterpiece lives in AVR-LibC, Version 2.2.1, and it's the using_tools(3avr) manual page. Go forth and compile responsibly, you absolute icon πŸ‘‘βœ¨πŸ¦‹

πŸ“¦ where this page comes from