50 Shades of Blink

Well, not really 50, but I will dive into various ways to make an LED blink, after all, if you can control an LED you can control a relay and if you can control a relay you can control the world. This article is mainly aimed at showing beginners various approaches to a specific functionality, but perhaps there will be some new stuff for the more seasoned user as well, as I will also touch upon assembly language.

Classic Blink

Everybody who is using an Arduino started with the blink sketch. if not loaded by themselves, it was probably already loaded on the arduino they got.
In all it’s simplicity it looks like this:

void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(500);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(500);                       // wait for a second
}

When compiled it uses 924 bytes or 3% of the available storage space. That is a lot for just a blinking LED. Can we do better?
Yes we can. We can use something that is called ‘port manipulation’.

Blink with Portmanipulation

If we look at the pin-out of the Arduino, we see that digital pin13 is also called ‘PB5’. That means that it is controlled by the 5th bit of a register that is called ‘PORT B’. Instead of using statements like ‘digitalWrite(13, HIGH);’ we can directly manipulate the status of that 5th bit.


There is something similar of the ‘pinMode’ command: Input and output for the arduino is controlled by another register that is called the ‘Data Direction Register’. For PORTB (that is the one we need for pin13/PB5) we use ‘DataDirectionRegisterB’ or DDRB and as we want to set it to output, we need to write a ‘1’ in that bit. We could do that by writing DDRB=”0B11111111″, but then we set D8-D13 to output (PB0-PB5) all to output and we (uselessly) write to the bits that control the bits controlling the 16Mhz crystal (PB6-PB7).
So we have to make sure we only write to PB5. There are several ways to do that, but I have chosen to ‘bitwise OR’ the DDRB register. With a bitwise OR, we leave the original content of the register intact and only set the bit HIGH that we want to be HIGH.

The bitwise OR of two bits is 1 if either or both of the input bits is 1, otherwise it is 0. In other words:

    0 | 0 == 0
    0 | 1 == 1
    1 | 0 == 1
    1 | 1 == 1


So suppose the content of the DDRB= 00000000 and we OR that with 00100000, it becomes 00100000. If originally it was 00000100 and we OR it with 00100000, it becomes: 001000100. The sign for the OR command is ‘|’.

So our pinMode(13,1); statement becomes DDRB=DDRB | 0B00100000;

We do something similar setting bit 5 of PORTB HIGH, so our digitalWrite(13,HIGH); becomes PORTB=PORTB | B00100000;

In order to set set bit5 of PORT B low again (the equivalent of digitalWrite(13,LOW);), we cannot just ‘OR’ the PORTB register with ‘0’ as that would have no effect: it leaves all ‘1”s to ‘1’. We could just write a ‘0’ to the register, that would work, but then we set all bits to ‘0’ and we might not want to do that.
For this we use another bitwise operator, the ‘bitwise AND’ (symbol &). The truth table looks like:

    0 & 0 == 0
    0 & 1 == 0
    1 & 0 == 0
    1 & 1 == 1

so suppose our PORTB register has bit 5 and bit 1 HIGH and we only want bit 5 to be set LOW, we have to ‘AND’ the PORTB register with 0B11011111. Therefore digitalWrite(13,LOW); becomes PORTB=PORTB & 0B11011111;
Now there are some ‘easier’ ways to deal with having to count zero’s and one’s. It is obvious that 0B11011111 is the inverse of 0B00100000 and as such it can be written as ~(0B00100000). it still can be ‘simpler’ or at least with less ‘counting’ we can just state that we want a ‘1’ on the position of PB5. That is written as (1<<PB5). So our statement becomes PORTB= PORTB& ~(1<<PORTB5); and that can be written in good C++ fashion like PORTB &=~(1<<PORTB5);
The program then looks like this:

void setup() {
  // initialize digital pin 13 as an output
DDRB = DDRB | 0B00100000 ; //set bit 5 to output
}

void loop() {
PORTB = PORTB | B00100000;
delay(500);
PORTB &= ~(1<<PORTB5);
delay(500);
}

//644 bytes
//gewone blink: 924 bytes (3%)
//assembler blink 488 bytes (1%)

We see that we now saved some memory as we only need 640 bytes

Can we do better? yes we can: the ‘delay(500)’ statement is one of those high order statements that might be consuming more bytes than necessary. With some caveats, we can use the _delay_ms(); function

//depending on your IDE version and setup you may need to include the following system libraries
//#include <avr/io.h>
//#include <avr/delay.h>
void setup() {
  // initialize digital pin 13 as an output
DDRB = DDRB | 0B00100000 ; //set bit 5 to output
}

void loop() {
PORTB = PORTB | B00100000;
_delay_ms(1000);
PORTB &= ~(1<<PORTB5);
_delay_ms(500);
}

Again some savings, we now only need 486 bytes

That is quite a saving compared to the original 924 bytes. The downside of using registers is that your code becomes less portable. What may work on an Atmega328 will likely not work on an Atmega8 or Atmega32.
Can we do better? Going to be hard, but let’s see if we can. How about if we forego C++ and use assembly language?

Blink in Assembly

It is possible to write assembly in the arduino IDE. You can do it in the main ino file with the inline ‘asm’ command, but I prefer to do it in a separate asm file.
For that, open the IDE and create two tabs. One called “blinkasm.ino” (or any other name you prefer) and a second one called “blink.S” (or any other name as long as the extension is a capital “S”). Like this:

Then in the ino file we only have to give some basic instructions to go to the asm file.
We start with

extern "C" {
  void start();
  void blink();
}

This basically says that we define two external procedures, called ‘start’ and ‘blink’.
Then in the startup() we just call ‘start()’ and in the loop we call ‘blink()’
The entire file then looks like this:

extern "C" {
  void start();
  void blink();
}
void setup() {
  start();
}

void loop() {
  blink();
}

we then go to the ‘blink.S’ tab and past the following code:

#define __SFR_OFFSET 0
#include "avr/io.h"

.global start
.global blink

start:
sbi DDRB,5    ;PB5as output
ret

blink:
ldi r20,250 ;  set delay in msec. Max is 255
call delay_n_ms
sbi PORTB,5
ldi r20,250
call delay_n_ms
cbi PORTB,5
ret

delay_n_ms:
ldi 31, 6400>>8    ;here we divide 6400 ovet 2 registers
ldi 30, 6400&255
delaylp:
sbiw r30,1  ;subtract immediate word -> subtract a <63 value from a 16 bit pointer register
brne delaylp
subi r20,1  ;subtract "1" from r20 and store in r20
brne delay_n_ms
ret

It is not my intention to give an entire course in AVR assembly so i will discuss the ASM program very superficially: We see 3 procedures: ‘start’ that you could compare with the ‘setup’ in arduino programming, there is ‘blink’ that is repeatedly called from the ‘loop’ in the ino tab,and there is ‘delay_n_ms’ (delay number of microsecs).

In ‘start’, the only thing we do is to set bit 5 in the DataDirectionregisterB (DDRB) and by now we know that means ‘set D13 as output’

In the blink procedure we load 250 in register 20. register 20 is a common register in the ATMEGA 328 that is available to the user,however it can only contain one byte, so we cannot put more in it than 255. For now we put in 250 and use that as a base for our 500ms delay. We then set bit 5 of PORTB, switching the LED on PB5 (=D13) on. We then call the delay function (explained later). We then load r20 again with 250 again and call the delay function again.
The delay function does the following: it creates a loop that takes 5 cycles. On a 16MHz Arduino 1ms takes 1600 cycles, so if we go through that loop 16000/5 =3200 times we have delayed 1ms and if we do it 6400 times, we have a delay of 2ms (stating the obvious). If we then repeat that entire proces 250 times (being the value in r20) we come to 500ms.

Surprisingly, the ASM code takes up a bit more space than the code in which we bitwise manipulated the registers

It uses 488 bytes as opposed to 486 bytes in the previous program.
I am fairly sure though that it is not the most optimal way to write Assembler code and we might still be able to shave a few bytes off using a proper editor, but we are probably very close to what is possible.

The downside of using Assembly is that your code becomes less portable. What may work on an Atmega328 will likely not work on an Atmega8 or Atmega32.

Non-Blocking Blink

There is one thing though that all these programs have in common that virtually makes them worthless: They are ‘blocking’ codes. that eans that as long as the LED is blinking, it is hard to have the code do anything else, so we have to come up with a non blocking way to blink the LED’s. I know that is cut and dry to anybody who spent a bit of time with the Arduino, but for completeness sake I will discuss it here. After all, novices have to learn somewhere. The only thing you ahve to do to avoid blocking code is to get rid of the delay statements and make the LED blink in another way.We can do that with timers, for which e.g. the ‘micros’ command is very handy.

#define BlinkPeriod 500  // this is in milliseconds

boolean ledState = LOW;

// here we store the last time the LED blinked
unsigned long lastTime = 0;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  unsigned long now = millis();
  if (now - lastTime >= BlinkPeriod) // this will be true every 500 milliseconds
  {
    lastTime = now;
    if (ledState == LOW)
    {
      ledState = HIGH;
    }
    else
    {
      ledState = LOW;
    }
    digitalWrite(LED_BUILTIN, ledState);
  }

  //can do other stuff here
}

What this program factually does is that it continuously compares the time the loop has been running since the LastTime the LED blinked (now-LastTime) against a preset time (‘BlinkPeriod’) and if that period is surpassed, it ‘toggles’ the LED. Toggling meaning that it just checks what th elast state of the LED was (On or OFF) and then it switches it to the other state.
Interestingly this method is more efficient -using 846 bytes- than the classic blinksketch, eventhough it has improved functionality

Ofcourse we can combine this non blocking code with some of the techniques we learned earlier, like using direct portmanipulation.

Such a sketch would look like this:

#define BlinkPeriod 500  // this is in milliseconds

boolean ledState = LOW;

// here we store the last time the LED blinked
unsigned long lastTime = 0;

void setup() {
  DDRB = DDRB | 0B00100000;
}

void loop() {
  unsigned long now = millis();
  if (now - lastTime >= BlinkPeriod) // this will be true every 500 milliseconds
  {
    lastTime = now;
    if (ledState == LOW)
    {
      ledState = HIGH;
      PORTB = PORTB | B00100000;
    }
    else
    {
      ledState = LOW;
      PORTB=PORTB& ~(1<<PORTB5);
    }
    //digitalWrite(LED_BUILTIN, ledState);
    
  }

  //can do other stuff here
}

Then we get a sketch using only 550 bytes

Classes

What else can we do if we want an LED to blink. Suppose we have a code in which we want to blink one or more LED’s from time to time. Do we have to put in that piece of blink code every time we want to blink the LED? Sure we could define a procedure that takes various parameters such as blinkperiod and LED number, that works, but actually this is a good time to use a ‘class’. A class basically is a collection of commands and variables that belong to eachother. Below I present a class called ‘Blinker’. It allows you to use several different LEDs, all with different on and off periods.

class  Blinker
{
    byte ledNr;
    long OnTime;
    long OffTime;
    boolean lampState;
    unsigned long previousMillis;
  public:
    Blinker(char nr, long on, long off)
    {
      ledNr = nr;
      OnTime = on;
      OffTime = off;
      lampState = true;
      previousMillis = 0;

    }
    void Update()
    {
      unsigned long currentMillis = millis();
      if ((lampState == true) && (currentMillis - previousMillis >= OffTime))
      {
        lampState = false;
        previousMillis = currentMillis;
        digitalWrite(ledNr,HIGH);
      }
      else if ((lampState == false) && (currentMillis - previousMillis >= OnTime))
      {
        lampState = true;
        previousMillis = currentMillis;
        digitalWrite(ledNr,LOW);
      }
    }
    void Stop()
    {
      lampState = false;
    }
};
Blinker led1(13, 1000, 400);
Blinker led2(12, 500,300);

void setup() {
  // put your setup code here, to run once:
}

void loop() {
  // put your main code here, to run repeatedly:
  
  led1.Update();
  led2.Update();
 //here your other code
}



This sketch takes 922 bytes.

So, as one can see, there are various ways to skin a cat when it comes to blinking LEDs. Keep in mind tough that if one uses any ‘delays’ in the rest of the code, that can influence the blink.