101 Ways to Blink an LED on an Arduino UNO
[arduino
]
T
here is more than one way to skin a cat, it’s just that some methods are messier than other. So, inspired code from students and developers alike, I wanted to collect together the myriad of weird and wonderful ways that have been found to tackle the simple task of blinking an LED on Arduino. By no means is this comprehensive, however if you are reading this and are infuriated that your favourite method has been left out, get in touch.
Lets Blink a Light
/*
* 101 Methods to Blink an LED
* Warning: May not include 101 methods
*/
#define blink digitalWrite(LED_BUILTIN, HIGH);delay(1000);digitalWrite(LED_BUILTIN, LOW);delay(1000);
void setTimer1(uint8_t blinksPerSecond)
{
cli();
TCCR1A = 0;
TCCR1B = 0;
TCCR1B |= _BV(WGM12) | _BV(CS12) | _BV(CS10);
OCR1A = (16e6 / 1024) / blinksPerSecond;
TIMSK1 = 0;
sei();
}
void ledBlink(int a, int b)
{
for (int i = 0; i < a; ++i)
{
digitalWrite(LED_BUILTIN, HIGH);
delay(b);
digitalWrite(LED_BUILTIN, LOW);
delay(b);
}
};
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
setTimer1 (1);
}
void loop()
{
digitalWrite(LED_BUILTIN, HIGH); delay(1000); digitalWrite(LED_BUILTIN, LOW); delay(1000);
ledBlink(int a, int b)
auto blinkLed = [](int a, int b){for(int i=0; i<a; ++i){digitalWrite(13,!digitalRead(13));delay(b);}};
blinkLed(2,1000);
blink
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); delay(1000);
((digitalRead(LED_BUILTIN)) ? digitalWrite(LED_BUILTIN, LOW) : digitalWrite(LED_BUILTIN, HIGH)); delay(1000);
PORTB |= 0x20;
delay(1000);
PORTB &= ~0x20;
delay(1000);
PORTB |= (1 << PB5); delay(1000);
PORTB &= ~(1 << PB5); delay(1000);
PORTB |= _BV(PB5);
unsigned long then = millis();
while ((millis() - then) < 1000) {}
PORTB &= ~_BV(PB5);
then = millis();
while ((millis() - then) < 1000) {}
PORTB ^= 0x20; delay(1000);
PORTB ^= 32; delay(1000);
PORTB ^= 0b100000; delay(1000);
then = millis();
TIMSK1 |= (1 << OCIE1A);
while ((millis() - then) < 2000) {}
TIMSK1 = 0;
}
ISR(TIMER1_COMPA_vect)
{
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}
Explanations
The above approaches can be split into two groups, C++ solutions and Arduino solutions. C++ solutions are those that are based around code or pre-compiler trickery. Arduino solutions are those that implement specific aspects of the ATMEGA architecture, such as port manipulation and timer interrupts.
C++ Methods
By C++ methods, what I mean are any way of changing a pin’s state on the Arduino that are based around the quirks of the C++ language. So, although digitalWrite
is an Arduino function, using it in a for-loop, in another function, in a lambda function (& c…), I define that as still being grounded in C++.
Line 38: Inline Code
This method should require little explanation as it is the standard way to turn the built-in LED of an Arduino on. What was interesting about this one was the effort to format all instructions on one line, proving the lengths some will go to to make code look concise without writing any functions.
Line 19 - 28, 40: Functions
I always try and encourage students to make their code re-usable and create their own tools. So, it is no surprise that this is the least common way I have seen to LED blinking. When it is done, the author tends to sacrifice readability for brevity, in this case with the enigmatic function arguments int a, int b
. The a
argument dictates the number of blinks by setting the upper limit for a for
loop. The b
argument controls the value passed to the delay function. Functions are more utilitarian than in-line code but in this case, you just have to hope whoever is reading it already knows what the code does. It costs extra characters, but I would recommend going for the more explicit:
If you desperately need to write in-line, then you could alway use a lambda function…
Line 42: Lambda Functions
Speaking of lambda functions, this approach has cropped up once or twice, mainly by seasoned c++ students who I think were looking to show off. The down side is that readability is greatly reduced. This is similar to the vanilla function declaration with the nuance that the a
variable dictates the number of state switches (on to off or vice-versa) rather than whole blinks. So, it is more utilitarian than not writing a function. Lambda (or anonymous) functions I feel are more suited for the situations where you wan to disturb as little of the surrounding code as possible. Or, a situation where you need to use a function more than once in a contained space. For Arduino coding, most of the time it shouldn’t come up, especially for beginners.
Line 6, 45: Define Macros
I feel someone was trying to mess with me when I saw this. I am not the biggest fan of pre-compiler macros at the best of times. So, what gains there are in only having to write blink
to have the built-in LED turn on and off are lost in having to read blink
and know what is going on. This is such a messy way of writing that it is kind of impressive.
Line 47, 48: NOT and Ternary
This is very hacky, but I appreciated the brevity of it. Even though the LED_BUILTIN
has been set as OUTPUT
, you can still read it’s state. The !digitalRead(LED_BUILTIN)
basically say, get me the state of the LED_BUILTIN
pin (digitalRead(LED_BUILTIN)
) then, return the opposite value by using !
. This is the same logic for ((digitalRead(LED_BUILTIN)) ? digitalWrite(LED_BUILTIN, LOW) : digitalWrite(LED_BUILTIN, HIGH));
, except this case uses the C++ conditional (or ternary) operator. This basically evealutes the expression in brackets as boolean and then, if the result is true
the first statement (left of ?
) executes and the second statement (right of ?
) executes if it is false
.
Arduino Methods
What I define as ‘Arduino Methods’ are ways to change the state of a pin that are based around quirks and the framework of interacting with the ATMega. For the most part this is a healthy dose of Port Manipulation and usage of the ATMega’s in built timers.
Line 50 - 53: Bitwise Operators
This is one of the first methods that involve Port Manipulation. In this case, PORTB is used, which refers to pins 8 to 13. So, PORTB can be considered an 6-bit number where each bit refers to the state of a pin.
Pin | 13 | 12 | 11 | 10 | 9 | 8 |
---|---|---|---|---|---|---|
value | 32 | 16 | 8 | 4 | 2 | 1 |
So, say for instance we wanted to turn pin 13 on. Well, then we can simply say PORTB = 32
, which in binary is equivalent to 100000
. This is illustrated in the table below
Pin | 13 | 12 | 11 | 10 | 9 | 8 |
---|---|---|---|---|---|---|
value | 32 | 16 | 8 | 4 | 2 | 1 |
32 | 1 | 0 | 0 | 0 | 0 | 0 |
We can then use bitwise operators to easily flip all the bits on and off. Firstly we use the or operator which, when used with another number, will turn on any pins that are not already on.
PORTB |= 0x20
is equivalent to PORTB = PORTB | 0x20
. This will turn on pin 13, if it is not already on, without altering the state of other pins.
Lets say pins 8 and 11 were already on, PORTB |= 0x20
would result in the following:
Pin | 13 | 12 | 11 | 10 | 9 | 8 |
---|---|---|---|---|---|---|
PORTB | 0 | 0 | 1 | 0 | 0 | 1 |
0x20 | 1 | 0 | 0 | 0 | 0 | 0 |
PORTB | 0x20 | 1 | 0 | 1 | 0 | 0 | 1 |
In this case, to turn off the pins, two different operators are used, the &
(AND) operator and the ~
(NOT) operator. The &
only returns 1 if both number have a 1 in that bit.
The ~
operator returns the opposite bits to the number given
So, PORTB &= ~0x20
would result in the following, again assuming that pins 8, 11 and 13 are already on:
Pin | 13 | 12 | 11 | 10 | 9 | 8 |
---|---|---|---|---|---|---|
PORTB | 1 | 0 | 1 | 0 | 0 | 1 |
~0x20 | 0 | 1 | 1 | 1 | 1 | 1 |
PORTB & ~0x20 | 0 | 0 | 1 | 0 | 0 | 1 |
Line 55 - 61: Bitshifting
We can use a number with bitwise operators or we can shift bits to the left or right. In this instance we take one and shit it the desired number of bits to the left. Pin 13 is 5 spaces to the left so if we shift that amount to left:
This just another way to go about making a bit vector equal to 32. Perhaps a clearer version of this process is using the _BV()
macro function. _BV
is a common way of manipulating the address registers associated with certain modes or behaviour of the Arduino. The bit position of pin 13 is defined by its ATMega assignment PB5
which is equivalent to the number 5. So, writing _BV(PB5)
will return a bit vector that relates to pin 13 for PORTB, _BV(PB5) is 10000
Line 65 - 67: XOR
Rather than switching between the &
and |
operators, this example uses the bitwise XOR. This returns 0 if both compared bits match
PORTB ^= 0x20
will flip pin 13 to it’s opposite state. When the pin is not on, XOR does not match
Pin | 13 | 12 | 11 | 10 | 9 | 8 |
---|---|---|---|---|---|---|
PORTB | 0 | 0 | 1 | 0 | 0 | 1 |
0x20 | 1 | 0 | 0 | 0 | 0 | 0 |
PORTB ^ 0x20 | 1 | 0 | 1 | 0 | 0 | 1 |
When the pin is on, both bits will match and the bit will be turned off.
Pin | 13 | 12 | 11 | 10 | 9 | 8 |
---|---|---|---|---|---|---|
PORTB | 1 | 0 | 1 | 0 | 0 | 1 |
0x20 | 1 | 0 | 0 | 0 | 0 | 0 |
PORTB ^ 0x20 | 0 | 0 | 1 | 0 | 0 | 1 |
Timer Interrupts
Lines 10 - 18, 80 - 83 and 63 - 65 are involved in using the Arduino timers. The first step is in the setup, lines 10 -18:
All that happens here is the Timer1 is set to overflow every second. The uint8_t blinksPerSecond
variable is essentially a frequency in Hz of how often the timer will execute it’s function.
The timer function is defined in line 75 - 78
All this does is switch the pin to be the opposite state at the pre-defined frequency.
TIMSK1 |= (1 << OCIE1A);
or TIMSK1 |= _BV(OCIE1A);
enables the timer (line 70) and TIMSK1 = 0;
disables it again (line 72). The benefit of this method is that it will consistently execute while other functions can be written into the main loop()
. Though, it is an overly convoluted way of performing something fairly trivial.