04 - Initial examples

Submitted by Webbot on September 22, 2008 - 6:46pm.

Lets now look at what the compiler can do for us and try to understand the information output by the make process.


Here is our simple, if useless, little program. Use cut and paste to save it as 'main.c'

 



#include "main.h"


void postIt(uint32_t stuff){
}

int main(void){
    int8_t  j=0;
    int8_t i;
    for(i=0; i<100;i++){
        j+=i;
    }
    postIt(j);
    return 0;
}

 


 

Lets compile it with no optimisation (OPT = 0 in your makefile). Run the makefile and you should see the following output:-

 


 


avr-gcc (GCC) 4.2.2 (WinAVR 20071221)
Copyright (C) 2007 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Compiling: main.c
avr-gcc -c -mmcu=atmega8 -I. -gstabs -D F_CPU=8000000 -O0 -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=main.lst
-std=gnu99 -Wp,-M,-MP,-MT,main.o,-MF,.dep/main.o.d main.c -o main.o

Linking: main.elf
avr-gcc -mmcu=atmega8 -I. -gstabs -D F_CPU=8000000 -O0 -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=main.o
-std=gnu99 -Wp,-M,-MP,-MT,main.o,-MF,.dep/main.elf.d main.o --output ma
in.elf -Wl,-Map=main.map,--cref -lm

Creating load file for Flash: main.hex
avr-objcopy -O ihex -R .eeprom main.elf main.hex

Creating load file for EEPROM: main.eep
avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" \
--change-section-lma .eeprom=0 -O ihex main.elf main.eep
c:\WinAVR-20071221\bin\avr-objcopy.exe: --change-section-lma .eeprom=0x00000000
never used

Creating Extended Listing: main.lss
avr-objdump -h -S main.elf > main.lss

Creating Symbol Table: main.sym
avr-nm -n main.elf > main.sym

Size after:
AVR Memory Usage
----------------
Device: atmega8

Program: 228 bytes (2.8% Full)
(.text + .data + .bootloader)

Data: 0 bytes (0.0% Full)
(.data + .bss + .noinit)

 


 


The final few lines tell us that the program will require 228 bytes of program flash memory to store the program, and 0 bytes of SRAM memory at runtime. Note the 'cool' percentage figures - this is because the compiler knows how much memory an ATMega8 has.


Now we will edit the makefile and change the line saying 'OPT=0' to say 'OPT=s' - ie we want to change the compiler to optimise for size. Note that when we change the optimisation level then its imperative to run:
make clean
to force the files to be re-compiled with the new setting.

 


So run the makefile again - and the last part of the output should now say:-

 


 


Size after:
AVR Memory Usage
----------------
Device: atmega8

Program: 104 bytes (1.3% Full)
(.text + .data + .bootloader)

Data: 0 bytes (0.0% Full)
(.data + .bss + .noinit)

 



So our program now takes up less than HALF THE SPACE !!!!!! You may now be tempted to just add the OPT=s option to your own makefile and stop reading this tutorial. Do so at your peril. Otherwise: a lot of the SoR code will no longer run !!
You need to keep reading to find out why and how to fix it.



So the size reporting is really useful. Note how each element that makes up the size was discussed in our earlier section: ie .text, .data, .bss.
So lets make some changes to our code and see if the results are as we expected. First of all change the makefile to disable optimisation ie 'OPT = 0' and run a 'make clean'.

You may have wondered why the makefile says that the Data size is 0 bytes. The reason is that we don't have any global variables. The only variables we have are local to their methods and only exist when that method is running and are therefore stored in the stack - they dont require any memory to be permanently allocated to them.

 

So lets add in some 'dummy' global variables just to see what happens:-

 

Edit your main.c and add the two variables as follows:-

 


 


#include "main.h"



uint16_t fred = 10;
uint16_t jim = 20;

void postIt(uint32_t stuff){
}


int main(void){
    int8_t  j=0;
    int8_t i;
    for(i=0; i<100;i++){
        j+=i;
    }
    postIt(j);
    return 0;
}

 


We have added two variables. Each of them is an 'uint16_t' so require 16 bits (ie 2 bytes) each. So a total of 4 bytes.

Recompile your program. You should find that the 'Data' area now requires 4 bytes and the 'Program' has also grown by 4 bytes. This is because the variables have been given initial values. So the program has grown in order to store these initial values so they are remembered between power-ups, and the Data has grown to store the actual versions at runtime.

 

Now replace the two lines you just added for 'fred' and 'jim' with a line saying:

char buffer[256];

ie a 256 byte buffer that could, say, be used to build up lines of text to send out via a UART. Note that although we have asked for 256 bytes - we haven't actually given any value to them. So when we compile the program we notice that:

The Data area is now 256 (ie we need 256 bytes at runtime for our buffer) - but the Program area hasn't grown at all (because there are no initial values that it needs to remember).

 

This illustrates the difference between initialised and unitialised values. The motto is 'dont initialise variables if you don't need to - as it will make your program bigger'. See this example:

#include "main.h"

uint8_t sensor = 0;

int main(void){

    while(1){

        sensor = readSensor();

        ... do stuff ..

    }

}

There is no point initialising sensor with 0 as your main program assigns it a real value before it is ever read back in. By removing the initialisation your program will be slightly smaller.

 

So I suggest that you play with the various optimisation settings and see what happens. One thing I've noticed is that the 'register' keyword doesn't seem to make any difference - I guess the AVR compiler either ignores it or automatically selects the best variables to keep in registers. Which is good.