NaMaLiCS Tutorial Part 7, F_Toggle_OnOff
The full code for this lesson [A_NaMaLiCS_WorkingCode_01.ino] is attached as a zip file.
This time we're adding the toggle effect. It is similar to the blink effect in that a pixel's brightness is immediately changed from off to on and on to off. Unlike the blink effect where the on/off cycle is repeated endlessly, a toggle effect, when switched on, stays on...and when switched off, stays off. You can think of the toggle effect as a lightswitch.
The blink effect has a single function that handles both turning a pixel on and turning a pixel off. The toggle effect has two functions, one to turn a pixel on and another to turn a pixel off.
In the updated project code, everything remains the same as the previous version found in the NaMaLiCS Tutorial Supplement 3 until you get to the loop() section where calls to the two functions (toggleOn & toggleOff) are introduced.
As with the blinkPixel function, the toggleOn and toggleOff functions take two parameters. First, the number of the pixel that will get the effect and next, the delay in milliseconds before the effect starts.
However, as more effects are introduced, calling each effect on each pixel gets unwieldy, so we're going to change how effect functions get called. Instead of have a separate line for each effects function call for each pixel, we'll put it all into a nice compact for-loop.
So
blinkPixel( 1, 0 ); blinkPixel( 2, 0 ); blinkPixel( 3, 0 ); blinkPixel( 4, 0 ); blinkPixel( 5, 0 ); blinkPixel( 6, 0 ); blinkPixel( 7, 0 ); blinkPixel( 8, 0 ); blinkPixel( 9, 0 );
becomes
for( pixelNumber = 1; pixelNumber <= 9; pixelNumber++ ) { blinkPixel( pixelNumber, fxEffectStart ); }
Notice in the first set of blinkPixel function call, the pixelNumber and the effect delay are specific numbers. In the for-loop, variables are introduced so that if you make changes, you don't have to repeatedly retype a set of numbers.
Immediately after getting and storing the current time we need to add some variables that will used in all the for-loops. In this way, to make changes to the pixels and the timing of the effects, you only have to change one number. Three variables are added, pixelNumber, fxOffset and fxEffectStart.
void loop() { // Refresh the current time each time through the loop // This is used to calculate the timing needed to trigger // events or the duration of an effect currentMillis = millis(); // keeps track of which pixel is being has the effect // being applied uint8_t pixelNumber; // fxOffset determines the amount of time between pixel events // within an effect and the amount of time between effects uint32_t fxOffset = 500; // This keeps track of when a pixel effect will start uint32_t fxEffectStart = 0;
The effects sequence we're going to set up is:
1) 3 seconds of blinking pixels
2) toggle off all the pixels simultaneously
3) sequentially toggle on the pixels
4) sequentially toggle off the pixels
5) sequentially toggle on the pixels again
6) sequentially toggle off the pixels in reverse order
The first for-loop starts the blink effect for all 9 pixels in the BrickPixel testbed. Then the effects start time is updated to 3 seconds and that becomes the effects delay for the next effect, toggling all the pixels off simultaneously. The effects start time is updated again to be a half second later, when the next effects toggleOn for-loop starts.
Compare the blinkPixel for-loop:
// Have all nine pixels in a BrickPixel 9-LED Controller // blink and the blink effect starts immediately. for( pixelNumber = 1; pixelNumber <= 9; pixelNumber++ ) { blinkPixel( pixelNumber, fxEffectStart ); }
and the toggleOn for-loop:
// Sequentially turn on each pixel starting at pixel 1 // and going to pixel 9 for( pixelNumber = 1; pixelNumber <= 9; pixelNumber++ ) { toggleOn( pixelNumber, fxEffectStart + (pixelNumber * fxOffset) ); }
Everything is the same except for the function call, blinkPixel vs. toggleOn, and the second parameter of the function call...the effects delay. In the blinkPixel, the effects delay is the same for each pixelNumber. In the toggleOn, the effects delay gets incremented by the value of fxOffset each time through the for-loop. That means the effect on each pixel starts at a time later than the previous pixel. So if the effect starts at time 0 for the first pixel and the fxOffset is equal to 500, then the second pixel starts at time 500 milliseconds, and the third starts at 1000 milliseconds and the fourth at 1500 milliseconds and so on.
If you look at the rest of the effects for-loops you will see each uses the same offset calculations. However, what makes each effects for-loop unique is updating the fxEffectStart value between each for-loop. The updated value takes into account how long it took for an effect to complete for all the pixels. After three seconds of blinking, all the pixels are turned off simultaneously, so there is a negligible amount of time used.
However for the first toggleOn sequence, each pixel is turned on 500 milliseconds after the previous pixel was toggled. That means the entire sequence of 9 pixels, each taking 500 milliseconds to complete and makes the effects total time 4500 milliseconds. If you start a subsequent pixel effect before the 4500 milliseconds has elapsed, the next effect will interfere with the current effect...and really strange things happen. Sometimes, they are very interesting.
So between each effect for-loop the fxEffectStart value gets updated to include the time it took for the previous for-loop to finish.
There is a GOTCHA here in calculating the new fxEffectStart value. The way these for-loops work is that a variable, pixelNumber, is given an initial value, then compared to an end condition, if the end condition isn't met, the variable is incremented and then compared to the end condition again. The loop continues until the end condition is true. In this case, the end condition it true when pixelNumber is greater than nine...10, in this case.
But the actual number of times through the for-loop is 9, not 10. So the actual amount of time taken for the pixel effect is 9 * fxOffset. Since coming out of the for-loop pixelNumber equals 10, we need to decrement the value by 1 to get the actual number of times the loop was implemented. That give us fxEffectStart += (pixelNumber - 1) * fxOffset;
// After previous pixelEffect is finished, update the // start time for the next effect. Because the effect // was applied to the individual pixels sequentially, // the new start time is number of pixels times the fxOffset. // // REMEMBER: in the previous for loop, the pixelNumber has // been incremented one more time, which causes the for // condition to fail. Because of that, the pixelNumber // needs to be decremented by one. fxEffectStart += (pixelNumber - 1) * fxOffset;
Now let's dig into the toggleOn / toggleOff functions.
Comparing the blinkPixel and the toggleOn code you can see they are mostly the same. The same template is also used for the flicker and fade effects. [differences are highlighted in bold]
The core of the blink function is flipping the state of the pixel. Doesn't make any difference if it's on or off, what ever state the pixel is in, change it to the opposite state.
// Change the state of the three pixels if( pixelNumber == 0 ) pixel01 ^= preferredBrightness; if( pixelNumber == 1 ) pixel02 ^= preferredBrightness; if( pixelNumber == 2 ) pixel03 ^= preferredBrightness;
The core of the toggle functions is checking whether a pixel is on or off then switching its state.
// Check each pixel to see whether it's at minimum brightness // if so, set it to preferredbrightness if( (pixelNumber == 0) && (pixel01 < preferredBrightness) ) pixel01 = preferredBrightness; if( (pixelNumber == 1) && (pixel02 < preferredBrightness) ) pixel02 = preferredBrightness; if( (pixelNumber == 2) && (pixel03 < preferredBrightness) ) pixel03 = preferredBrightness;
And...that's it. Very simple.
Next up, how real streetlights work...G_FADE_OnOff.
-wrtyler-