Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.
 Entire forum ➜ Electronics ➜ Microprocessors ➜ Why does it take 1000 bytes to blink one LED?

Why does it take 1000 bytes to blink one LED?

Postings by administrators only.

Refresh page


Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Mon 21 Oct 2013 07:51 AM (UTC)

Amended on Fri 19 Dec 2014 08:04 PM (UTC) by Nick Gammon

Message
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:


1140 - 1084 = 56


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.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


10,578 views.

Postings by administrators only.

Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.