You already have some perfectly good answers. I am posting this only to
share some stats I did one day I asked myself the same sort of
questions: What is taking so much space on a minimal sketch? What is the
minimum needed to achieve the same functionality?
Below are three versions of a minimal blinky program that toggles the
LED on pin 13 every second. All three versions have been compiled for an
Uno (no USB involved) using avr-gcc 4.8.2, avr-libc 1.8.0 and
arduino-core 1.0.5 (I do not use the Arduino IDE).
First, the standard Arduino way:
const uint8_t ledPin = 13;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
digitalWrite(ledPin, HIGH);
delay(1000);
digitalWrite(ledPin, LOW);
delay(1000);
}
This compiles to 1018 bytes. Using both avr-nm
and
disassembly,
I broke down that size into individual functions. From
largest to smallest:
148 A ISR(TIMER0_OVF_vect)
118 A init
114 A pinMode
108 A digitalWrite
104 C vector table
82 A turnOffPWM
76 A delay
70 A micros
40 U loop
26 A main
20 A digital_pin_to_timer_PGM
20 A digital_pin_to_port_PGM
20 A digital_pin_to_bit_mask_PGM
16 C __do_clear_bss
12 C __init
10 A port_to_output_PGM
10 A port_to_mode_PGM
8 U setup
8 C .init9 (call main, jmp exit)
4 C __bad_interrupt
4 C _exit
-----------------------------------
1018 TOTAL
In the list above, the first column is the size in bytes, and the second
column tells whether the code comes from the Arduino core library (A,
822 bytes total), the C runtime (C, 148 bytes) or the user (U, 48
bytes).
As can be seen in this list, the largest function is the routine
servicing the timer 0 overflow interrupt. This routine is responsible of
tracking time, and is needed by millis()
, micros()
and delay()
.
The second largest function is init()
, which sets the hardware timers
for PWM, enables TIMER0_OVF interrupt and disconnects the USART (that
was used by the bootloader). Both this and the previous function are
defined in
<Arduino directory>/hardware/arduino/cores/arduino/wiring.c
.
Next is the C + avr-libc version:
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRB |= _BV(PB5); /* set pin PB5 as output */
for (;;) {
PINB = _BV(PB5); /* toggle PB5 */
_delay_ms(1000);
}
}
The break-down of the individual sizes:
104 C vector table
26 U main
12 C __init
8 C .init9 (call main, jmp exit)
4 C __bad_interrupt
4 C _exit
----------------------------------
158 TOTAL
This is 132 bytes for the C runtime and 26 bytes of user code, including
the inlined function _delay_ms()
.
It could be noted that, since this program does not use interrupts,
the interrupt vector table is not needed, and regular user code
could be put in its place. The following assembly version does exactly
that:
#include <avr/io.h>
#define io(reg) _SFR_IO_ADDR(reg)
sbi io(DDRB), 5 ; set PB5 as output
loop:
sbi io(PINB), 5 ; toggle PB5
ldi r26, 49 ; delay for 49 * 2^16 * 5 cycles
delay:
sbiw r24, 1
sbci r26, 0
brne delay
rjmp loop
This is assembled (with avr-gcc -nostdlib
) into only 14 bytes,
most of which are used to delay the toggles so that the blink is
visible. If you remove that delay loop, you end up with a 6-byte
program that blinks too fast to be seen (at 2 MHz):
sbi io(DDRB), 5 ; set PB5 as output
loop:
sbi io(PINB), 5 ; toggle PB5
rjmp loop