Created
February 19, 2019 03:52
-
-
Save resnbl/c533adb328909faef620ffa562ad9dd1 to your computer and use it in GitHub Desktop.
Arduino code for Christmas tree-topper star
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Define macros to enable/disable debugging output through the Serial monitor: | |
You must: | |
#define DEBUG | |
before including this file to enable debug output. | |
If you place this file in a folder named "Debug_Macros" in your Arduino | |
libraries folder, it won't need to be copied into every project. | |
*/ | |
#ifndef __DEBUG_MACROS_H__ | |
#define __DEBUG_MACROS_H__ | |
#ifdef DEBUG | |
#define debugBegin(baud) Serial.begin(baud) | |
#define debug(x) Serial.println(x) | |
#define debugIt(x) Serial.print(x) | |
#define debugLabel(l, v) Serial.print(l); Serial.print(":\t"); Serial.println(v) | |
#else | |
#define debugBegin(baud) | |
#define debug(x) | |
#define debugIt(x) | |
#define debugLabel(l, v) | |
#endif | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <FastLED.h> | |
#include <JC_Button.h> | |
//#define DEBUG // uncomment to enable debug on Serial monitor | |
#include "Debug_Macros.h" | |
/* | |
This program is an amalgamation of code published as: | |
TwinkleFOX - Mark Kriegsman - https://gist.github.com/kriegsman/756ea6dcae8e30845b5a | |
LedString - Doug Leary - https://github.com/DougLeary/LedString | |
FastLED - https://github.com/FastLED/FastLED | |
JC_Button - Jack Christensen - https://github.com/JChristensen/JC_Button | |
The basic TwinkleFOX code has been modified to support the "flame simulation" from LedString, | |
which is not "palette-based" and therefore cannot simply be added as a new palette choice. | |
I took this approach because I like the appearance of this effect on a small ring of WS2812Bs | |
much better than others such as Fire2012 that work better with larger strips of WS2811s. | |
This code supports an optional push button to control the animations. | |
A single press "cycles" to the next animation in the list. | |
A long press (> 1 sec) toggles "cycling", either sticking to the current animation, | |
or resuming the cycle operation. The display will fade out while the button is pressed | |
to signal the change in cycling mode has been recognized. | |
Bob Shields (resnbl) - Feb 2019 | |
*/ | |
#if defined(FASTLED_VERSION) && (FASTLED_VERSION < 3001000) | |
#warning "Requires FastLED 3.1 or later; check github for latest code." | |
#endif | |
// Modified for 16-pixel ring powered off Arduino USB port | |
#define NUM_LEDS 16 | |
#define LED_TYPE WS2812B | |
#define COLOR_ORDER GRB | |
#define DATA_PIN 6 | |
//#define CLK_PIN ? | |
#define VOLTS 5 | |
#define MAX_MA 500 | |
#define BTN_PIN 4 // if no button defined: always cycle animations | |
// TwinkleFOX: Twinkling 'holiday' lights that fade in and out. | |
// Colors are chosen from a palette; a few palettes are provided. | |
// | |
// This December 2015 implementation improves on the December 2014 version | |
// in several ways: | |
// - smoother fading, compatible with any colors and any palettes | |
// - easier control of twinkle speed and twinkle density | |
// - supports an optional 'background color' | |
// - takes even less RAM: zero RAM overhead per pixel | |
// - illustrates a couple of interesting techniques (uh oh...) | |
// | |
// The idea behind this (new) implementation is that there's one | |
// basic, repeating pattern that each pixel follows like a waveform: | |
// The brightness rises from 0..255 and then falls back down to 0. | |
// The brightness at any given point in time can be determined as | |
// as a function of time, for example: | |
// brightness = sine( time ); // a sine wave of brightness over time | |
// | |
// So the way this implementation works is that every pixel follows | |
// the exact same wave function over time. In this particular case, | |
// I chose a sawtooth triangle wave (triwave8) rather than a sine wave, | |
// but the idea is the same: brightness = triwave8( time ). | |
// | |
// Of course, if all the pixels used the exact same wave form, and | |
// if they all used the exact same 'clock' for their 'time base', all | |
// the pixels would brighten and dim at once -- which does not look | |
// like twinkling at all. | |
// | |
// So to achieve random-looking twinkling, each pixel is given a | |
// slightly different 'clock' signal. Some of the clocks run faster, | |
// some run slower, and each 'clock' also has a random offset from zero. | |
// The net result is that the 'clocks' for all the pixels are always out | |
// of sync from each other, producing a nice random distribution | |
// of twinkles. | |
// | |
// The 'clock speed adjustment' and 'time offset' for each pixel | |
// are generated randomly. One (normal) approach to implementing that | |
// would be to randomly generate the clock parameters for each pixel | |
// at startup, and store them in some arrays. However, that consumes | |
// a great deal of precious RAM, and it turns out to be totally | |
// unnessary! If the random number generate is 'seeded' with the | |
// same starting value every time, it will generate the same sequence | |
// of values every time. So the clock adjustment parameters for each | |
// pixel are 'stored' in a pseudo-random number generator! The PRNG | |
// is reset, and then the first numbers out of it are the clock | |
// adjustment parameters for the first pixel, the second numbers out | |
// of it are the parameters for the second pixel, and so on. | |
// In this way, we can 'store' a stable sequence of thousands of | |
// random clock adjustment parameters in literally two bytes of RAM. | |
// | |
// There's a little bit of fixed-point math involved in applying the | |
// clock speed adjustments, which are expressed in eighths. Each pixel's | |
// clock speed ranges from 8/8ths of the system clock (i.e. 1x) to | |
// 23/8ths of the system clock (i.e. nearly 3x). | |
// | |
// On a basic Arduino Uno or Leonardo, this code can twinkle 300+ pixels | |
// smoothly at over 50 updates per seond. | |
// | |
// -Mark Kriegsman, December 2015 | |
CRGBArray<NUM_LEDS> leds; | |
// Overall twinkle speed. | |
// 0 (VERY slow) to 8 (VERY fast). | |
// 4, 5, and 6 are recommended, default is 4. | |
#define TWINKLE_SPEED 4 | |
// Overall twinkle density. | |
// 0 (NONE lit) to 8 (ALL lit at once). | |
// Default is 5. | |
#define TWINKLE_DENSITY 5 | |
// How often to change color palettes. | |
#define SECONDS_PER_PALETTE 20 | |
// Also: toward the bottom of the file is an array | |
// called "ActivePaletteList" which controls which color | |
// palettes are used; you can add or remove color palettes | |
// from there freely. | |
// Background color for 'unlit' pixels | |
// Can be set to CRGB::Black if desired. | |
CRGB gBackgroundColor = CRGB::Black; | |
// Example of dim incandescent fairy light background color | |
// CRGB gBackgroundColor = CRGB(CRGB::FairyLight).nscale8_video(16); | |
// If AUTO_SELECT_BACKGROUND_COLOR is set to 1, | |
// then for any palette where the first two entries | |
// are the same, a dimmed version of that color will | |
// automatically be used as the background color. | |
#define AUTO_SELECT_BACKGROUND_COLOR 0 | |
// If COOL_LIKE_INCANDESCENT is set to 1, colors will | |
// fade out slighted 'reddened', similar to how | |
// incandescent bulbs change color as they get dim down. | |
#define COOL_LIKE_INCANDESCENT 0 | |
CRGBPalette16 gCurrentPalette; | |
CRGBPalette16 gTargetPalette; | |
////////// Flicker setup | |
// min/max brightness range of normal and intense flickers | |
// For NeoPixels (these generally *are* WS2812Bs..., but these settings work best to my eye) | |
#define FIRE_MIN 80 | |
#define FIRE_MAX 160 | |
#define FLICKER_MIN 70 | |
#define FLICKER_MAX 70 | |
// For WS2811, WS2812B, WS2813 (better for strips instead of rings?) | |
//#define FIRE_MIN 150 | |
//#define FIRE_MAX 190 | |
//#define FLICKER_MIN 130 | |
//#define FLICKER_MAX 225 | |
#define FLICKER_RATE 80 // mSec between flickers | |
#define FLICKER_EXTRA 2 // when brightness is this close to FIRE_MIN or FIRE_MAX, use FLICKER_MIN or FLICKER_MAX instead | |
#define PIXEL_FRACT 3 // # pixels to flicker: 1 = all, 2 = half, 3 = third (rest are off) | |
// note that 1/3 of 16 == 5: one for every arm of a 5-pointed star! | |
#define FLAME_HUE 22 // HSV hue for flames (orange-ish red) | |
////////// Button control | |
#ifdef BTN_PIN | |
// Button presses change animations | |
#define LONG_PRESS_MS 1000L // min mSec for "long" button press | |
Button btn(BTN_PIN, 40); // change if external resistor used | |
#endif | |
void setup() { | |
delay(2000); // safety startup delay | |
debugBegin(57600); // if debugging, init Serial port | |
FastLED.setMaxPowerInVoltsAndMilliamps(VOLTS, MAX_MA); | |
FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip); | |
// FastLED.addLeds<LED_TYPE,DATA_PIN,CLK_PIN,COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip); | |
#ifdef BTN_PIN | |
btn.begin(); // init button | |
#endif | |
} | |
void loop() | |
{ | |
static uint8_t twinkling = chooseNextColorPalette(gTargetPalette); // 0 -> flicker, else twinkle pattern index | |
static bool cycling = true; // cycle patterns automatically | |
static bool pressing = false; // button long-press in progress | |
EVERY_N_SECONDS( SECONDS_PER_PALETTE ) { | |
if (cycling) // move to next pattern | |
twinkling = chooseNextColorPalette( gTargetPalette ); | |
} | |
EVERY_N_MILLISECONDS( 10 ) { | |
if (twinkling) | |
nblendPaletteTowardPalette( gCurrentPalette, gTargetPalette, 12); | |
} | |
#ifdef BTN_PIN | |
// Check the push button: short press = incr pattern, long = toggle cycling | |
btn.read(); | |
if (btn.wasReleased()) | |
{ | |
if (pressing) // if was a long one, toggle cycling | |
{ | |
pressing = false; | |
cycling = !cycling; | |
debugLabel("cycling", cycling); | |
} | |
else // short: incr pattern | |
{ | |
debug("Next palette..."); | |
twinkling = chooseNextColorPalette( gTargetPalette ); | |
} | |
} | |
else if (btn.pressedFor(LONG_PRESS_MS)) // long-press in progress? | |
{ | |
pressing = true; | |
} | |
#endif | |
if (pressing) | |
fadeOut(); | |
else if (twinkling) | |
drawTwinkles(leds); | |
else | |
flicker(); | |
FastLED.show(); | |
} | |
void fadeOut() | |
{ | |
leds.fadeToBlackBy(30); | |
FastLED.delay(10); // interfers with TwinkleFOX timings... | |
} | |
// This function loops over each pixel, calculates the | |
// adjusted 'clock' that this pixel should use, and calls | |
// "CalculateOneTwinkle" on each pixel. It then displays | |
// either the twinkle color of the background color, | |
// whichever is brighter. | |
void drawTwinkles( CRGBSet& L) | |
{ | |
// "PRNG16" is the pseudorandom number generator | |
// It MUST be reset to the same starting value each time | |
// this function is called, so that the sequence of 'random' | |
// numbers that it generates is (paradoxically) stable. | |
uint16_t PRNG16 = 11337; | |
uint32_t clock32 = millis(); | |
// Set up the background color, "bg". | |
// if AUTO_SELECT_BACKGROUND_COLOR == 1, and the first two colors of | |
// the current palette are identical, then a deeply faded version of | |
// that color is used for the background color | |
CRGB bg; | |
if( (AUTO_SELECT_BACKGROUND_COLOR == 1) && | |
(gCurrentPalette[0] == gCurrentPalette[1] )) { | |
bg = gCurrentPalette[0]; | |
uint8_t bglight = bg.getAverageLight(); | |
if( bglight > 64) { | |
bg.nscale8_video( 16); // very bright, so scale to 1/16th | |
} else if( bglight > 16) { | |
bg.nscale8_video( 64); // not that bright, so scale to 1/4th | |
} else { | |
bg.nscale8_video( 86); // dim, scale to 1/3rd. | |
} | |
} else { | |
bg = gBackgroundColor; // just use the explicitly defined background color | |
} | |
uint8_t backgroundBrightness = bg.getAverageLight(); | |
for( CRGB& pixel: L) { | |
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number | |
uint16_t myclockoffset16= PRNG16; // use that number as clock offset | |
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number | |
// use that number as clock speed adjustment factor (in 8ths, from 8/8ths to 23/8ths) | |
uint8_t myspeedmultiplierQ5_3 = ((((PRNG16 & 0xFF)>>4) + (PRNG16 & 0x0F)) & 0x0F) + 0x08; | |
uint32_t myclock30 = (uint32_t)((clock32 * myspeedmultiplierQ5_3) >> 3) + myclockoffset16; | |
uint8_t myunique8 = PRNG16 >> 8; // get 'salt' value for this pixel | |
// We now have the adjusted 'clock' for this pixel, now we call | |
// the function that computes what color the pixel should be based | |
// on the "brightness = f( time )" idea. | |
CRGB c = computeOneTwinkle( myclock30, myunique8); | |
uint8_t cbright = c.getAverageLight(); | |
int16_t deltabright = cbright - backgroundBrightness; | |
if( deltabright >= 32 || (!bg)) { | |
// If the new pixel is significantly brighter than the background color, | |
// use the new color. | |
pixel = c; | |
} else if( deltabright > 0 ) { | |
// If the new pixel is just slightly brighter than the background color, | |
// mix a blend of the new color and the background color | |
pixel = blend( bg, c, deltabright * 8); | |
} else { | |
// if the new pixel is not at all brighter than the background color, | |
// just use the background color. | |
pixel = bg; | |
} | |
} | |
} | |
// This function takes a time in pseudo-milliseconds, | |
// figures out brightness = f( time ), and also hue = f( time ) | |
// The 'low digits' of the millisecond time are used as | |
// input to the brightness wave function. | |
// The 'high digits' are used to select a color, so that the color | |
// does not change over the course of the fade-in, fade-out | |
// of one cycle of the brightness wave function. | |
// The 'high digits' are also used to determine whether this pixel | |
// should light at all during this cycle, based on the TWINKLE_DENSITY. | |
CRGB computeOneTwinkle( uint32_t ms, uint8_t salt) | |
{ | |
uint16_t ticks = ms >> (8-TWINKLE_SPEED); | |
uint8_t fastcycle8 = ticks; | |
uint16_t slowcycle16 = (ticks >> 8) + salt; | |
slowcycle16 += sin8( slowcycle16); | |
slowcycle16 = (slowcycle16 * 2053) + 1384; | |
uint8_t slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8); | |
uint8_t bright = 0; | |
if( ((slowcycle8 & 0x0E)/2) < TWINKLE_DENSITY) { | |
bright = attackDecayWave8( fastcycle8); | |
} | |
uint8_t hue = slowcycle8 - salt; | |
CRGB c; | |
if( bright > 0) { | |
c = ColorFromPalette( gCurrentPalette, hue, bright, NOBLEND); | |
if( COOL_LIKE_INCANDESCENT == 1 ) { | |
coolLikeIncandescent( c, fastcycle8); | |
} | |
} else { | |
c = CRGB::Black; | |
} | |
return c; | |
} | |
// This function is like 'triwave8', which produces a | |
// symmetrical up-and-down triangle sawtooth waveform, except that this | |
// function produces a triangle wave with a faster attack and a slower decay: | |
// | |
// / \ | |
// / \ | |
// / \ | |
// / \ | |
// | |
uint8_t attackDecayWave8( uint8_t i) | |
{ | |
if( i < 86) { | |
return i * 3; | |
} else { | |
i -= 86; | |
return 255 - (i + (i/2)); | |
} | |
} | |
// This function takes a pixel, and if its in the 'fading down' | |
// part of the cycle, it adjusts the color a little bit like the | |
// way that incandescent bulbs fade toward 'red' as they dim. | |
void coolLikeIncandescent( CRGB& c, uint8_t phase) | |
{ | |
if( phase < 128) return; | |
uint8_t cooling = (phase - 128) >> 4; | |
c.g = qsub8( c.g, cooling); | |
c.b = qsub8( c.b, cooling * 2); | |
} | |
// A mostly red palette with green accents and white trim. | |
// "CRGB::Gray" is used as white to keep the brightness more uniform. | |
const TProgmemRGBPalette16 RedGreenWhite_p FL_PROGMEM = | |
{ CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, | |
CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, | |
CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray, | |
CRGB::Green, CRGB::Green, CRGB::Green, CRGB::Green }; | |
// A mostly (dark) green palette with red berries. | |
//#define Holly_Green 0x00580c // too blue for my ring | |
#define Holly_Green 0x005800 | |
#define Holly_Red 0xB00402 | |
const TProgmemRGBPalette16 Holly_p FL_PROGMEM = | |
{ Holly_Green, Holly_Green, Holly_Green, Holly_Green, | |
Holly_Green, Holly_Green, Holly_Green, Holly_Green, | |
Holly_Green, Holly_Green, Holly_Green, Holly_Green, | |
Holly_Green, Holly_Green, Holly_Red, Holly_Red | |
}; | |
// A red and white striped palette | |
// "CRGB::Gray" is used as white to keep the brightness more uniform. | |
const TProgmemRGBPalette16 RedWhite_p FL_PROGMEM = | |
{ CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, | |
CRGB::Gray, CRGB::Gray, CRGB::Gray, CRGB::Gray, | |
CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, | |
CRGB::Gray, CRGB::Gray, CRGB::Gray, CRGB::Gray }; | |
// A mostly blue palette with white accents. | |
// "CRGB::Gray" is used as white to keep the brightness more uniform. | |
const TProgmemRGBPalette16 BlueWhite_p FL_PROGMEM = | |
{ CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue, | |
CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue, | |
CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue, | |
CRGB::Blue, CRGB::Gray, CRGB::Gray, CRGB::Gray }; | |
// A pure "fairy light" palette with some brightness variations | |
#define HALFFAIRY ((CRGB::FairyLight & 0xFEFEFE) / 2) | |
#define QUARTERFAIRY ((CRGB::FairyLight & 0xFCFCFC) / 4) | |
const TProgmemRGBPalette16 FairyLight_p FL_PROGMEM = | |
{ CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight, | |
HALFFAIRY, HALFFAIRY, CRGB::FairyLight, CRGB::FairyLight, | |
QUARTERFAIRY, QUARTERFAIRY, CRGB::FairyLight, CRGB::FairyLight, | |
CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight }; | |
// A palette of soft snowflakes with the occasional bright one | |
const TProgmemRGBPalette16 Snow_p FL_PROGMEM = | |
{ 0x304048, 0x304048, 0x304048, 0x304048, | |
0x304048, 0x304048, 0x304048, 0x304048, | |
0x304048, 0x304048, 0x304048, 0x304048, | |
0x304048, 0x304048, 0x304048, 0xE0F0FF }; | |
// A palette reminiscent of large 'old-school' C9-size tree lights | |
// in the five classic colors: red, orange, green, blue, and white. | |
#define C9_Red 0xB80400 | |
#define C9_Orange 0x902C02 | |
#define C9_Green 0x046002 | |
#define C9_Blue 0x070758 | |
#define C9_White 0x606820 | |
const TProgmemRGBPalette16 RetroC9_p FL_PROGMEM = | |
{ C9_Red, C9_Orange, C9_Red, C9_Orange, | |
C9_Orange, C9_Red, C9_Orange, C9_Red, | |
C9_Green, C9_Green, C9_Green, C9_Green, | |
C9_Blue, C9_Blue, C9_Blue, | |
C9_White | |
}; | |
// A cold, icy pale blue palette | |
#define Ice_Blue1 0x0C1040 | |
#define Ice_Blue2 0x182080 | |
#define Ice_Blue3 0x5080C0 | |
const TProgmemRGBPalette16 Ice_p FL_PROGMEM = | |
{ | |
Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1, | |
Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1, | |
Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1, | |
Ice_Blue2, Ice_Blue2, Ice_Blue2, Ice_Blue3 | |
}; | |
struct PaletteDesc { | |
TProgmemRGBPalette16* palette_p; | |
char* paletteName; | |
}; | |
// Add or remove palette names from this list to control which color | |
// palettes are used, and in what order. | |
struct PaletteDesc PaletteList[] = { | |
{ NULL, "Flicker" }, // must be included and must be first! | |
{ &RetroC9_p, "Retro C9" }, | |
{ &BlueWhite_p, "Blue White" }, | |
// { &FairyLight_p, "Fairy Light" }, | |
// { &RedGreenWhite_p, "Red Green White" }, | |
{ &PartyColors_p, "Party Colors" }, | |
{ &RedWhite_p, "Red White" }, | |
{ &RainbowColors_p, "Rainbow Colors" }, | |
// { &Snow_p, "Snow" }, | |
{ &Holly_p, "Holly" }, | |
{ &Ice_p, "Ice" } | |
}; | |
// Advance to the next color palette in the list (above). | |
uint8_t chooseNextColorPalette(CRGBPalette16& pal) | |
{ | |
static uint8_t whichPalette = -1; | |
const uint8_t numberOfPalettes = sizeof(PaletteList) / sizeof(PaletteList[0]); | |
whichPalette = addmod8(whichPalette, 1, numberOfPalettes); | |
pal = *(PaletteList[whichPalette]).palette_p; | |
debugLabel("palette", PaletteList[whichPalette].paletteName); | |
return whichPalette; | |
} | |
// Routines to "flicker" a number of pixel-ring LEDs like a candle or flame. | |
// Algorithm lifted from: LedString - Doug Leary - https://github.com/DougLeary/LedString | |
// Check if enough time has elapsed to change the LEDs - avoids blocking delay() calls | |
bool isFlickerTime() | |
{ | |
static unsigned long lastFlickerTime = 0L; // timestamp for activity | |
unsigned long now = millis(); | |
if (now - lastFlickerTime >= FLICKER_RATE) | |
{ | |
lastFlickerTime = now; | |
return true; | |
} | |
else | |
{ | |
// note: this includes when millis() rolls over to 0, because gap will be < 0 | |
return false; | |
} | |
} | |
// Alter the brightnes of a single LED | |
void flickerLed(int led) { | |
if (mod8(led, PIXEL_FRACT) == 0) | |
{ | |
int value = random8(FIRE_MIN, FIRE_MAX); | |
// occasional intense flicker | |
if (value <= FIRE_MIN + FLICKER_EXTRA) { | |
value = FIRE_MIN - ((FIRE_MIN - FLICKER_MIN) / (value - FIRE_MIN)); | |
} | |
else if (value >= FIRE_MAX - FLICKER_EXTRA) { | |
value = FIRE_MAX + ((FLICKER_MAX - FIRE_MAX) / (FIRE_MAX - value)); | |
} | |
leds[led] = CHSV(FLAME_HUE, 255, value); | |
} | |
else | |
leds[led] = 0; // turn off | |
} | |
// If enough time has elapsed, flicker the LEDs | |
void flicker() | |
{ | |
if (isFlickerTime()) | |
{ | |
for (int i = 0; i < NUM_LEDS; ++i) | |
flickerLed(i); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment