Skip to content

Instantly share code, notes, and snippets.

@Lukelectro
Created January 7, 2022 20:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Lukelectro/d7849672654de212e27e0f82257c0f35 to your computer and use it in GitHub Desktop.
Save Lukelectro/d7849672654de212e27e0f82257c0f35 to your computer and use it in GitHub Desktop.
Showreel for 241 pixel nested LED-rings, based on FastLED's showreel100, includes a modified Fire2012
#include <FastLED.h>
FASTLED_USING_NAMESPACE
// FastLED "100-lines-of-code" demo reel, showing just a few
// of the kinds of animation patterns you can quickly and easily
// compose using FastLED.
//
// This example also shows one easy way to define multiple
// animations patterns and have them automatically rotate.
//
// -Mark Kriegsman, December 2014
// Modified for 241 pixel circulair LED-matrix / nested LED rings. Quite a few todo's left but here is a gist.
// - Lucas, Januari 2022 (No longer 100 lines...)
#if defined(FASTLED_VERSION) && (FASTLED_VERSION < 3001000)
#warning "Requires FastLED 3.1 or later; check github for latest code."
#endif
#define DATA_PIN 6
//#define CLK_PIN 4
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
#define NUM_LEDS 241
CRGB leds[NUM_LEDS];
#define BRIGHTNESS 64
#define FRAMES_PER_SECOND 120
const int numrings = 9;
const int ledPos[numrings][2] = { // positions of the leds for each ring. note: start with 0
{ 0, 59}, //60
{60, 107}, //48
{108, 147}, //40
{148, 179}, //32
{180, 203}, //24
{204, 219}, //16
{220, 231}, //12
{232, 239}, //8
{240, 240}// 1
};
const int numinring[numrings] = {60, 48, 40, 32, 24, 16, 12, 8, 1};
void setup() {
delay(3000); // 3 second delay for recovery
// tell FastLED about the LED strip configuration
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);
// set master brightness control
FastLED.setBrightness(BRIGHTNESS);
}
// List of patterns to cycle through. Each is defined as a separate function below.
typedef void (*SimplePatternList[])();
//SimplePatternList gPatterns = { rainbow, rainbowWithGlitter, confetti, sinelon, juggle, bpm }; // all preexisting (Not all these work out nicely on non-lineair led things)
//SimplePatternList gPatterns = { ring_fire2012, ring_circles, ring_firework, ring_knight, ring_radar, ring_radar_reverse, ring_rainbow, ring_rainbow_2, ring_rgb, ring_cyclo}; // all new
SimplePatternList gPatterns = { ring_fire2012, ring_circles, ring_firework, ring_knight, ring_radar, ring_radar_reverse, ring_rainbow, ring_rainbow_2, ring_rgb, ring_cyclo, confetti, sinelon, juggle, bpm}; // all nice on 241 led ring thing
//SimplePatternList gPatterns = { ring_cyclo }; //ring_rgb}; // current test(s)
uint8_t gCurrentPatternNumber = 0; // Index number of which pattern is current
uint8_t gHue = 0; // rotating "base color" used by many of the patterns
void loop()
{
// Call the current pattern function once, updating the 'leds' array
gPatterns[gCurrentPatternNumber]();
// send the 'leds' array out to the actual LED strip
FastLED.show();
// insert a delay to keep the framerate modest
FastLED.delay(1000 / FRAMES_PER_SECOND);
// do some periodic updates
EVERY_N_MILLISECONDS( 20 ) {
gHue++; // slowly cycle the "base color" through the rainbow
}
EVERY_N_SECONDS( 20 ) {
nextPattern(); // change patterns periodically
fill_solid(leds, NUM_LEDS, CRGB::Black); // clear screen before changing to next
}
}
#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))
void nextPattern()
{
// add one to the current pattern number, and wrap around at the end
gCurrentPatternNumber = (gCurrentPatternNumber + 1) % ARRAY_SIZE( gPatterns);
}
void rainbow()
{
// FastLED's built-in rainbow generator
fill_rainbow( leds, NUM_LEDS, gHue, 7); // pointer to first, number to fill, initial color, delta color
}
void ring_rainbow() // rainbow around circle
{
// FastLED's built-in rainbow generator
for (int i = 0; i < numrings; i++) { //i<numrings
fill_rainbow( &leds[ledPos[i][0]], numinring[i], gHue, 255 / numinring[i]); // pointer to first, number to fill, initial color, delta color
}
}
void ring_rgb() // TODO: hues are a bit too pastel-y. Still looks nice but should be a variant. And another one with pure RGB and saturated mix colors.
{
static uint8_t j;
j++;
for (int i = 0; i < numrings; i++) { //i<numrings
fill_solid( &leds[ledPos[i][0]], numinring[i], CHSV(gHue + ((i + j) * 72), 200, 255)); // pointer to first, number to fill, color to fill with
}
FastLED.delay(800); //TODO: make less flashy / slwoly fade from one hue to the next instead of flashing over
}
void ring_rainbow_2() // rainbow from center to outside
{
for (int j = 0; j < 255; j++) {
for (int i = 0; i < numrings; i++) { //i<numrings
fill_solid( &leds[ledPos[i][0]], numinring[i], CHSV(gHue + ((i + j) * 12), 255, 255)); // pointer to first, number to fill, color to fill with
}
}
}
void ring_radar()
{
for (int j = 60; j > 0; j--) {
fadeToBlackBy(leds, NUM_LEDS, 64);
for (int ring = 0; ring < numrings; ring++) {
leds[ (j * numinring[ring]) / 60 + ledPos[ring][0]] = CRGB::Green; // startposition plus (j/60)-th ratio of largest ring.
}
FastLED.delay(25);
}
}
void ring_radar_reverse()
{
for (int j = 0; j < 60; j++) {
fadeToBlackBy(leds, NUM_LEDS, 64);
for (int i = 0; i < numrings; i++) { //i<numrings
leds[ (j * numinring[i]) / 60 + ledPos[i][0]] = CRGB::Green;
}
FastLED.delay(25);
}
}
void ring_knight()
{
static int j;
if (j >= 60) j = 0; else j++;
for (int i = 0; i < numrings; i++) { //i<numrings
fadeToBlackBy(leds, NUM_LEDS, 128);
leds[ledPos[i][0] + ((j * numinring[i]) / 60)] = CRGB::Red;
FastLED.delay(50);
}
for (int i = numrings - 2; i >= 0; i--) {
fadeToBlackBy(leds, NUM_LEDS, 128);
leds[ledPos[i][0] + (((j + 30) % 60) * numinring[i]) / 60] = CRGB::Red;
FastLED.delay(50);
}
for (int i = 1; i < numrings; i++) { //i<numrings
fadeToBlackBy(leds, NUM_LEDS, 128);
leds[ledPos[i][0] + (((j + 30) % 60) * numinring[i]) / 60] = CRGB::Red;
FastLED.delay(50);
}
for (int i = numrings - 2; i >= 0; i--) {
fadeToBlackBy(leds, NUM_LEDS, 128);
leds[ledPos[i][0] + ((j * numinring[i]) / 60) ] = CRGB::Red;
FastLED.delay(50);
}
}
void ring_firework()
{
// firework goes up from random start position, then when it reaches the center it goes off with a random effect in random hues or silver/gold crackling.
// TODO: hues are often too pastel-y
CRGB up = CRGB::DarkGoldenrod; /* color of rocket*/
byte r_hue = random8(); /*random for hue of other parts*/
CHSV mid = CHSV( r_hue, 200, 255); /* middle part of effect */
CHSV stream = CHSV((r_hue + 30) % 255, 200, 255); /* color of streamer part of effect */
CHSV point = CHSV((r_hue - 127) % 255, 200, 255); /* color of end part of effect */
CRGB crackle = CRGB::Silver; /* color of crackling */
if (random8() < 127) crackle = CRGB::Gold; /* sometimes cracling is gold*/
int up_pos = random8(59); // random start position from outer ring
byte effect, r_effect = random8(); /* pick which effect */
// make some effects more likely then others:
if (r_effect < 127) effect = 0; else // half the time: flower
if (r_effect < 152) effect = 1; else // 25/255 : small/failed flower
if (r_effect < 177) effect = 3; else effect = 2; // 25/255 : crackling and resting 78/255: rings
/* go up */
for (int i = 0; i < numrings; i++) { //i<numrings
fadeToBlackBy(leds, NUM_LEDS, 200);
leds[ledPos[i][0] + up_pos * numinring[i] / 60] = up;
FastLED.delay(20 + i * 6); // so it seems to slow down / height ilusion
}
/* go off*/
switch (effect) {
default:
case 0:
// effect: Flower/peony/coconut/whatever the pyro's call it. 8 streamers
for (int i = numrings; i >= 0; i--) { //i<numrings
fadeToBlackBy(leds, NUM_LEDS, 50);
//inner few rings solid, then streamers
if (i > 6) {
fill_solid( &leds[ledPos[i][0]], numinring[i], mid); // pointer to first, number to fill, color to fill with (can be CHSV(gHue+((i+j)*12), 255, 255))
}
else if (i != 0)
{
for (int j = 0; j < numinring[i]; j += (numinring[i] / 7))
leds[ledPos[i][0] + j] = stream;
}
else {
leds[ledPos[i][0] + 0] = point;
leds[ledPos[i][0] + 7] = point;
leds[ledPos[i][0] + 8 + 7] = point;
leds[ledPos[i][0] + 7 + 8 + 7] = point;
leds[ledPos[i][0] + 8 + 7 + 8 + 7] = point;
leds[ledPos[i][0] + 7 + 8 + 7 + 8 + 8] = point;
leds[ledPos[i][0] + 8 + 7 + 8 + 7 + 8 + 7] = point;
leds[ledPos[i][0] + 7 + 8 + 7 + 8 + 7 + 8 + 8] = point;
}
FastLED.delay(40 - i * 4);
}
break;
case 1:
// effect: smaller flower, "failed firework"
for (int i = numrings; i >= 0; i--) { //i<numrings
fadeToBlackBy(leds, NUM_LEDS, 50);
//inner few rings solid, then streamers
if (i > 6) {
fill_solid( &leds[ledPos[i][0]], numinring[i], mid); // pointer to first, number to fill, color to fill with (can be CHSV(gHue+((i+j)*12), 255, 255))
}
else
{
for (int j = 0; j < numinring[i]; j += (numinring[i] / 5))
{
leds[ledPos[i][0] + j] = stream;
if (i == 0) leds[ledPos[i][0] + j] = point;
}
}
FastLED.delay(40 - i * 4);
}
break;
case 2:
//effect: rings
for (int i = numrings - 1; i >= 0; i--) {
fadeToBlackBy(leds, NUM_LEDS, 50);
fill_solid( &leds[ledPos[i][0]], numinring[i], stream);
FastLED.delay(20);
}
for (int i = 0; i < 30 ; i++) { /* extinguish, else outer rings linger too long */
fadeToBlackBy(leds, NUM_LEDS, 70);
FastLED.delay(20);
}
break;
case 3:
//cracling
for (int i = 0; i < 90; i++) {
fadeToBlackBy( leds, NUM_LEDS, 3);
int pos = random16(NUM_LEDS);
leds[pos] += crackle;
}
for (int i = 0; i < 20; i++) {
fadeToBlackBy( leds, NUM_LEDS, 3);
int pos = random16(NUM_LEDS);
leds[pos] += crackle;
FastLED.delay(1);
}
break;
}
// extinguish
for (int i = 0; i < 120; i++) {
fadeToBlackBy(leds, NUM_LEDS, 5);
FastLED.delay(10);
}
}
void ring_circles()
{
CHSV color;
color = CHSV(gHue, 200, 255);
for (int i = 0; i < numrings; i++) { //i<numrings
fadeToBlackBy(leds, NUM_LEDS, 150);
fill_solid( &leds[ledPos[i][0]], numinring[i], color);
FastLED.delay(80);
}
for (int i = numrings - 2; i >= 0; i--) {
fadeToBlackBy(leds, NUM_LEDS, 150);
fill_solid( &leds[ledPos[i][0]], numinring[i], color);
FastLED.delay(80);
}
}
void rainbowWithGlitter()
{
// built-in FastLED rainbow, plus some random sparkly glitter
rainbow();
addGlitter(80);
}
void addGlitter( fract8 chanceOfGlitter)
{
if ( random8() < chanceOfGlitter) {
leds[ random16(NUM_LEDS) ] += CRGB::White;
}
}
void confetti()
{
// random colored speckles that blink in and fade smoothly
fadeToBlackBy( leds, NUM_LEDS, 10);
int pos = random16(NUM_LEDS);
leds[pos] += CHSV( gHue + random8(64), 200, 255);
}
void sinelon()
{
// a colored dot sweeping back and forth, with fading trails
fadeToBlackBy( leds, NUM_LEDS, 20);
int pos = beatsin16( 13, 0, NUM_LEDS - 1 );
leds[pos] += CHSV( gHue, 255, 192);
}
void bpm()
{
// colored stripes pulsing at a defined Beats-Per-Minute (BPM)
uint8_t BeatsPerMinute = 62;
CRGBPalette16 palette = PartyColors_p;
uint8_t beat = beatsin8( BeatsPerMinute, 64, 255);
for ( int i = 0; i < NUM_LEDS; i++) { //9948
leds[i] = ColorFromPalette(palette, gHue + (i * 2), beat - gHue + (i * 10));
}
}
void juggle() {
// eight colored dots, weaving in and out of sync with each other
fadeToBlackBy( leds, NUM_LEDS, 20);
byte dothue = 0;
for ( int i = 0; i < 8; i++) {
leds[beatsin16( i + 7, 0, NUM_LEDS - 1 )] |= CHSV(dothue, 200, 255);
dothue += 32;
}
}
void ring_cyclo()
{
static int pos[6]; // position of dots
static bool dir[6] = { true,false,true,false,true,false }; // directions of movement of dot(s)
for (int i = 0 ; i < 6; i++) {
(pos[i] < numinring[i]-1) ? pos[i]++ : pos[i] = 0; /* else just increment untill full circle */
}
fadeToBlackBy(leds, NUM_LEDS, 64);
for (int ring = 0; ring < 6; ring++) {
if (dir[ring]) {
leds[ pos[ring] + ledPos[ring][0]] = CHSV(gHue+ring * 32, 200, 255);
} else {
leds[ ledPos[ring][1] - pos[ring] ] = CHSV(gHue+ring * 32, 200, 255);
}
}
FastLED.delay(35); // todo: delay should be adapted to size of ring? Now dot travels at constant linear velocity but angular velocity increases on smaller circles.
}
void ring_fire2012()
{
#define FLAMES 4
// Array of temperature readings at each simulation cell
static byte heat[numrings][FLAMES];
//static uint8_t heat[FLAMES][numrings];
CRGB ledcolors[numrings][FLAMES];
// COOLING: How much does the air cool as it rises?
// Less cooling = taller flames. More cooling = shorter flames.
// Default 50, suggested range 20-100
#define COOLING 70
// SPARKING: What chance (out of 255) is there that a new spark will be lit?
// Higher chance = more roaring fire. Lower chance = more flickery fire.
// Default 120, suggested range 50-200.
#define SPARKING 120
for (int flame = 0; flame < FLAMES; flame++) {
// Step 1. Cool down every cell a little
for ( int i = 0; i < numrings; i++) {
heat[i][flame] = qsub8( heat[i][flame], random8(0, ((COOLING * 10) / numrings) + 2));
}
// Step 2. Heat from each cell drifts 'up' and diffuses a little
for ( int k = numrings - 1; k >= 2; k--) {
heat[k][flame] = (heat[k - 1][flame] + heat[k - 2][flame] + heat[k - 2][flame] ) / 3;
}
// Step 3. Randomly ignite new 'sparks' of heat at/near the bottom
if ( random8() < SPARKING ) {
int y = random8(2);
heat[y][flame] = qadd8( heat[y][flame], random8(160, 255) );
}
// Step 4. Map from heat cells to LED colors
for ( int j = 0; j < numrings; j++) {
ledcolors[j][flame] = HeatColor( heat[j][flame]);
}
}
//step 5: display on LED rings
for (int ring = 0; ring < numrings; ring++) {
/* Flame 0 */
leds[ledPos[ring][0]] = ledcolors[ring][0];
if (ring < 7) { // wider flame at base
leds[ledPos[ring][0] + 1] = ledcolors[ring + 1][0];
leds[(ledPos[ring][1])] = ledcolors[ring + 1][0];
}
if (ring < 5) {
leds[ledPos[ring][0] + 2] = ledcolors[ring + 2][0];
leds[(ledPos[ring][1] - 1)] = ledcolors[ring + 3][0];
}
if (ring < 3) {
leds[ledPos[ring][0] + 3] = ledcolors[ring + 3][0];
leds[(ledPos[ring][1] - 2)] = ledcolors[ring + 4][0];
}
/* Flame 1 */
leds[ledPos[ring][0] + numinring[ring] / 4] = ledcolors[ring][1];
if (ring < 7) { // wider flame at base
leds[ledPos[ring][0] + numinring[ring] / 4 + 1] = ledcolors[ring + 1][1];
leds[(ledPos[ring][0]) + numinring[ring] / 4 - 1] = ledcolors[ring + 1][1];
}
if (ring < 5) {
leds[ledPos[ring][0] + numinring[ring] / 4 + 2] = ledcolors[ring + 2][1];
leds[(ledPos[ring][0] + numinring[ring] / 4 - 2)] = ledcolors[ring + 3][1];
}
if (ring < 3) {
leds[ledPos[ring][0] + numinring[ring] / 4 + 3] = ledcolors[ring + 3][1];
leds[(ledPos[ring][0] + numinring[ring] / 4 - 3)] = ledcolors[ring + 4][1];
}
/* Flame 2 */
leds[ledPos[ring][0] + numinring[ring] / 2] = ledcolors[ring][1];
if (ring < 7) { // wider flame at base
leds[ledPos[ring][0] + numinring[ring] / 2 + 1] = ledcolors[ring + 1][1];
leds[(ledPos[ring][0]) + numinring[ring] / 2 - 1] = ledcolors[ring + 1][1];
}
if (ring < 5) {
leds[ledPos[ring][0] + numinring[ring] / 2 + 2] = ledcolors[ring + 2][1];
leds[(ledPos[ring][0] + numinring[ring] / 2 - 2)] = ledcolors[ring + 3][1];
}
if (ring < 3) {
leds[ledPos[ring][0] + numinring[ring] / 2 + 3] = ledcolors[ring + 3][1];
leds[(ledPos[ring][0] + numinring[ring] / 2 - 3)] = ledcolors[ring + 4][1];
}
/* Flame 3 */
leds[ledPos[ring][1] - numinring[ring] / 4] = ledcolors[ring][1];
if (ring < 7) { // wider flame at base
leds[ledPos[ring][1] - numinring[ring] / 4 + 1] = ledcolors[ring + 1][1];
leds[(ledPos[ring][1]) - numinring[ring] / 4 - 1] = ledcolors[ring + 1][1];
}
if (ring < 5) {
leds[ledPos[ring][1] - numinring[ring] / 4 + 2] = ledcolors[ring + 2][1];
leds[(ledPos[ring][1] - numinring[ring] / 4 - 2)] = ledcolors[ring + 3][1];
}
if (ring < 3) {
leds[ledPos[ring][1] - numinring[ring] / 4 + 3] = ledcolors[ring + 3][1];
leds[(ledPos[ring][1] - numinring[ring] / 4 - 3)] = ledcolors[ring + 4][1];
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment