How to Flicker an LED
In the last tutorial we covered how not to blink an LED, where I explained what was wrong with the most common blink example and showed a much more elegant solution. We now have an LED that switches between 'Off' and 'Blindingly Bright', there has to be another way that gives us more control over the LED, reduces the brightness (and therefore power consumption) and that could also give us a bit more 'character' for our lights.
The answer is Pulse Width Modulation, or PWM for short. By switching the LED power on and off, imperceptibly quickly we can control the average amount of Voltage that is supplied to the LED, which in turn reduces the current and brightness of the LED. If we switch the power on and off faster than the LED can react, the LED will receive an average Voltage that is proportional to the ratio between on and off. If the LED is on for half the time and off for half the time it will receive half of the full voltage.
#include <FastLED.h> | |
const uint8_t ledPin = LED_BUILTIN; | |
const int interval = 1000; | |
bool ledState = false; | |
uint8_t loopCount = 0; | |
uint8_t brightness = 20; // Set the led for half brightness. | |
void setup() { | |
pinMode(ledPin, OUTPUT); // Initialise the LED pin as an output | |
} | |
void loop() { | |
// Every second turn the LED On/Off | |
EVERY_N_MILLISECONDS(interval) { | |
ledState = !ledState; // Toggle the state of the LED | |
} | |
// Control the PWM by for the LED | |
if(loopCount == 0) { | |
digitalWrite(ledPin, ledState); // Turn the LED on, if required | |
} | |
else if(loopCount == brightness) { | |
digitalWrite(ledPin, HIGH); // Turn the LED off | |
} | |
loopCount++; // Increment the counter and let it automatically roll over to zero | |
} |
I set up a single byte to use as a counter and I add one to this value at the end every single loop. When this counter gets to the maximum value of 255 it automatically rolls over to 0 and the count starts again. When the count value is actually zero I turn the LED on and when the count matches the desired brightness I turn the LED off. This software implemented PWM makes the LED visibly dimmer but has the downside that it can use a lot of software time to achieve this. If the main body of the program had a lot of other things to do the LED may not be switched frequently or consistently enough.
void loop() { | |
// Every second turn the LED On/Off | |
EVERY_N_MILLISECONDS(interval) { | |
ledState = !ledState; // Toggle the state of the LED | |
if(ledState) { | |
analogWrite(ledPin, 255 - brightness); // Turn the led on | |
} else { | |
analogWrite(ledPin, 255); // Turn the led off | |
} | |
} | |
} |
Most modern microcontrollers have hardware peripheral built into them to create this PWM square wave without using any additional processing power. The ESP8266 can output PWM on any of it's pins but the basic Uno can only output on a few pins so you need to check to see that your LED is connected to a PWM capable pin. Instead of using a loopCounter to keep track, I'm able to pull all of the code back inside the EVERY_N_MILLISECONDS block. The analogWrite function tells the processor that it should create a square wave on the selected pin and also tells it how long the pin should be on for (Don't forget that for the built in led, 5V is off and 0V is on)
Now that we've established that analogWrite is better than digitalWrite for controlling our single LED we can adjust all the code to give us a lot more control over the LED behaviour. To do this we need to define a series of states that the LED can be in and a variable to keep track of which one is currently selected. We also need a new variable to keep track of a value for the LED, this value will mean different things in different states.
enum class ledModes { Off, | |
On, | |
PWM, | |
Flicker, | |
Blink}; | |
ledModes ledMode = ledModes::Off; | |
uint8_t ledValue = ledON; | |
The interesting block of code here is the 'enum class', we're using this to define a type of state variable and list all of it's states. In our case a list of all the possible ledModes, Off, On, PWM, Flicker, Blink and I've defined them here in order of complexity. You can see that the next line creates a variable to store the ledMode but it can declared as an ledModes type so it knows that the value should be one of those five states. In fact it doesn't matter what the actual number of the state is, we can let the compiler worry about that and just refer to it by name from now on.
We're going to create a whole new function called 'controlLights()' to work out what to change and when to change it and we're going to call this from within our millisecond block. A subtle change is that instead of calling the block once per second, we're going to drop that down to every 25 milliseconds to make it a bit quicker to respond to changes.
#define INTERVAL 25 | |
const uint8_t ledMAX = 32; | |
const uint8_t ledMIN = 0; | |
void setup() { | |
pinMode(ledPin, OUTPUT); // Initialise the LED pin as an output | |
ledMode = ledModes::Flicker; // Set the kind of behaviour from the LED | |
} | |
void loop() { | |
EVERY_N_MILLISECONDS(INTERVAL) { | |
controlLights(); | |
} | |
} | |
void controlLights(void) { | |
switch (ledMode) { | |
case ledModes::Off: | |
ledValue = ledMIN; // Turn the PWM all the way off | |
break; | |
case ledModes::On: | |
ledValue = ledMAX; // Set the PWM to the max brightness | |
break; | |
case ledModes::PWM: | |
// Nothing needs to be done for PWM | |
break; | |
case ledModes::Flicker: | |
ledValue = random(ledMAX); // Randomly select a value for the brightness | |
break; | |
} | |
analogWrite(ledPin, 255 - ledValue); | |
} |
At the end of the controlLights function is a single line that sends the ledValue to the ledPin as a PWM signal, this means the rest of the code needs to calculate what that value should be before the end of the function. The value will be different depending on the ledMode so the first thing is a giant switch statement to divert into all the different possible modes.
- When the ledMode is Off, set the ledValue to the minimum amount, zero, or fully off.
- When the ledMode is On, set the ledValue to the maximum desired amount. I've defined this as 32 to avoid the blindingly bright problem again.
- The PWM mode is used to set the led to a constant value that isn't the min or max, ledValue should be set before switching into this mode and it never needs to change.
- The new mode of Flicker randomly changes the brightness every loop and it creates an effect that looks like the LED is failing. The random value should be less than the maximum value.
case ledModes::Blink: | |
// Check how many loops have elapsed and toggle on/off every second | |
if(loopCount > 40) { | |
ledValue = ledON - ledValue; | |
loopCount = 0; | |
} | |
break; |
The full code for this and all the other tutorials in this series can be found in my github repository.
https://github.com/msraynsford/Puzzling/