03 - Telling your compiler how you would like it to optimise

Submitted by Webbot on September 18, 2008 - 7:04pm.

The best way to tell the compiler how you would like it to perform optimisation is via your 'makefile'. This way you can change one line in your makefile and rec-compile the entire program with a new global optimisation setting.

 

 

Some compilers allow you to explicitly change the optimisation by placing directives (such as a '#pragma') to change the default optimisation level. For example: you may have a certain method that you never want to be optimised - in which case you can surround that method with the directives to explicitly say 'Dont optimise' regardless of what the global settings are in your makefile. Unfortunately - I've not found a way to do this with avr-gcc.

 

At the end of this page you will find my 'generic' makefile for our test programs. Please use this, for now, rather than trying to add my changes to your makefile and then bombarding me with questions as to why your file doesn't work. The answer will always be: 'have you tried it with my makefile'!

 

 

Create a scratch directory somewhere for your code. Cut and paste my makefile and save it to that folder. The makefile expects a singe C file called 'main.c' and a single H file called 'global.h' (since some of the AVRLIB files expect it to be called global.h). For the puposes of our tests then I suggest you also cut and paste my global.h which just links in some avr-lib routines. Then, for each of our tests, you will just need to edit 'main.c'.

 

 

 

Here is the makefile:-

 


 

 

# On command line:
#
# make all = Make software.
#
# make clean = Clean out built project files.
#
#
# make filename.s = Just compile filename.c into the assembler code only
#
# To rebuild project do "make clean" then "make all".
#

# Where your AVRLIB is located
AVRLIB = "C:/Program Files/AVRlib"

# MCU name
MCU = atmega8

# Processor frequency.
#     This will define a symbol, F_CPU, in all source code files equal to the
#     processor frequency. You can then use this symbol in your source code to
#     calculate timings. Do NOT tack on a 'UL' at the end, this will be done
#     automatically to create a 32-bit value in your source code.ie 8000000 = 8MHz
F_CPU =  8000000

# Optimization level, can be [0, 1, 2, 3, s].
# 0 = turn off optimization. s = optimize for size.
# (Note: 3 is not always the best optimization level. See avr-libc FAQ.)
OPT = 0

# Target file name (without extension).
TARGET = main


# List C source files here. (C dependencies are automatically generated.)
SRC = $(TARGET).c

# List Assembler source files here.
# Make them always end in a capital .S.  Files ending in a lowercase .s
# will not be considered source files but generated files (assembler
# output from the compiler), and will be deleted upon "make clean"!
# Even though the DOS/Win* filesystem matches both .s and .S the same,
# it will preserve the spelling of the filenames, and gcc itself does
# care about how the name is spelled on its command-line.
ASRC =




# Debugging format.
# Native formats for AVR-GCC's -g are stabs [default], or dwarf-2.
# AVR (extended) COFF requires stabs, plus an avr-objcopy run.
DEBUG = stabs

# Output format. (can be srec, ihex, binary)
FORMAT = ihex

 

# List any extra directories to look for include files here.
#     Each directory must be seperated by a space. This means you cannot use any directories
#     that include a space such as 'C:/Program Files/AVRlib'
#     In that case use the CINCS below and add a -I to each directory
EXTRAINCDIRS =

# Place -I options here to include directories with spaces in their names
CINCS = -I ${AVRLIB}

# Compiler flag to set the C Standard level.
# c89   - "ANSI" C
# gnu89 - c89 plus GCC extensions
# c99   - ISO C99 standard (not yet fully implemented)
# gnu99 - c99 plus GCC extensions
CSTANDARD = -std=gnu99

# Place -D or -U options here
CDEFS = -D F_CPU=$(F_CPU)

# Compiler flags.
#  -g*:          generate debugging information
#  -O*:          optimization level
#  -f...:        tuning, see GCC manual and avr-libc documentation
#  -Wall...:     warning level
#  -Wa,...:      tell GCC to pass this to the assembler.
#    -adhlns...: create assembler listing
CFLAGS = -g$(DEBUG)
CFLAGS += $(CDEFS) $(CINCS)
CFLAGS += -O$(OPT)
CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
CFLAGS += -Wall -Wstrict-prototypes
CFLAGS += -Wa,-adhlns=$(<:.c=.lst)
CFLAGS += $(patsubst %,-I%,$(EXTRAINCDIRS))
CFLAGS += $(CSTANDARD)



# Assembler flags.
#  -Wa,...:   tell GCC to pass this to the assembler.
#  -ahlms:    create listing
#  -gstabs:   have the assembler create line number information; note that
#             for use in COFF files, additional information about filenames
#             and function names needs to be present in the assembler source
#             files -- see avr-libc docs [FIXME: not yet described there]
ASFLAGS = -Wa,-adhlns=$(<:.S=.lst),-gstabs



#Additional libraries.

# Minimalistic printf version
PRINTF_LIB_MIN = -Wl,-u,vfprintf -lprintf_min

# Floating point printf version (requires MATH_LIB = -lm below)
PRINTF_LIB_FLOAT = -Wl,-u,vfprintf -lprintf_flt

PRINTF_LIB =

# Minimalistic scanf version
SCANF_LIB_MIN = -Wl,-u,vfscanf -lscanf_min

# Floating point + %[ scanf version (requires MATH_LIB = -lm below)
SCANF_LIB_FLOAT = -Wl,-u,vfscanf -lscanf_flt

SCANF_LIB =

MATH_LIB = -lm

# External memory options

# 64 KB of external RAM, starting after internal RAM (ATmega128!),
# used for variables (.data/.bss) and heap (malloc()).
#EXTMEMOPTS = -Wl,-Tdata=0x801100,--defsym=__heap_end=0x80ffff

# 64 KB of external RAM, starting after internal RAM (ATmega128!),
# only used for heap (malloc()).
#EXTMEMOPTS = -Wl,--defsym=__heap_start=0x801100,--defsym=__heap_end=0x80ffff

EXTMEMOPTS =

# Linker flags.
#  -Wl,...:     tell GCC to pass this to linker.
#    -Map:      create map file
#    --cref:    add cross reference to  map file
LDFLAGS = -Wl,-Map=$(TARGET).map,--cref
LDFLAGS += $(EXTMEMOPTS)
LDFLAGS += $(PRINTF_LIB) $(SCANF_LIB) $(MATH_LIB)




# ---------------------------------------------------------------------------

# Define directories, if needed.
DIRAVR = c:/winavr
DIRAVRBIN = $(DIRAVR)/bin
DIRAVRUTILS = $(DIRAVR)/utils/bin
DIRINC = .
DIRLIB = $(DIRAVR)/avr/lib


# Define programs and commands.
SHELL = sh
CC = avr-gcc
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
SIZE = avr-size
NM = avr-nm
REMOVE = rm -f
COPY = cp




# Define Messages
# English
MSG_SIZE_BEFORE = Size before:
MSG_SIZE_AFTER = Size after:
MSG_FLASH = Creating load file for Flash:
MSG_EEPROM = Creating load file for EEPROM:
MSG_EXTENDED_LISTING = Creating Extended Listing:
MSG_SYMBOL_TABLE = Creating Symbol Table:
MSG_LINKING = Linking:
MSG_COMPILING = Compiling:
MSG_ASSEMBLING = Assembling:
MSG_CLEANING = Cleaning project:




# Define all object files.
OBJ = $(SRC:.c=.o) $(ASRC:.S=.o)

# Define all listing files.
LST = $(ASRC:.S=.lst) $(SRC:.c=.lst)


# Compiler flags to generate dependency files.
GENDEPFLAGS = -Wp,-M,-MP,-MT,$(*F).o,-MF,.dep/$(@F).d


# Combine all necessary flags and optional flags.
# Add target processor to flags.
ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS) $(GENDEPFLAGS)
ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS)





# Default target.
all: gccversion sizebefore build sizeafter

build: elf hex eep lss sym

elf: $(TARGET).elf
hex: $(TARGET).hex
eep: $(TARGET).eep
lss: $(TARGET).lss
sym: $(TARGET).sym


# Link: create ELF output file from object files.
.SECONDARY : $(TARGET).elf
.PRECIOUS : $(OBJ)
%.elf: $(OBJ)
    @echo
    @echo $(MSG_LINKING) $@
    $(CC) $(ALL_CFLAGS) $(OBJ) --output $@ $(LDFLAGS)


# Create final output files (.hex, .eep) from ELF output file.
%.hex: %.elf
    @echo
    @echo $(MSG_FLASH) $@
    $(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@

%.eep: %.elf
    @echo
    @echo $(MSG_EEPROM) $@
    -$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \
    --change-section-lma .eeprom=0 -O $(FORMAT) $< $@

# Create extended listing file from ELF output file.
%.lss: %.elf
    @echo
    @echo $(MSG_EXTENDED_LISTING) $@
    $(OBJDUMP) -h -S $< > $@

# Create a symbol table from ELF output file.
%.sym: %.elf
    @echo
    @echo $(MSG_SYMBOL_TABLE) $@
    $(NM) -n $< > $@



# Display size of file.
ELFSIZE = $(SIZE) -C  --mcu=$(MCU) $(TARGET).elf
sizebefore:
    @if [ -f $(TARGET).elf ]; then echo; echo $(MSG_SIZE_BEFORE); $(ELFSIZE); echo; fi

sizeafter:
    @if [ -f $(TARGET).elf ]; then echo; echo $(MSG_SIZE_AFTER); $(ELFSIZE); echo; fi



# Display compiler version information.
gccversion :
    @$(CC) --version




# Compile: create object files from C source files.
%.o : %.c
    @echo
    @echo $(MSG_COMPILING) $<
    $(CC) -c $(ALL_CFLAGS) $< -o $@


# Compile: create assembler files from C source files.
%.s : %.c
    $(CC) -S $(ALL_CFLAGS) $< -o $@


# Assemble: create object files from assembler source files.
%.o : %.S
    @echo
    @echo $(MSG_ASSEMBLING) $<
    $(CC) -c $(ALL_ASFLAGS) $< -o $@



# Target: clean project.
clean: clean_list

clean_list :
    @echo
    @echo $(MSG_CLEANING)
    $(REMOVE) $(TARGET).hex
    $(REMOVE) $(TARGET).eep
    $(REMOVE) $(TARGET).elf
    $(REMOVE) $(TARGET).map
    $(REMOVE) $(TARGET).sym
    $(REMOVE) $(TARGET).lss
    $(REMOVE) $(OBJ)
    $(REMOVE) $(LST)
    $(REMOVE) $(SRC:.c=.s)
    $(REMOVE) $(SRC:.c=.d)
    $(REMOVE) .dep/*




# Include the dependency files.
-include $(shell mkdir .dep 2>/dev/null) $(wildcard .dep/*)


# Listing of phony targets.
.PHONY : all sizebefore sizeafter gccversion \
build elf hex eep lss sym \
clean clean_list

 


The important parts of the makefile that you may want to change are:

AVRLIB = ...

This is where your copy of AVR library is installed.

 

MCU = atmega8

This line indicates the target processor type that the compiler should issue commands for. The make process also shows the percentage of memory used based on the amount of memory contained in the specified device.

 

F_CPU = 8000000

This sets the processor speed ie 8000000 = 8Hz. This will pass this value to your code as a variable so that if you have code, like a delay loop, that changes based on the processor speed then it can use this variable. So you only need to change the variable in the makefile and recompile your code.

 

OPT = 0

This sets the optimisation level and is the setting we are going to experiment with the most. The valid values are the numbers 0, 1, 2, 3 and the letter s. 0 will not optimise the code at all. The other numbers will apply more and more optimisation. The 's' option will set the various optimisation flags to optimise for the best speed.

 

 

 

 

 

And here is the default contents for 'global.h' that just includes some of the most basic files from avr-lib. Note that this shows how the value of F_CPU, passed from the makefile, can be used within your code. In this case it creates another variable/macro that holds the number of cycles that are executed per microsecond.

 



#ifndef _GLOBAL_H
#define _GLOBAL_H

// Include standard files from AVRLIB
#include "avrlibdefs.h"
#include "avrlibtypes.h"

// include I/O definitions (port names, pin names, etc) from avr-gcc
#include <avr/io.h>

#define CYCLES_PER_US ((F_CPU+500000)/1000000)     // cpu cycles per microsecond

#endif