Skip to content

Commit bab19df

Browse files
Add Fri3d Badge library with TwinkleFox.ino
1 parent 0c171a5 commit bab19df

File tree

4 files changed

+397
-0
lines changed

4 files changed

+397
-0
lines changed

libraries/Fri3dBadge/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Fri3d Badge 2024 library
2+
3+
This library provides integration and examples for the [Fri3d Camp 2024 Badge](https://github.com/Fri3dCamp/badge_2024).
Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
/// @file TwinkleFox.ino
2+
/// @brief Twinkling "holiday" lights that fade in and out.
3+
/// @example TwinkleFox.ino
4+
#include "FastLED.h"
5+
6+
7+
#define NUM_LEDS 5
8+
#define LED_TYPE WS2812
9+
#define COLOR_ORDER GRB
10+
#define DATA_PIN X_WS2812_LED
11+
#define VOLTS 5
12+
#define MAX_MA 100
13+
14+
// TwinkleFOX: Twinkling 'holiday' lights that fade in and out.
15+
// Colors are chosen from a palette; a few palettes are provided.
16+
//
17+
// This December 2015 implementation improves on the December 2014 version
18+
// in several ways:
19+
// - smoother fading, compatible with any colors and any palettes
20+
// - easier control of twinkle speed and twinkle density
21+
// - supports an optional 'background color'
22+
// - takes even less RAM: zero RAM overhead per pixel
23+
// - illustrates a couple of interesting techniques (uh oh...)
24+
//
25+
// The idea behind this (new) implementation is that there's one
26+
// basic, repeating pattern that each pixel follows like a waveform:
27+
// The brightness rises from 0..255 and then falls back down to 0.
28+
// The brightness at any given point in time can be determined as
29+
// as a function of time, for example:
30+
// brightness = sine( time ); // a sine wave of brightness over time
31+
//
32+
// So the way this implementation works is that every pixel follows
33+
// the exact same wave function over time. In this particular case,
34+
// I chose a sawtooth triangle wave (triwave8) rather than a sine wave,
35+
// but the idea is the same: brightness = triwave8( time ).
36+
//
37+
// Of course, if all the pixels used the exact same wave form, and
38+
// if they all used the exact same 'clock' for their 'time base', all
39+
// the pixels would brighten and dim at once -- which does not look
40+
// like twinkling at all.
41+
//
42+
// So to achieve random-looking twinkling, each pixel is given a
43+
// slightly different 'clock' signal. Some of the clocks run faster,
44+
// some run slower, and each 'clock' also has a random offset from zero.
45+
// The net result is that the 'clocks' for all the pixels are always out
46+
// of sync from each other, producing a nice random distribution
47+
// of twinkles.
48+
//
49+
// The 'clock speed adjustment' and 'time offset' for each pixel
50+
// are generated randomly. One (normal) approach to implementing that
51+
// would be to randomly generate the clock parameters for each pixel
52+
// at startup, and store them in some arrays. However, that consumes
53+
// a great deal of precious RAM, and it turns out to be totally
54+
// unnessary! If the random number generate is 'seeded' with the
55+
// same starting value every time, it will generate the same sequence
56+
// of values every time. So the clock adjustment parameters for each
57+
// pixel are 'stored' in a pseudo-random number generator! The PRNG
58+
// is reset, and then the first numbers out of it are the clock
59+
// adjustment parameters for the first pixel, the second numbers out
60+
// of it are the parameters for the second pixel, and so on.
61+
// In this way, we can 'store' a stable sequence of thousands of
62+
// random clock adjustment parameters in literally two bytes of RAM.
63+
//
64+
// There's a little bit of fixed-point math involved in applying the
65+
// clock speed adjustments, which are expressed in eighths. Each pixel's
66+
// clock speed ranges from 8/8ths of the system clock (i.e. 1x) to
67+
// 23/8ths of the system clock (i.e. nearly 3x).
68+
//
69+
// On a basic Arduino Uno or Leonardo, this code can twinkle 300+ pixels
70+
// smoothly at over 50 updates per seond.
71+
//
72+
// -Mark Kriegsman, December 2015
73+
74+
CRGBArray<NUM_LEDS> leds;
75+
76+
// Overall twinkle speed.
77+
// 0 (VERY slow) to 8 (VERY fast).
78+
// 4, 5, and 6 are recommended, default is 4.
79+
#define TWINKLE_SPEED 4
80+
81+
// Overall twinkle density.
82+
// 0 (NONE lit) to 8 (ALL lit at once).
83+
// Default is 5.
84+
#define TWINKLE_DENSITY 5
85+
86+
// How often to change color palettes.
87+
#define SECONDS_PER_PALETTE 30
88+
// Also: toward the bottom of the file is an array
89+
// called "ActivePaletteList" which controls which color
90+
// palettes are used; you can add or remove color palettes
91+
// from there freely.
92+
93+
// Background color for 'unlit' pixels
94+
// Can be set to CRGB::Black if desired.
95+
CRGB gBackgroundColor = CRGB::Black;
96+
// Example of dim incandescent fairy light background color
97+
// CRGB gBackgroundColor = CRGB(CRGB::FairyLight).nscale8_video(16);
98+
99+
// If AUTO_SELECT_BACKGROUND_COLOR is set to 1,
100+
// then for any palette where the first two entries
101+
// are the same, a dimmed version of that color will
102+
// automatically be used as the background color.
103+
#define AUTO_SELECT_BACKGROUND_COLOR 0
104+
105+
// If COOL_LIKE_INCANDESCENT is set to 1, colors will
106+
// fade out slighted 'reddened', similar to how
107+
// incandescent bulbs change color as they get dim down.
108+
#define COOL_LIKE_INCANDESCENT 1
109+
110+
111+
CRGBPalette16 gCurrentPalette;
112+
CRGBPalette16 gTargetPalette;
113+
114+
void setup() {
115+
delay( 3000 ); //safety startup delay
116+
FastLED.setMaxPowerInVoltsAndMilliamps( VOLTS, MAX_MA);
117+
FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS)
118+
.setCorrection(TypicalLEDStrip);
119+
120+
chooseNextColorPalette(gTargetPalette);
121+
}
122+
123+
124+
void loop()
125+
{
126+
EVERY_N_SECONDS( SECONDS_PER_PALETTE ) {
127+
chooseNextColorPalette( gTargetPalette );
128+
}
129+
130+
EVERY_N_MILLISECONDS( 10 ) {
131+
nblendPaletteTowardPalette( gCurrentPalette, gTargetPalette, 12);
132+
}
133+
134+
drawTwinkles( leds);
135+
136+
FastLED.show();
137+
}
138+
139+
140+
// This function loops over each pixel, calculates the
141+
// adjusted 'clock' that this pixel should use, and calls
142+
// "CalculateOneTwinkle" on each pixel. It then displays
143+
// either the twinkle color of the background color,
144+
// whichever is brighter.
145+
void drawTwinkles( CRGBSet& L)
146+
{
147+
// "PRNG16" is the pseudorandom number generator
148+
// It MUST be reset to the same starting value each time
149+
// this function is called, so that the sequence of 'random'
150+
// numbers that it generates is (paradoxically) stable.
151+
uint16_t PRNG16 = 11337;
152+
153+
uint32_t clock32 = millis();
154+
155+
// Set up the background color, "bg".
156+
// if AUTO_SELECT_BACKGROUND_COLOR == 1, and the first two colors of
157+
// the current palette are identical, then a deeply faded version of
158+
// that color is used for the background color
159+
CRGB bg;
160+
if( (AUTO_SELECT_BACKGROUND_COLOR == 1) &&
161+
(gCurrentPalette[0] == gCurrentPalette[1] )) {
162+
bg = gCurrentPalette[0];
163+
uint8_t bglight = bg.getAverageLight();
164+
if( bglight > 64) {
165+
bg.nscale8_video( 16); // very bright, so scale to 1/16th
166+
} else if( bglight > 16) {
167+
bg.nscale8_video( 64); // not that bright, so scale to 1/4th
168+
} else {
169+
bg.nscale8_video( 86); // dim, scale to 1/3rd.
170+
}
171+
} else {
172+
bg = gBackgroundColor; // just use the explicitly defined background color
173+
}
174+
175+
uint8_t backgroundBrightness = bg.getAverageLight();
176+
177+
for( CRGB& pixel: L) {
178+
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number
179+
uint16_t myclockoffset16= PRNG16; // use that number as clock offset
180+
PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number
181+
// use that number as clock speed adjustment factor (in 8ths, from 8/8ths to 23/8ths)
182+
uint8_t myspeedmultiplierQ5_3 = ((((PRNG16 & 0xFF)>>4) + (PRNG16 & 0x0F)) & 0x0F) + 0x08;
183+
uint32_t myclock30 = (uint32_t)((clock32 * myspeedmultiplierQ5_3) >> 3) + myclockoffset16;
184+
uint8_t myunique8 = PRNG16 >> 8; // get 'salt' value for this pixel
185+
186+
// We now have the adjusted 'clock' for this pixel, now we call
187+
// the function that computes what color the pixel should be based
188+
// on the "brightness = f( time )" idea.
189+
CRGB c = computeOneTwinkle( myclock30, myunique8);
190+
191+
uint8_t cbright = c.getAverageLight();
192+
int16_t deltabright = cbright - backgroundBrightness;
193+
if( deltabright >= 32 || (!bg)) {
194+
// If the new pixel is significantly brighter than the background color,
195+
// use the new color.
196+
pixel = c;
197+
} else if( deltabright > 0 ) {
198+
// If the new pixel is just slightly brighter than the background color,
199+
// mix a blend of the new color and the background color
200+
pixel = blend( bg, c, deltabright * 8);
201+
} else {
202+
// if the new pixel is not at all brighter than the background color,
203+
// just use the background color.
204+
pixel = bg;
205+
}
206+
}
207+
}
208+
209+
210+
// This function takes a time in pseudo-milliseconds,
211+
// figures out brightness = f( time ), and also hue = f( time )
212+
// The 'low digits' of the millisecond time are used as
213+
// input to the brightness wave function.
214+
// The 'high digits' are used to select a color, so that the color
215+
// does not change over the course of the fade-in, fade-out
216+
// of one cycle of the brightness wave function.
217+
// The 'high digits' are also used to determine whether this pixel
218+
// should light at all during this cycle, based on the TWINKLE_DENSITY.
219+
CRGB computeOneTwinkle( uint32_t ms, uint8_t salt)
220+
{
221+
uint16_t ticks = ms >> (8-TWINKLE_SPEED);
222+
uint8_t fastcycle8 = ticks;
223+
uint16_t slowcycle16 = (ticks >> 8) + salt;
224+
slowcycle16 += sin8( slowcycle16);
225+
slowcycle16 = (slowcycle16 * 2053) + 1384;
226+
uint8_t slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8);
227+
228+
uint8_t bright = 0;
229+
if( ((slowcycle8 & 0x0E)/2) < TWINKLE_DENSITY) {
230+
bright = attackDecayWave8( fastcycle8);
231+
}
232+
233+
uint8_t hue = slowcycle8 - salt;
234+
CRGB c;
235+
if( bright > 0) {
236+
c = ColorFromPalette( gCurrentPalette, hue, bright, NOBLEND);
237+
if( COOL_LIKE_INCANDESCENT == 1 ) {
238+
coolLikeIncandescent( c, fastcycle8);
239+
}
240+
} else {
241+
c = CRGB::Black;
242+
}
243+
return c;
244+
}
245+
246+
247+
// This function is like 'triwave8', which produces a
248+
// symmetrical up-and-down triangle sawtooth waveform, except that this
249+
// function produces a triangle wave with a faster attack and a slower decay:
250+
//
251+
// / \
252+
// / \
253+
// / \
254+
// / \
255+
//
256+
257+
uint8_t attackDecayWave8( uint8_t i)
258+
{
259+
if( i < 86) {
260+
return i * 3;
261+
} else {
262+
i -= 86;
263+
return 255 - (i + (i/2));
264+
}
265+
}
266+
267+
// This function takes a pixel, and if its in the 'fading down'
268+
// part of the cycle, it adjusts the color a little bit like the
269+
// way that incandescent bulbs fade toward 'red' as they dim.
270+
void coolLikeIncandescent( CRGB& c, uint8_t phase)
271+
{
272+
if( phase < 128) return;
273+
274+
uint8_t cooling = (phase - 128) >> 4;
275+
c.g = qsub8( c.g, cooling);
276+
c.b = qsub8( c.b, cooling * 2);
277+
}
278+
279+
// A mostly red palette with green accents and white trim.
280+
// "CRGB::Gray" is used as white to keep the brightness more uniform.
281+
const TProgmemRGBPalette16 RedGreenWhite_p FL_PROGMEM =
282+
{ CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red,
283+
CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red,
284+
CRGB::Red, CRGB::Red, CRGB::Gray, CRGB::Gray,
285+
CRGB::Green, CRGB::Green, CRGB::Green, CRGB::Green };
286+
287+
// A mostly (dark) green palette with red berries.
288+
#define Holly_Green 0x00580c
289+
#define Holly_Red 0xB00402
290+
const TProgmemRGBPalette16 Holly_p FL_PROGMEM =
291+
{ Holly_Green, Holly_Green, Holly_Green, Holly_Green,
292+
Holly_Green, Holly_Green, Holly_Green, Holly_Green,
293+
Holly_Green, Holly_Green, Holly_Green, Holly_Green,
294+
Holly_Green, Holly_Green, Holly_Green, Holly_Red
295+
};
296+
297+
// A red and white striped palette
298+
// "CRGB::Gray" is used as white to keep the brightness more uniform.
299+
const TProgmemRGBPalette16 RedWhite_p FL_PROGMEM =
300+
{ CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red,
301+
CRGB::Gray, CRGB::Gray, CRGB::Gray, CRGB::Gray,
302+
CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red,
303+
CRGB::Gray, CRGB::Gray, CRGB::Gray, CRGB::Gray };
304+
305+
// A mostly blue palette with white accents.
306+
// "CRGB::Gray" is used as white to keep the brightness more uniform.
307+
const TProgmemRGBPalette16 BlueWhite_p FL_PROGMEM =
308+
{ CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue,
309+
CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue,
310+
CRGB::Blue, CRGB::Blue, CRGB::Blue, CRGB::Blue,
311+
CRGB::Blue, CRGB::Gray, CRGB::Gray, CRGB::Gray };
312+
313+
// A pure "fairy light" palette with some brightness variations
314+
#define HALFFAIRY ((CRGB::FairyLight & 0xFEFEFE) / 2)
315+
#define QUARTERFAIRY ((CRGB::FairyLight & 0xFCFCFC) / 4)
316+
const TProgmemRGBPalette16 FairyLight_p FL_PROGMEM =
317+
{ CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight,
318+
HALFFAIRY, HALFFAIRY, CRGB::FairyLight, CRGB::FairyLight,
319+
QUARTERFAIRY, QUARTERFAIRY, CRGB::FairyLight, CRGB::FairyLight,
320+
CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight, CRGB::FairyLight };
321+
322+
// A palette of soft snowflakes with the occasional bright one
323+
const TProgmemRGBPalette16 Snow_p FL_PROGMEM =
324+
{ 0x304048, 0x304048, 0x304048, 0x304048,
325+
0x304048, 0x304048, 0x304048, 0x304048,
326+
0x304048, 0x304048, 0x304048, 0x304048,
327+
0x304048, 0x304048, 0x304048, 0xE0F0FF };
328+
329+
// A palette reminiscent of large 'old-school' C9-size tree lights
330+
// in the five classic colors: red, orange, green, blue, and white.
331+
#define C9_Red 0xB80400
332+
#define C9_Orange 0x902C02
333+
#define C9_Green 0x046002
334+
#define C9_Blue 0x070758
335+
#define C9_White 0x606820
336+
const TProgmemRGBPalette16 RetroC9_p FL_PROGMEM =
337+
{ C9_Red, C9_Orange, C9_Red, C9_Orange,
338+
C9_Orange, C9_Red, C9_Orange, C9_Red,
339+
C9_Green, C9_Green, C9_Green, C9_Green,
340+
C9_Blue, C9_Blue, C9_Blue,
341+
C9_White
342+
};
343+
344+
// A cold, icy pale blue palette
345+
#define Ice_Blue1 0x0C1040
346+
#define Ice_Blue2 0x182080
347+
#define Ice_Blue3 0x5080C0
348+
const TProgmemRGBPalette16 Ice_p FL_PROGMEM =
349+
{
350+
Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1,
351+
Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1,
352+
Ice_Blue1, Ice_Blue1, Ice_Blue1, Ice_Blue1,
353+
Ice_Blue2, Ice_Blue2, Ice_Blue2, Ice_Blue3
354+
};
355+
356+
357+
// Add or remove palette names from this list to control which color
358+
// palettes are used, and in what order.
359+
const TProgmemRGBPalette16* ActivePaletteList[] = {
360+
&RetroC9_p,
361+
&BlueWhite_p,
362+
&RainbowColors_p,
363+
&FairyLight_p,
364+
&RedGreenWhite_p,
365+
&PartyColors_p,
366+
&RedWhite_p,
367+
&Snow_p,
368+
&Holly_p,
369+
&Ice_p
370+
};
371+
372+
373+
// Advance to the next color palette in the list (above).
374+
void chooseNextColorPalette( CRGBPalette16& pal)
375+
{
376+
const uint8_t numberOfPalettes = sizeof(ActivePaletteList) / sizeof(ActivePaletteList[0]);
377+
static uint8_t whichPalette = -1;
378+
whichPalette = addmod8( whichPalette, 1, numberOfPalettes);
379+
380+
pal = *(ActivePaletteList[whichPalette]);
381+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# TwinkleFox Code origin
2+
Code for this example came from the MIT licensed dir at https://github.com/FastLED/FastLED/tree/602bd81c01f010c7b58fe0cf30d246fefe1ff797/examples/TwinkleFox
3+
Only parameters where change to make this work on the 2024 Fri3d badge.

0 commit comments

Comments
 (0)