Skip to content

Instantly share code, notes, and snippets.

@jshaw
Forked from kriegsman/MarqueeOverlay.ino
Created November 6, 2023 06:03
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 jshaw/ed5070815381ca627617ebe1e6223830 to your computer and use it in GitHub Desktop.
Save jshaw/ed5070815381ca627617ebe1e6223830 to your computer and use it in GitHub Desktop.
MarqueeOverlay - render marquee-like effects on segments of a display (for YUM cart, Burning Man 2015)
#include "FastLED.h"
// MarqueeOverlay
//
// Code to overlay a 'marquee' effect on top
// of other animations, e.g. to highlight individual
// letters of a sign one after another, while
// still allowing the underlying animations to show through.
//
// Initially designed for the "YUM cart" for Burning Man 2015.
// The YUM cart has six segments of 124 LEDs each.
// This example code assumes merely one sixty-LED strip,
// but includes definitions for 6 * 124 segments as well.
//
// Also initially designed to run on a Teensy 3.1,
// where SRAM is relatively plentiful. This code
// runs fine on an AVR Arduino, but much of the static
// data could be moved into PROGMEM to save SRAM there.
//
// Mark Kriegsman, August 2015.
#if FASTLED_VERSION < 3001000
#error "Requires FastLED 3.1 or later; check github for latest code."
#endif
#define DATA_PIN 13
//#define CLK_PIN 4
#define LED_TYPE WS2811
#define COLOR_ORDER GRB
#define NUM_LEDS 60
#define BRIGHTNESS 255
CRGB leds[NUM_LEDS];
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) // cpt-city palettes have different color balance
.setDither(BRIGHTNESS < 255);
// set master brightness control
FastLED.setBrightness(BRIGHTNESS);
}
void loop()
{
DrawRegularPattern(); // here's where you draw your regular animation
// Then you add this, and applies various 'marquee' effects, from time to time
// across the various segments of LED strip that you define (below).
ApplyMarqueeEffect();
FastLED.show();
FastLED.delay(10);
}
// This is just a stand-in for whatever your regular animations are.
void DrawRegularPattern()
{
static uint8_t start = 0;
start -= 1;
uint8_t hue = start;
for( uint16_t i = 0; i < NUM_LEDS; i++) {
leds[i] = CHSV( hue, 240, 192);
hue += 3;
}
}
/////////////////// Start of Marquee animation effects //////////////////
//
// Activate these by putting "ApplyMarqueeEffect();" just before
// your call to FastLED.show.
//
// How often to start a new marquee animation, in seconds:
const uint32_t gMarqueeCycleSeconds = 10;
// How long the marquee animation should run, in seconds:
const uint32_t gMarqueeEffectSeconds = 3;
// How long each 'step' of the marquee animation is, in milliseconds:
const uint16_t gMarqueeSequenceStepTimeMsMin = 50;
const uint16_t gMarqueeSequenceStepTimeMsMax = 450;
typedef struct TMarqueeSegment {
CRGB* mBase;
uint16_t mLength;
} TMarqueeSegment;
// Definitions for the "segments" of the LED strip(s) to
// be treated as separate areas. They can be in
// different CRGB arrays, or all in the same one, or any combination.
// Each segment is defined as a starting point in an array, and a length.
#ifndef NUM_LEDS_PER_STRIP
// Sixty-pixel demo, with six segments in it.
TMarqueeSegment gMarqueeSegments[] = {
{ leds + 0 * (NUM_LEDS/6), (NUM_LEDS/6)},
{ leds + 1 * (NUM_LEDS/6), (NUM_LEDS/6)},
{ leds + 2 * (NUM_LEDS/6), (NUM_LEDS/6)},
{ leds + 3 * (NUM_LEDS/6), (NUM_LEDS/6)},
{ leds + 4 * (NUM_LEDS/6), (NUM_LEDS/6)},
{ leds + 5 * (NUM_LEDS/6), (NUM_LEDS/6)}
};
#else
// If you just so happen to have six segments
// with NUM_LEDS_PER_STRIP pixels each, you could do this:
TMarqueeSegment gMarqueeSegments[] = {
{ leds + (0*NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP},
{ leds + (1*NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP},
{ leds + (2*NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP},
{ leds + (3*NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP},
{ leds + (4*NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP},
{ leds + (5*NUM_LEDS_PER_STRIP), NUM_LEDS_PER_STRIP}
};
#endif
// Animated sequence patterns for the marquees.
// Single digits refer to individual segments, '*' means all segments.
// So to affect segment 1, then segment 2, then segment 3, then all of them,
// you'd write the pattern "123*".
// If you have more physical segments than accounted for in the pattern,
// the pattern repeats across all segments. So for example, if you have
// six segments, but a pattern only uses the tokens 1, 2, and 3, the pattern
// is duplicated from segments 1..3 onto segments 4..6.
// The patterns listed here are executed in sequence, one after another.
const char* gMarqueeSequencePatterns[] = {
" 123",
"123456",
"112233",
"123 456 ",
"1 * 2 * 3 ",
"* ",
"1 2 3 ",
"14253625",
"123",
"* "
};
// There are multiple visual overlay effects used to create
// the marquee animation.
// If you don't like one of the effects, just comment out that line.
// You can also write your own overlay effects and just add them
// to this array.
// Visual effects are chosen at random.
typedef void (*TMarqueeSegmentEffect)( const struct TMarqueeSegment& segment);
const TMarqueeSegmentEffect gMarqueeSegmentEffects[] = {
// Effects that sprinkle 'glitter' on the background pattern
MarqueeEffectGlitterWhite,
MarqueeEffectGlitterBlack,
MarqueeEffectGlitterColor,
// Effects that create a crawling marquee on the background pattern
MarqueeEffectCrawlWhite,
MarqueeEffectCrawlBlack,
MarqueeEffectCrawlColor,
// Effects that display a steady, solid color
MarqueeEffectSolidWhite,
MarqueeEffectSolidBlack,
MarqueeEffectSolidColor,
// Effects that rapidly flicker a solid color
MarqueeEffectFlashWhite,
MarqueeEffectFlashBlack,
MarqueeEffectFlashColor,
// Effect that changes the hue of the background pattern
MarqueeEffectRotateColor
};
const uint8_t kMarqueeSequenceCount = sizeof( gMarqueeSequencePatterns ) / sizeof( const char*);
const uint8_t kMarqueeSegmentCount = sizeof(gMarqueeSegments) / sizeof( TMarqueeSegment);
const uint8_t kMarqueeSegmentEffectCount = sizeof( gMarqueeSegmentEffects) / sizeof( TMarqueeSegmentEffect);
uint8_t gMarqueeCurrentPatternNumber = 0;
uint8_t gMarqueeSegmentEffectNumber = 0;
void ApplyMarqueeEffect()
{
static bool running = true;
static uint32_t posStartTimeMs = 0;
static uint8_t seqpos = 0;
uint32_t curTimeMs = millis();
uint32_t cycletime = curTimeMs % (uint32_t)(gMarqueeCycleSeconds * 1000L);
bool shouldRun = cycletime < (uint32_t)(gMarqueeEffectSeconds * 1000L);
// Step 1: figure out if we're "off duty" right now, and if so,
// is it time to start up again?
if( !running ) {
if( shouldRun ) {
// start
running = true;
posStartTimeMs = curTimeMs;
seqpos = 0;
MarqueeChooseNewSegmentEffect();
gMarqueeCurrentPatternNumber = addmod8( gMarqueeCurrentPatternNumber, 1, kMarqueeSequenceCount);
} else {
return;
}
}
// Step 2: figure out which where we are in the current Marquee sequence
uint8_t len = MarqueeGetSequenceLength();
uint16_t gMarqueeSequenceStepTimeMs = beatsin88( 555, gMarqueeSequenceStepTimeMsMin, gMarqueeSequenceStepTimeMsMax);
if( (curTimeMs - posStartTimeMs) >= gMarqueeSequenceStepTimeMs) {
seqpos++;
posStartTimeMs = curTimeMs;
}
if( seqpos >= len) {
seqpos = 0;
if( !shouldRun) {
running = false;
return;
}
}
// Step 3: figure out which LED strip segment(s) to apply
// the effect to.
const char* cp = gMarqueeSequencePatterns[gMarqueeCurrentPatternNumber];
char c = cp[seqpos];
if( c == ' ') return;
uint8_t segno = 0;
if( c >= '0' && c <= '9') {
segno = c - '0';
} else if (c == '*') {
segno = 255; // meaning "all"
}
// junk characters = ignore;
if( segno == 0) return;
// apply to all segments
if( segno == 255) {
for( uint8_t s = 0; s < kMarqueeSegmentCount; s++) {
ApplyEffectToSegment( gMarqueeSegments[ s]);
}
} else {
// just one segment phase
segno--; // adjust for zero-index
uint8_t maxNamedSegment = MarqueeGetMax();
for( uint8_t s = segno; s < kMarqueeSegmentCount; s += maxNamedSegment) {
ApplyEffectToSegment( gMarqueeSegments[ s]);
}
}
}
uint8_t MarqueeGetSequenceLength()
{
return strlen( gMarqueeSequencePatterns[gMarqueeCurrentPatternNumber]);
}
uint8_t MarqueeGetMax()
{
uint8_t mx = 1;
const char* cp = gMarqueeSequencePatterns[gMarqueeCurrentPatternNumber];
char c;
while( (c = *cp)) {
if( c >= '0' && c <= '9') {
uint8_t n = c - '0';
if( n > mx ) {
mx = n;
}
}
cp++;
}
return mx;
}
void MarqueeChooseNewSegmentEffect()
{
gMarqueeSegmentEffectNumber = random8( kMarqueeSegmentEffectCount );
}
void ApplyEffectToSegment( const struct TMarqueeSegment& segment)
{
TMarqueeSegmentEffect effect = gMarqueeSegmentEffects[ gMarqueeSegmentEffectNumber ];
(effect)(segment);
}
void MarqueeEffectDim( const struct TMarqueeSegment& segment)
{
fadeToBlackBy( segment.mBase, segment.mLength, 192);
}
void MarqueeEffectFlashBlack( const struct TMarqueeSegment& segment)
{
MarqueeEffectFlashX( segment, CRGB::Black);
}
void MarqueeEffectFlashWhite( const struct TMarqueeSegment& segment)
{
MarqueeEffectFlashX( segment, CRGB(120,120,160)); // not FFFFFF for power reasons!
}
void MarqueeEffectFlashColor( const struct TMarqueeSegment& segment)
{
MarqueeEffectFlashX( segment, CHSV( millis() / 16, 200, 255));
}
void MarqueeEffectFlashX( const struct TMarqueeSegment& segment, const CRGB& color)
{
uint8_t m = millis() & 0x20;
if( m ) {
fill_solid( segment.mBase, segment.mLength, color);
}
}
void MarqueeEffectSolidBlack( const struct TMarqueeSegment& segment)
{
MarqueeEffectSolidX( segment, CRGB::Black);
}
void MarqueeEffectSolidWhite( const struct TMarqueeSegment& segment)
{
MarqueeEffectSolidX( segment, CRGB(120,120,160)); // not FFFFFF for power reasons!
}
void MarqueeEffectSolidColor( const struct TMarqueeSegment& segment)
{
MarqueeEffectSolidX( segment, CHSV( millis() / 16, 200, 255));
}
void MarqueeEffectSolidX( const struct TMarqueeSegment& segment, const CRGB& color)
{
fill_solid( segment.mBase, segment.mLength, color);
}
void MarqueeEffectGlitterBlack( const struct TMarqueeSegment& segment)
{
MarqueeEffectGlitterX( segment, CRGB::Black, 2);
}
void MarqueeEffectGlitterColor( const struct TMarqueeSegment& segment)
{
MarqueeEffectGlitterX( segment, CHSV( millis() / 16, 200, 255), 3);
}
void MarqueeEffectGlitterWhite( const struct TMarqueeSegment& segment)
{
MarqueeEffectGlitterX( segment, CRGB::White, 7);
}
void MarqueeEffectGlitterX( const struct TMarqueeSegment& segment, const CRGB& color, uint8_t glitFactor)
{
CRGB* segleds = segment.mBase;
uint16_t len = segment.mLength;
for( uint16_t i = random8(glitFactor); i < len; i += (random8(glitFactor) + 1)) {
segleds[i] = color;
}
}
void MarqueeEffectRotateColor( const struct TMarqueeSegment& segment)
{
CRGB* segleds = segment.mBase;
uint16_t len = segment.mLength;
for( uint16_t i = 0; i < len; i++) {
uint8_t t = segleds[i].r;
segleds[i].r = segleds[i].g;
segleds[i].g = segleds[i].b;
segleds[i].b = t;
}
}
void MarqueeEffectCrawlWhite( const struct TMarqueeSegment& segment)
{
MarqueeEffectCrawlX( segment, CRGB::White);
}
void MarqueeEffectCrawlBlack( const struct TMarqueeSegment& segment)
{
MarqueeEffectCrawlX( segment, CRGB::Black);
}
void MarqueeEffectCrawlColor( const struct TMarqueeSegment& segment)
{
MarqueeEffectCrawlX( segment, CHSV( millis() / 16, 200, 255));
}
void MarqueeEffectCrawlX( const struct TMarqueeSegment& segment, const CRGB& color)
{
CRGB* segleds = segment.mBase;
uint16_t len = segment.mLength;
// static uint8_t start = 0;
//start = addmod8( start, 1, 4);
uint8_t start = millis();
start = (start >> 5) & 0x03;
for( uint16_t i = start; i < len; i += 4) {
segleds[i] = color;
if( (i + 1) < len) {
segleds[i+1] = color;
}
}
}
/////////////////// End of Marquee animation effects //////////////////
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment