ATTiny13 — avr-gcc Hello World uses over 100 bytes?

I'm trying to write a program for the ATTiny13. My problem is that it has huge size constraints. Well, when making my first Hello World program, it took 100 bytes of program space just to make a light turn on and off! Are there any options I can give to avr-gcc to shrink this size down? Also, what is in the crt0? I'm not too keen on AVR assembly so I don't understand it much..

I do not want to have to drop to assembly for this project..

-
As a little side note, some people would call that a "Blinky"-program. –  Johan Apr 25 '10 at 6:44
@Johan well I wasn't sure how to spell "blinkenlights" –  Earlz Apr 25 '10 at 21:10
add comment

5 Answers

crt0 is the startup routine for the uC. The routines performs the setup of the registers and also the initialization of data.

Does the 100 bytes include the interrupt vector table? I am not sure about the ATtiny13 but the ATtiny25/45/85 has 15 interrupts vectors. This would take up 30 bytes.

gcc has an option to link in your crt0. You could take the AVR crt0.S file and modify it. It is not very long so it should not be difficult to do.

-
I can't seem to find the crt0 source, but in crt1 there is what seems to be an interrupt vector table. Maybe that is it –  Earlz Apr 25 '10 at 20:11
I can't find it on my system either :( I compiled all the tools from source so I thought it would be there. If you google for "crt0.S atmel" a couple of Atmel app-notes about startup, crt0 and gcc optimization come up. Maybe there are some hints in those docs. –  jluciani Apr 25 '10 at 21:58
@jlu I'm trying to figure out the difference between the two but haven't gotten any thing good on Stack Overflow yet: stackoverflow.com/questions/2709998/… –  Earlz Apr 26 '10 at 1:09
avr-libc has a different CRT for each type of AVR chip, and the standard avr-libc distros only include the .o version of the file. The one for the ATtiny13 is located at [avr-libc-path]/avr-3/lib/crttn13.o –  todbot Apr 26 '10 at 1:37
@todbot hmm. Ah, ok yea I have it in /avr-libc-1.6.7/avr/lib/avr2/attiny13/crttn13.S –  Earlz Apr 26 '10 at 3:12
show 1 more comment

What is your eventual application? An ATtiny13 has 1kB of flash and you can do a lot with that in C. The crt0 is the avr-libc C runtime. It contains things like stack handling so you can use functions with arguments and return values.

100 bytes for embedded C setup isn't too bad, and it's constant size. Doubling the lines of program logic won't necessarily make it 200 bytes. What optimization level are you compiling at? You should be at "-Os". And how are you compiling this? The Makefiles in the demo projects available from the avr-libc site are pretty good and comprehensive.

The simple LED on/off program below takes 62 bytes on an ATtiny13 with "-Os" on the avr-gcc 4.3.3. from CrossPack-AVR:

#include <avr/io.h>
#include <avr/delay.h>

int main(void)
{
DDRB |= _BV( PB3 );
while( 1 ) {
PORTB |=  _BV( PB3 );
_delay_ms(200);
PORTB &=~ _BV( PB3 );
_delay_ms(200);
}
}


Removing the _delay_ms() calls makes it 46 bytes.

A larger example on the ATtiny13 are my Smart LED prototypes. This code contains a 3-channel software PWM, an HSV-to-RGB color conversion, a state machine, and reads two buttons. It's not written particularly well and comes in at 864 bytes. Under avr-gcc 3.x it was even smaller. (for some reason avr-gcc 4 has made almost all programs grow by a few bytes)

-
avr-gcc -std=c99 -Wall -Os -mmcu=attiny13 -o hello.out helloworld.c is the relevant line in my makefile(self created). and I use almost identical code except for to flip the LED I use PORTB &= ~(1 << LED); and such –  Earlz Apr 25 '10 at 4:42
And yea, the size is constant, but even 46 bytes seems kinda heavy if all it has to do is setup a stackframe –  Earlz Apr 25 '10 at 4:43
add comment

You can use avr-objdump -d .elf to see what's being generated:

Let's analyze it a little:

[jpc@jpc ~] avr-objdump -d avr.elf | sed -e 's/^/    /' | pbcopy

avr.elf:     file format elf32-avr

Disassembly of section .text:

00000000 <__vectors>:
0:   09 c0           rjmp    .+18        ; 0x14 <__ctors_end>
2:   0e c0           rjmp    .+28        ; 0x20 <__bad_interrupt>
4:   0d c0           rjmp    .+26        ; 0x20 <__bad_interrupt>
6:   0c c0           rjmp    .+24        ; 0x20 <__bad_interrupt>
8:   0b c0           rjmp    .+22        ; 0x20 <__bad_interrupt>
a:   0a c0           rjmp    .+20        ; 0x20 <__bad_interrupt>
c:   09 c0           rjmp    .+18        ; 0x20 <__bad_interrupt>
e:   08 c0           rjmp    .+16        ; 0x20 <__bad_interrupt>
10:   07 c0           rjmp    .+14        ; 0x20 <__bad_interrupt>
12:   06 c0           rjmp    .+12        ; 0x20 <__bad_interrupt>


20 bytes interrupt vector table (at least some of the entries could be omitted if you insisted and promised you would never enable the corresponding interrupts).

00000014 <__ctors_end>:
14:   11 24           eor r1, r1
16:   1f be           out 0x3f, r1    ; 63
18:   cf e9           ldi r28, 0x9F   ; 159
1a:   cd bf           out 0x3d, r28   ; 61
1c:   02 d0           rcall   .+4         ; 0x22 <main>
1e:   05 c0           rjmp    .+10        ; 0x2a <_exit>


Clears SREG (I am not sure this is really needed), writes 0x9f (RAMEND) to SPL (the stack pointer) and jumps to main. The last rjmp is kind of redundant. (you could promise never to return from main)

00000020 <__bad_interrupt>:
20:   ef cf           rjmp    .-34        ; 0x0 <__vectors>


Default interrupt procedure for those interrupts than do not have one overwritten in C. (same rules as for __vectors)

00000022 <main>:
22:   bb 9a           sbi 0x17, 3 ; 23
24:   c3 9a           sbi 0x18, 3 ; 24
26:   c3 98           cbi 0x18, 3 ; 24
28:   fd cf           rjmp    .-6         ; 0x24 <main+0x2>


Your main proc. Tight.

0000002a <_exit>:
2a:   f8 94           cli

0000002c <__stop_program>:
2c:   ff cf           rjmp    .-2         ; 0x2c <__stop_program>


This two are not very useful. _exit is probably required by the C standard and __stop_program is needed for it to work as it should.

-
add comment

If you're short on space, try IAR's Embedded workbench - their free 'kickstart' version has a 4K word code size limit, so plenty for ATTiny's, and probably better optimisation than gcc

-
Optimization comparisons is a subject of high contention. I wouldn't go there. –  tyblu Apr 10 '11 at 0:43
@tyblu I agree, but IAR is known for producing smaller binaries than avr-gcc for instance.. I would also agree with mikeselectricstuff though and I think it's sane advice. –  Morten Jensen Mar 6 at 16:57
add comment

Devices like that are often programmed in assembler, resulting in smaller executables. It's worth making the effort and learning to use it.

-
I would agree but IMHO the goal is not to program whole devices in assembly (I know this is frequently done and I did this as well) but to be able to decode and verify what the C compiler is doing behind your back. It also means you will frequently be able to second guess the compiler and optimize the code you write in C to get small executable size. –  jpc Apr 9 '11 at 21:55
add comment