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..

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

5 Answers 5


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.

  • \$\begingroup\$ 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 \$\endgroup\$
    – Earlz
    Apr 25, 2010 at 20:11
  • \$\begingroup\$ 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. \$\endgroup\$
    – jluciani
    Apr 25, 2010 at 21:58
  • \$\begingroup\$ @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/… \$\endgroup\$
    – Earlz
    Apr 26, 2010 at 1:09
  • 2
    \$\begingroup\$ 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 \$\endgroup\$
    – todbot
    Apr 26, 2010 at 1:37
  • \$\begingroup\$ @todbot hmm. Ah, ok yea I have it in /avr-libc-1.6.7/avr/lib/avr2/attiny13/crttn13.S \$\endgroup\$
    – Earlz
    Apr 26, 2010 at 3:12

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.


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 );
        PORTB &=~ _BV( PB3 );

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)

  • \$\begingroup\$ 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 \$\endgroup\$
    – Earlz
    Apr 25, 2010 at 4:42
  • \$\begingroup\$ And yea, the size is constant, but even 46 bytes seems kinda heavy if all it has to do is setup a stackframe \$\endgroup\$
    – Earlz
    Apr 25, 2010 at 4:43

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

  • 1
    \$\begingroup\$ Optimization comparisons is a subject of high contention. I wouldn't go there. \$\endgroup\$
    – tyblu
    Apr 10, 2011 at 0:43
  • 1
    \$\begingroup\$ @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. \$\endgroup\$ Mar 6, 2013 at 16:57

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

  • 1
    \$\begingroup\$ 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. \$\endgroup\$
    – jpc
    Apr 9, 2011 at 21:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.