LED Flash Patterns...for the last time - Part 4
This will be (I think) the last post in the exploration of LED pattern control. I’m going to build on the previous post to explore how we might include brightness in the design, as this was one of the features that would have been delivered if we had used “The Electrical Engineer Way”.
There are two main designs that spring to mind here (nevermind there are many ways to achieve them):
- A design where the brightness control is encoding / related to the flash pattern definition.
- A design where the brightness control affects the flash patterns, but is completely orthogonal.
Patterns that are agnostic to the implemenation
The whole goal of this series of posts has been to provide a product manager friendly way to describe the flash patterns. (Cross-promo: this is similar the objective behind my docopt-uc library, the justifications there are much the same). With that in mind, it is best to first show and explain the patterns I am going to use for both examples.:
The setup()
of both implementations will include the following code:
// Register the patterns against the states
LedMgr_addPattern(FOUR_TO_THE_FLOOR, "R--g--g--g--", tricolorDrive, &leds);
LedMgr_addPattern(SWUNG_BEAT, "RRR r G ", tricolorDrive, &leds);
LedMgr_addPattern(THREE_FOUR, "RrrrGgggYyyy", tricolorDrive, &leds);
Although this product manager has not been very consistent (using spaces and hyphens….tisk tisk), I think it is pretty clear that they want something where:
“Four to the floor” will have a RED GREEN GREEN GREEN pattern, with all flashes being short, and possibly with an emphasis on the first light.
“Swung beat” will have a RED RED GREEN, with the first red being a long flash, and possibly with the second red having less emphasis.
“Three / four” will have a RED GREEN YELLOW, with no gaps between the flashes, and possibly with the start of each colour having more emphasis.
Additionally, it is pretty easy for us to implement a driver function that tolerates both cases easily, and can treat anything that is not a supported character as OFF (i.e. ‘ ‘, ‘-’, ‘_’, ‘~‘).
A combined / in-band implementation
For example, if we wanted to treat uppercase letters as bright and lowercase letters as dim, we could perhaps write our driver function to utilise Arduino’s pin-wise analogWrite() function like this:
// The ascii characters are arranged in a way such that the uppercase and
// lowercase characters only have the 6th bit set (to mean "lowercase").
// http://www.asciitable.com makes this quite clear. We are going to utilise
// this to simplify our character checks by first forcing the provided char
// to be lowercase (by ORing with this bitmask, i.e. by setting the 6th bit),
// and then checking just this bit when determining the brightness.
enum {
LOWERCASE_BIT = 0x20,
};
/**
* Drive the provided pin to the provided pattern element, but ignore the case
* of each char.
*
* This also controls the brightness, by looking at the case of the char.
*
* @param color the pattern element
* @param arg a pointer to a tricolour_led_module
*/
int tricolorDrive(char color, void *arg) {
tricolour_led_module *pin = (tricolour_led_module *)arg;
int level = 250;
// check if the 6th bit is set using bitwise AND
if (color & LOWERCASE_BIT) {
level = 10;
}
if ((color | LOWERCASE_BIT) == 'r') {
analogWrite(pin->red, level);
analogWrite(pin->green, 0);
analogWrite(pin->yellow, 0);
} else if ((color | LOWERCASE_BIT) == 'g') {
analogWrite(pin->red, 0);
analogWrite(pin->green, level);
analogWrite(pin->yellow, 0);
} else if ((color | LOWERCASE_BIT) == 'y') {
analogWrite(pin->red, 0);
analogWrite(pin->green, 0);
analogWrite(pin->yellow, level);
} else {
analogWrite(pin->red, 0);
analogWrite(pin->green, 0);
analogWrite(pin->yellow, 0);
}
}
The full INO file can be seen here and if you load it in to an arduino with LEDs hooked up to pins 11, 3 and 5, you might see something like this video.
Sure, it’s strange that we have to use these particular pins, but the Arduino libraries are doing a lot for us here, setting up PWMs with different duty cycles on different pins etc etc. Obviously if this were going in a product we wanted to sell we would be interested in knowing exactly what was happening, but we could easily achieve the same result.
Orthogonal control
This example is somewhat more complicated, as we have to setup a whole separate timer and ISRs etc to manage our own bit-bang PWM for brightness control. This is identical to the method used the post on Arduino Yun Timer 3 PWM
In this example we use our bit-bang PWM to control the brightness, and we change this setting every time through the loop()
function by switching from a bright value to a dim value and back again constantly (NB: LOOP_DELAY is 3000, or 3 seconds).
void loop() {
delay(LOOP_DELAY);
static int loops = 0;
// Every loop switch the brightness
if (brightness == 250) {
brightness = 10;
} else {
brightness = 250;
}
...etc
}
Every third loop we switch the pattern that is being displayed:
// Every 3 loops, switch the pattern
if (loops % 3 == 0) {
static long nextBeat = 0;
switch (nextBeat) {
case 0:
LedMgr_activate(FOUR_TO_THE_FLOOR);
Serial.println("four to the floor");
nextBeat = 1;
break;
case 1:
LedMgr_activate(SWUNG_BEAT);
Serial.println("feel the swing");
nextBeat = 2;
break;
default:
LedMgr_activate(THREE_FOUR);
Serial.println("3/4 beat");
nextBeat = 0;
break;
}
}
loops++;
However, the driver function is a little different for this implementation. Instead of switching the pin value to be HIGH or LOW (like in the PWM post), this implementation switches the port direction to be an INPUT or OUTPUT. This has the effect of acting like a gate that either allows the PWM’ed signal out or not.
So what exactly happens? Well:
- If the port direction is INPUT, the pin acts like it is OFF (mostly due to whatever the default pull-up / pull-down / hi-impedance settings of the pin…but we could alway add more circuitry / design here to cope in a real product).
- If the port direction is OUTPUT, then the pin does whatever the big-bang PWM is outputting on the pin, i.e.
- If the duty is high (250 / 255), then the LED is bright
- If the duty is low (10 / 255), then the LED is dim
Whether this is a good idea, and/or whether this is possible in another chip / a more serious product is a question for another day….for our purposes it works fine.
As a result, the driver function looks like this:
/**
Drive the provided pin to the provided pattern element, but ignore the case
of each char.
This controls the ON-ness by modulating the pin direction.
@param color the pattern element
@param arg a pointer to a tricolour_led_module
*/
int tricolorDrive(char color, void *arg) {
tricolour_led_module *pin = (tricolour_led_module *)arg;
pinMode(pin->red, (color | LOWERCASE_BIT) == 'r' ? OUTPUT : INPUT);
pinMode(pin->green, (color | LOWERCASE_BIT) == 'g' ? OUTPUT : INPUT);
pinMode(pin->yellow, (color | LOWERCASE_BIT) == 'y' ? OUTPUT : INPUT);
}
The full INO file can be seen here. I used the same pins (11, 3 and 5) as the other sketch just to keep them similar. This implementation does not use analogWrite()
so they could be any pins. If you load it in to an arduino you might see something like this video.
Summary
I think that is enough of blinky lights, so I don’t think I’ll write any more posts about them. The next steps would probably be to create a module that can have any number of “LED Modules” loaded, which themselves can have any number of patterns loaded (within the limits of memory of course), but I guess that can be an exercise for the readers (if there are any).