Introduction
Every now and again this subject pops up on the Arduino forum. Why does it take 1000 bytes to blink an LED? Why, oh why? It is obviously very bloated, eh?
Example: blink
Let's check that claim first.
The "blink" sketch, as shipped with the IDE (and stripped of comments) is:
int led = 13;
void setup()
{
pinMode(led, OUTPUT);
}
void loop()
{
digitalWrite(led, HIGH);
delay(1000);
digitalWrite(led, LOW);
delay(1000);
}
Compiled:
Binary sketch size: 1,084 bytes (of a 32,256 byte maximum)
OK, over 1000 bytes to "blink an LED".
Well first, as someone on the forum humorously pointed out, it doesn't take 2000 bytes to blink two LEDs!
int led1 = 13;
int led2 = 12;
void setup()
{
pinMode(led1, OUTPUT);
pinMode(led2, OUTPUT);
}
void loop()
{
digitalWrite(led1, HIGH);
delay(1000);
digitalWrite(led1, LOW);
delay(1000);
digitalWrite(led2, HIGH);
delay(1000);
digitalWrite(led2, LOW);
delay(1000);
}
That takes:
Binary sketch size: 1,140 bytes (of a 32,256 byte maximum)
So the overhead for the second LED is only:
So you could argue that it only takes 56 bytes to blink an LED (providing you are already blinking another one).
It does more than blink
The first relevant rejoinder here is that the example sketch does more than blink an LED. Rather importantly, it also waits for a second between turning the LED on and off (the "delay" function call).
If we remove that, the sketch is somewhat smaller:
int led = 13;
void setup()
{
pinMode(led, OUTPUT);
}
void loop()
{
digitalWrite(led, HIGH);
digitalWrite(led, LOW);
}
Code size:
Binary sketch size: 882 bytes (of a 32,256 byte maximum)
So we saved 202 bytes by not doing a delay. This isn't totally unreasonable, as doing delays requires the library to configure a timer, and have code to check that timer.
The pins numbers are variables
The code in the Blink example uses (wrongly in my opinion) a variable to hold the pin number (13) when a constant would do. And if you use a constant there is a library called digitalWriteFast that generates more efficient code.
https://code.google.com/p/digitalwritefast/
#include <digitalWriteFast.h>
const int led = 13;
void setup()
{
pinModeFast(led, OUTPUT);
}
void loop()
{
digitalWriteFast(led, HIGH);
digitalWriteFast(led, LOW);
}
Code size:
Binary sketch size: 470 bytes (of a 32,256 byte maximum)
Now we are down to 470 bytes to "blink an LED".
We can use port manipulation
If all we want to do is blink the LED, we can omit using setup and loop, and go straight into using the processor registers. For example:
int main ()
{
DDRB = bit (5);
while (true)
PINB = bit (5);
}
That blinks pin 13, believe it or not*. And now it only takes 178 bytes:
Binary sketch size: 178 bytes (of a 32,256 byte maximum)
* very rapidly indeed
Interrupt vectors
Well, you may be thinking "Pfft! Still 178 bytes. Why not 10 bytes?"
The answer to that lies in the way the hardware works. The Atmega328P has 26 interrupt vectors, at the start of program memory. Each one is 4 bytes (it jumps to a handler for each interrupt). So 26 * 4 = 104 bytes.
Thus there is an unavoidable overhead of 104 bytes in any piece of code. (Unless you know in advance you aren't going to use all those interrupt vectors, but this is rather specialized).
You can see these vectors if you add handlers for them:
EMPTY_INTERRUPT (INT0_vect)
EMPTY_INTERRUPT (INT1_vect)
EMPTY_INTERRUPT (PCINT0_vect)
EMPTY_INTERRUPT (PCINT1_vect)
EMPTY_INTERRUPT (PCINT2_vect)
EMPTY_INTERRUPT (WDT_vect)
EMPTY_INTERRUPT (TIMER2_COMPA_vect)
EMPTY_INTERRUPT (TIMER2_COMPB_vect)
EMPTY_INTERRUPT (TIMER2_OVF_vect)
EMPTY_INTERRUPT (TIMER1_CAPT_vect)
EMPTY_INTERRUPT (TIMER1_COMPA_vect)
EMPTY_INTERRUPT (TIMER1_COMPB_vect)
EMPTY_INTERRUPT (TIMER1_OVF_vect)
EMPTY_INTERRUPT (TIMER0_COMPA_vect)
EMPTY_INTERRUPT (TIMER0_COMPB_vect)
EMPTY_INTERRUPT (TIMER0_OVF_vect)
EMPTY_INTERRUPT (SPI_STC_vect)
EMPTY_INTERRUPT (USART_RX_vect)
EMPTY_INTERRUPT (USART_UDRE_vect)
EMPTY_INTERRUPT (USART_TX_vect)
EMPTY_INTERRUPT (ADC_vect)
EMPTY_INTERRUPT (EE_READY_vect)
EMPTY_INTERRUPT (ANALOG_COMP_vect)
EMPTY_INTERRUPT (TWI_vect)
EMPTY_INTERRUPT (SPM_READY_vect)
Now the start of the object code is:
00000000 <__vectors>:
0: 0c 94 34 00 jmp 0x68 ; 0x68 <__ctors_end>
4: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__vector_1>
8: 0c 94 52 00 jmp 0xa4 ; 0xa4 <__vector_2>
c: 0c 94 53 00 jmp 0xa6 ; 0xa6 <__vector_3>
10: 0c 94 54 00 jmp 0xa8 ; 0xa8 <__vector_4>
14: 0c 94 55 00 jmp 0xaa ; 0xaa <__vector_5>
18: 0c 94 56 00 jmp 0xac ; 0xac <__vector_6>
1c: 0c 94 57 00 jmp 0xae ; 0xae <__vector_7>
20: 0c 94 58 00 jmp 0xb0 ; 0xb0 <__vector_8>
24: 0c 94 59 00 jmp 0xb2 ; 0xb2 <__vector_9>
28: 0c 94 5a 00 jmp 0xb4 ; 0xb4 <__vector_10>
2c: 0c 94 5b 00 jmp 0xb6 ; 0xb6 <__vector_11>
30: 0c 94 5c 00 jmp 0xb8 ; 0xb8 <__vector_12>
34: 0c 94 5d 00 jmp 0xba ; 0xba <__vector_13>
38: 0c 94 5e 00 jmp 0xbc ; 0xbc <__vector_14>
3c: 0c 94 5f 00 jmp 0xbe ; 0xbe <__vector_15>
40: 0c 94 60 00 jmp 0xc0 ; 0xc0 <__vector_16>
44: 0c 94 61 00 jmp 0xc2 ; 0xc2 <__vector_17>
48: 0c 94 62 00 jmp 0xc4 ; 0xc4 <__vector_18>
4c: 0c 94 63 00 jmp 0xc6 ; 0xc6 <__vector_19>
50: 0c 94 64 00 jmp 0xc8 ; 0xc8 <__vector_20>
54: 0c 94 65 00 jmp 0xca ; 0xca <__vector_21>
58: 0c 94 66 00 jmp 0xcc ; 0xcc <__vector_22>
5c: 0c 94 67 00 jmp 0xce ; 0xce <__vector_23>
60: 0c 94 68 00 jmp 0xd0 ; 0xd0 <__vector_24>
64: 0c 94 69 00 jmp 0xd2 ; 0xd2 <__vector_25>
The "real" code has to start at 0x68 (decimal 104) which is where the reset vector takes us (the first vector).
So now, our code is really 178 - 104 bytes of useful code. That's 74 bytes to blink an LED.
However even some (most) of that is overhead, generated by the C compiler to initialize global variables, call class constructors, etc. Let's test that by blinking two LEDs.
int main ()
{
DDRB = bit (5); // pin 13
DDRB |= bit (4); // pin 12
while (true)
{
PINB = bit (5); // pin 13
PINB = bit (4); // pin 12
}
}
Code size:
Binary sketch size: 186 bytes (of a 32,256 byte maximum)
Eight bytes difference. So really, you can blink an LED in 8 bytes. That is the bottom line. And you can do it in the Arduino IDE.
The libraries are designed to make things easy
The fact is that the default behaviour of the Arduino libraries is to simplify things for beginners. Thus they do some things which take up a bit of memory;
- You can pass variables to pinMode and digitalWrite. Not just constants. Thus, at runtime, code has to convert the variable value to an actual processor port.
- You get an automatic set-up of Timer 0, so you can use millis(), micros(), and delay() function calls.
- You get Timer 1 and Timer 2 set-up, so you can do PWM output easily.
So you are trading off program space, to an extent, for ease of use. But if you want to code around it, as I did above, you can trim that program space right down.
Tests on an Arduino Uno board type, IDE 1.0.5. |