Close

AVR project doing nada = 58Bytes, and some experiments/results.

A project log for limited-code hacks/ideas

When you're running out of code-space... sometimes yah's gots to hack. Here are some ideas.

eric-hertzEric Hertz 11/26/2016 at 04:460 Comments

First, note: I'm using avr-gcc, directly, rather than going through e.g. WinAVR, or Arduino...

And be sure to check that previous log! I am *not* using stdio, as that's *huge*, but it takes some effort to make certain it's not included.

--------------

Here I've created an AVR project with nothing but the following, code-wise...

#include <avr/io.h>
#include <stdint.h>
#include <inttypes.h>

int main(void)
{
   while(1)
   {}
}

This project compiles with the following specs, output by 'avr-size'

_BUILD/minStartingPoint.elf  :
section    size      addr
.text        58         0
.data         0   8388704
.stab      1200         0
.stabstr   2993         0
.comment     17         0
Total      4268

As I understand the contest's requirements, this qualifies as 58 Bytes toward our 1kB limit.

-------

Now, what happens when we add a global-variable?

#include <avr/io.h>
#include <stdint.h>
#include <inttypes.h>

uint8_t globalVar; // = 0;

int main(void)
{
   while(1)
   {}
}

Now we get:

_BUILD/minStartingPoint.elf  :
section    size      addr
.text        74         0
.data         0   8388704
.bss          1   8388704
.stab      1212         0
.stabstr   3010         0
.comment     17         0
Total      4314
Toward the contest-requirements, I believe this qualifies as 74 Bytes toward our 1kB limit.

Note that I did not initialize the global variable... If I'd've initialized it to 0, we'd have *exactly* the same results. (Uninitialized global/static variables are always initialized to 0, per the C standard. THIS DIFFERS from *non-global*/*non-static* local-variables, which are *not* presumed to be 0 by default.)

-----------

But what happens when we initialize it to some other value?

#include <avr/io.h>
#include <stdint.h>
#include <inttypes.h>

uint8_t globalVar = 0x5a;

int main(void)
{
   while(1)
   {}
}  
_BUILD/minStartingPoint.elf  :
section    size      addr
.text        80         0
.data         2   8388704
.stab      1212         0
.stabstr   3010         0
.comment     17         0
Total      4321
NOW, note... our ".data" section has increased from 0 to 2. (and our .bss section has dropped from 1 to 0).

As I understand, the ".data" section counts toward both your RAM and ROM/Flash usage.

Why both? Because the global-variable is *initialized* to the value 0x5a. The variable itself sits in RAM, but flash-memory is necessary to store the initial-value so it can be written to the RAM at boot.

As I understand, there's a bit of code hidden from us that essentially iterates through a lookup-table writing these initial-values to sequential RAM locations, which will then become your memory-locations for your global/static variables.

Note, again, this didn't happen when the global-variable was uninitialized (or initialized to 0) because there's no need for a lookup-table to store a bunch of "0"s, sequentially. Instead, there's a separate piece of hidden-from-us code that loads '0' to each sequential RAM location used by "uninitialized" globals/statics.

SO...

As I understand, per the contest-requirements, the above example counts as 80+2 = 82 Bytes toward the 1kB limit.

-------

I'm just guessing, here, but I imagine it went to *2* rather than *1* because they indicate the end of the initialization/"lookup-table" with a "null"-character = 0... So, most-likely, if you add a second initialized global-variable the .data section will be 3 Bytes.

Let's Check:

#include <avr/io.h>
#include <stdint.h>
#include <inttypes.h>

uint8_t globalVar = 0x5a;
uint8_t globalVar2 = 0xa5;

int main(void)
{  
   while(1)
   {}
}
Well, color-me-stupid...
section    size      addr
.text        80         0
.data         2   8388704
.stab      1224         0
.stabstr   3028         0
.comment     17         0
Total      4351
.... and three?
#include <avr/io.h>
#include <stdint.h>
#include <inttypes.h>

uint8_t globalVar = 0x5a;
uint8_t globalVar2 = 0xa5;
uint8_t globalVar3 = 0xef;

int main(void)
{
   while(1)
   {}
}
section    size      addr
.text        80         0
.data         4   8388704
.stab      1236         0
.stabstr   3046         0
.comment     17         0
Total      4383
Uh Huh...!

So, maybe the init-routine handles 16-bit words at a time... might make sense, since 'int' is 16-bits.

Anyways, that's probably irrelevent.

But, do note that the ".text" section hasn't grown at all.

--------

So, again, this last example would most-likely count toward 84 Bytes of the 1kB limit.

......

The key, here, is that when you "Flash" your chip, it will flash ".text" + ".data" bytes to the flash/program memory...

So, regardless of this contest, the end-result is that even if your .text section is less than your flash-memory space (say 8190 bytes), your project still might not "fit" in your flash-memory.

I remember this being *quite confusing* when I first ran into it... so maybe this'll help save you some trouble.

.......

As far as the other sections... The ones listed here, from avr-size, don't really count, they contain stuff like debugging information that's stored in your compiled binary-file, but not written to flash.

Oh, and if I wasn't clear, ".bss," it seems, tells you the amount of RAM used by *uninitialized* global/static variables... which doesn't count toward the amount of program-memory used.

.....

Side-Note: When working with projects with limited memory, it's probably wise to *not* use many large local variables... E.G. say you've a string...

void printHello(void)
{
  char string[80] = "Hello";
  char *charPtr = &(string[0]);
  
  while(*charPtr != '\0')
  {
    uart_putChar(*charPtr);
    charPtr++;
  }
}
If you have several such functions, it might make more sense to have *one* *global* string array which can be (cautiously) reused between these functions... Otherwise, your stack can fill up quite-quickly, and stack-overflows are *really* confusing when they occur.

Similarly, wise not to use Malloc, etc.

And another plus-side of doing-so is that it shows up in your ".bss" section, so you have an idea of how much memory you're using, and how much stack is available.

------

Here's another interesting aside... I just noticed when rereading this:

Did you notice that the ".text" section increased by only 6 bytes when we changed our uninitialized global variable to an initialized one? Seems fishy... I highly doubt they can fit looping through a lookup-table in only six bytes' worth of instructions...

I wonder if they only include each of the two different initialization-routines *when needed*...

#include <avr/io.h>
#include <stdint.h>
#include <inttypes.h>

uint8_t globalVar = 0x5a;
uint8_t globalVar2 = 0xa5;
uint8_t globalVar3 = 0xef;
uint8_t uninitializedGlobalVar;

int main(void)
{
   while(1)
   {}
}
section    size      addr
.text        96         0
.data         4   8388704
.bss          1   8388708
.stab      1248         0
.stabstr   3076         0
.comment     17         0
Total      4442
Ah hah! The only change was adding of another "uninitialized" == (initialized-to-zero), global-variable, and now the ".text" size has jumped from 80 Bytes to 96 Bytes.

So it would seem that the "zeroing" routine for "uninitialized" global/static variables occupies 16 bytes, and the "lookup-table"-initialization routine occupies 22 Bytes.

Hey, wanna save a few bytes? Can you get away with converting all your global/static variables to either initialized or "uninitialized"? Might be something in there...

----------

Note: The above tests were performed on an ATmega8515...

The last-experiment shown was since run on an ATtiny861...

If anyone wonders about the differences in functionality of different systems, take a look here. Here's the result from the above test on a different processor of the same architecture:

section    size      addr
.text       100         0
.data         4   8388704
.bss          1   8388708
.stab      1248         0
.stabstr   3093         0
.comment     17         0
Total      4463
Check that... The Tiny861 requires 4 more bytes of code-space to do the exact same thing.

Is there a lesson, here? Nah... just, remember that the instruction-set may have something to do with code-space-usage. (And, maybe, if you've designed something on a Tiny AVR that *just* exceeds 1024 Bytes, then you might be able to recompile it for a Mega AVR and save a few bytes...)

Discussions