Switching and porting from Arduino Mega to Teensy 4.1

Hello,
I'm slowly going deeper into the rabbit hole of my project (those of you who have seen my previous posts will know) and now I'm thinking of swapping out the Mega for a Teensy 4.1.
I'm doing a pre-programmed lighting show synced to a video (~1.5h long) with 435 + 448 + 162 WS2812B 5V and 6 5050 12V strips.
Flash storage on the Mega is not enough. 5mins of programming I did take up ~20K so fitting it all into the 256K of the Mega, even with optimization, seems almost impossible to me.
The Teensy 4.1 has 32 times more flash, plus a faster processor and is programmable in the Arduino IDE so would be perfect for what I'm trying to achieve.

  • Will the code I've already wrote run on the Teensy without much hassle?
  • It looks like Teensy's logic of 3.3V is not good for the WS2812B data line. I'm planning to use a 74HCT245 like this. The only difference is that I'll use A2-B2 and A3-B3 too as I have 3 strips.
  • I also plan to use FastLED together with OctoWS2811 to drive the leds, similar to how is illustrated here.
  • On the Mega staying in sync with the video required a frame counter based off an RTC. Briefly, the problem was that the calls to FastLED.show() disable interrupts and for long strip interfere with the accuracy of millis(). I solved the problem by using the 32K output of a ZS-042 RTC to make a timer that increments 25 times a second to time the lights. Will this be needed on the Teensy? If so can I use the same approach and board?
  • To control the 5050 strips (2 RGB, 4 white) I currently use 10 IRL540N MOSFETs (1.5A max each). What should I swap them with if I have to? I now have 220Ω resistors on the gates. Max current on the output pins of the Teensy is 4mA, so I should swap them for 1kΩ right?

Big thanks!

Why flash?
Why not SD?

You can store programs on SD cards? I thought they were only for storing/logging data

No typically you cannot store directly executable code in SD, but you haven't said what you're trying to do, or how you're trying to do it, but

suggested to me (perhaps wrongly) you were trying to code the display in software, rather than interpreted stored data.

Correct, they are for storing data. It sounds like you are trying to use endless lines of C++ code to "encode" your sequences instead of treating the sequences as what they are: data, not program. They may seem too complex to you to be represented as data, and that's why you are taking the approach you have.

You will have seen, in videos, perhaps in real life, fairground organs, pianolas (player pianos), even Jacquard looms. The mechanisms inside these machines are not complex enough to have multiple pieces of complex music or woven patterns built into their mechanisms. The sequences within the music or patterns are encoded on drums or lengths of paper. The mechanism reads the data from the drum/paper and executes the sequences. Even nature uses the same technique: DNA. That's what you need to do with your light sequences.

Here's what I suggest: there are some very clever chaps and ladies on this forum. I'm not so bad myself. So how about giving us an example sequence. Not too long, but not a short one either. Let's see if we can invent a way to reproduce that sequence using less flash memory than your current method, or a way to express it as data which can be stored in a file on an SD card.

Yes, that’s what I was trying to do, not the best approach now that I think of it.

This is a great idea, if done correctly it could also make programming the whole sequence way easier. The only problem I see is that the sequences will take longer to test as I’ll constantly have to swap the SD card between the Arduino and my pc.

Tomorrow I’ll clean up my code and upload an example.

If this works out it’s a great solution, but still I would like to know how feasible moving to the Teensy could be.

Somewhere between very straight forward and quite tricky. That's all anyone can say without seeing the code!

Some code, including the code in some libraries, includes lines of code which are specific to AVR ATmega chips, sometimes even specific models of ATmega chip. These code lines tend to do something like accessing the internal registers in the chip which control pins and timers, for example. These lines of code won't work on other designs of chip, because the internal registers in those chips are different.

Code and libraries which use only the standard Arduino functions, those shown in the Arduino language reference, will generally work unaltered on any Arduino compatible board, including Teensy 4.

Some libraries, such as the Neopixel and FastLED libraries, have to use functions and code lines that are specific to particular chips. That's because there's no way to do it with the standard functions. These libraries contain versions of those code lines that are specific to several types of chip. The correct versions of those lines are automatically selected by the library when you select the board type you are using in the IDE and upload your code. These libraries will list the boards they are compatible with in their documentation.

For example here is the page for FastLED library. It mentions Teensy 2 and 3 but 4 is not mentioned! It might work on Teensy 4 but there's no guarantee.

I'll include almost all of my code. The sequences to look at are introCountdown(), introBuildup() and flagParade(). I used lots of nested if as an attempt to keep the code small and to avoid copy-pasting as much as possible. These parts are hard to read without knowing their output, but you should get an idea of what I need to do by looking at the function calls and the manipulation to the led arrays.
Even though this will be by far the best solution I'm still a bit skeptical on its feasibility and flexibility, but maybe you c++ masters can surprise me.

#include <FastLED.h>
#include <LEDMatrix.h>
#include <LEDText.h>
#include <FontMatrise.h>
#include "RTClib.h"

RTC_DS3231 rtc;

//leds
#define LED_PIN_SQ     5
#define LED_PIN_TR     35
#define LED_PIN_ST     6

#define MATRIX_WIDTH   56
#define MATRIX_HEIGHT  -8
#define MATRIX_TYPE    VERTICAL_ZIGZAG_MATRIX
#define NUM_LEDS_SQ    448

#define NUM_LEDS_TR    162
#define NUM_LEDS_ST    435

#define BRIGHTNESS_SQ  64
#define BRIGHTNESS_TR  255
#define BRIGHTNESS_ST  128

#define LED_TYPE       WS2812B
#define COLOR_ORDER    GRB

CRGB STleds[NUM_LEDS_ST];   //strip number 0
const uint16_t section[5] = {0, 180, 254, 375, 435}; //sections for strip 0
const uint16_t middle = 216; 

CRGB TRleds[NUM_LEDS_TR];   //strip number 1

cLEDMatrix<MATRIX_WIDTH, MATRIX_HEIGHT, MATRIX_TYPE> SQleds;   //strip (7 8*8 matrices) number 2
cLEDText Holder1, Holder2, Holder3, Holder4, Holder5, Holder6, Holder7;  //one holder for each matrix, for text display
unsigned char letter1[1] = {""}, letter2[1] = {""}, letter3[1] = {""}, letter4[1] = {""};
const unsigned char countries[] = {"ARMAUSAZEBLRBELBULCYPDNKFINGREITALTUMLTNORPOLRUSSVNESPSWESUINEDUKR"};

uint8_t rgbledpins[2][3] = {{7, 13, 4},{9, 10, 8}};
uint8_t whiteledpins[4] = {3,12,2,11};


//palette
CRGBPalette16 currentPalette;
TBlendType currentBlending = NOBLEND; //LINEARBLEND - NOBLEND


//variables
uint16_t i, j, k, t, n, c, d, value, b, toggle, dim, flag;
byte switch1, switch2;
int32_t counter;

//photoresistor
const uint16_t threshold = 850  ; //half brightness monitor = 850; tv (no autobrightness?) = 500
byte syncFlag1 = 1;
byte syncFlag2 = 1;

//RTC
volatile uint32_t frameCounter = 0;
uint32_t frameCount, frameCountPrev, startFrames;
int32_t beginningFrames;


//function declarations
void updateFrameCount();
uint32_t returnFrameCount();
void waitTO(uint32_t frames);
void waitForFrameChange();
void fillRGB(CRGB *array, uint16_t from, uint16_t to, uint8_t r, uint8_t g, uint8_t b, uint16_t brightness = 255);
void fillHSV(CRGB *array, uint16_t from, uint16_t to, uint8_t h, uint8_t s, uint8_t v);
void FillLEDsFromPalette(CRGB *array, uint16_t arraylength, uint8_t length, uint8_t colorIndex, uint8_t brightness = 255);
void setrgbledpins(byte side, uint8_t r, uint8_t g, uint8_t b);
void setwhiteledpins(byte side, uint8_t b);
void turnOff(byte a, byte b, byte c, byte d, byte e, byte f, byte g, byte h, byte i);

void introCountdown();
void introBuildup();
void flagParade();

void setup() {
    Serial.begin(115200);
    while (!Serial);
    Serial.println("startup");
    
    //ensures that random values repeat exactly each time
    random16_set_seed (0);
    //initialize output pins and set brightness to 0
    for (i=0; i<2; i++)
      for (j=0; j<3; j++){
        pinMode(rgbledpins[i][j], OUTPUT);
        analogWrite(rgbledpins[i][j], 0);
      }
    for (i=0; i<4; i++){
      pinMode(whiteledpins[i], OUTPUT);
      analogWrite(whiteledpins[i], 0);
    }

    //initialize individully addressable strips
    delay(3000);
    FastLED.addLeds<LED_TYPE, LED_PIN_ST, COLOR_ORDER>(STleds, NUM_LEDS_ST).setCorrection(TypicalLEDStrip);
    FastLED.addLeds<LED_TYPE, LED_PIN_TR, COLOR_ORDER>(TRleds, NUM_LEDS_TR).setCorrection(UncorrectedColor);
    FastLED.addLeds<LED_TYPE, LED_PIN_SQ, COLOR_ORDER>(SQleds[0], SQleds.Size()).setCorrection(UncorrectedColor);
    
    //check lights
    SQleds.DrawFilledRectangle(0, 0, 55, 7, CRGB(64,64,64));
    fill_solid(TRleds, NUM_LEDS_TR, CHSV(0,0,255));
    fill_solid(STleds, NUM_LEDS_ST, CHSV(0,0,128));
    setrgbledpins(0, 85, 85, 85);
    setrgbledpins(1, 85, 85, 85);
    setwhiteledpins(0, 85);
    setwhiteledpins(1, 85);
    setwhiteledpins(2, 85);
    setwhiteledpins(3, 85);

    FastLED.show();
    
    Holder1.Init(&SQleds, SQleds.Width(), 8, 2, 0);
    Holder1.SetFont(MatriseFontData);
    Holder1.SetTextColrOptions(COLR_RGB | COLR_SINGLE, 0xff, 0xff, 0xff);
  
    Holder2.Init(&SQleds, SQleds.Width(), 8, 10, 0);
    Holder2.SetFont(MatriseFontData);
    Holder2.SetTextColrOptions(COLR_RGB | COLR_SINGLE, 0xff, 0xff, 0xff);

    Holder3.Init(&SQleds, SQleds.Width(), 8, 18, 0);
    Holder3.SetFont(MatriseFontData);
    Holder3.SetTextColrOptions(COLR_RGB | COLR_SINGLE, 0xff, 0xff, 0xff);
  
    Holder4.Init(&SQleds, SQleds.Width(), 8, 26, 0);
    Holder4.SetFont(MatriseFontData);
    Holder4.SetTextColrOptions(COLR_RGB | COLR_SINGLE, 0xff, 0xff, 0xff);
    
    Holder5.Init(&SQleds, SQleds.Width(), 8, 33, 0);
    Holder5.SetFont(MatriseFontData);
    Holder5.SetTextColrOptions(COLR_RGB | COLR_SINGLE, 0xff, 0xff, 0xff);
  
    Holder6.Init(&SQleds, SQleds.Width(), 8, 41, 0);
    Holder6.SetFont(MatriseFontData);
    Holder6.SetTextColrOptions(COLR_RGB | COLR_SINGLE, 0xff, 0xff, 0xff);
    
    Holder7.Init(&SQleds, SQleds.Width(), 8, 49, 0);
    Holder7.SetFont(MatriseFontData);
    Holder7.SetTextColrOptions(COLR_RGB | COLR_SINGLE, 0xff, 0xff, 0xff);

    delay(1000);
    turnOff(1,1,1,1,1,1,1,1,1);

    //check RTC
    if (! rtc.begin()) {
      Serial.println("Couldn't find RTC");
      while (1);
    }
    if (rtc.lostPower()) {
      Serial.println("RTC lost power, setting time");
      // If the RTC have lost power it will sets the RTC to the date & time this sketch was compiled
      rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    }

    if (!rtc.isEnabled32K()) { //verify 32K output is enabled
      rtc.enable32K();
    }

    //setup timing interrupts
    TCCR5A = 0; //clear control register A
    TCCR5B = 0; //clear control register B
    TCNT5 = 0;  //clear counter
    OCR5A = 1309; //set value for output compare register A  (32768Hz * 1/25 second) - 1 = 1309
    TCCR5B |= (1 << WGM52); //Set CTC mode (WGM5 = 0100);
    TCCR5B |= (1 << CS52) | (1 << CS51) | (1 << CS50); //External Clock mode using D47 as input
    TIMSK5 |= (1 << OCIE5A); //Set the interrupt request
    interrupts(); //enable interrupt

    Serial.println("Setup done!");
}

void loop() {
    //wait for sync image
    Serial.println("Waiting for sync image...");
    while (syncFlag1 != 0){
      delay(5);
      if (analogRead(A0) > threshold){
        delay(5);
        Serial.println("Detected sync white");
        Serial.println(analogRead(A0));
        while(syncFlag2 != 0){
          if (analogRead(A0) < threshold){
            Serial.println("Detected sync black");
            Serial.println(analogRead(A0));
            syncFlag2=0;
          }
        }
        syncFlag1=0;
      }
    }
    Serial.println("Successfully synced!");
    delay(2970);
 
    noInterrupts();
    beginningFrames = frameCounter;
    interrupts();
  
    Serial.println("----- START -----");
    turnOff(1,1,1,1,1,1,1,1,1);


    //##############
    for(i = section[1]; i < section[2]; i++)
      STleds[i].setRGB(0, 128, 128);
    FastLED.show();
    
    waitTO(99);
    turnOff(1,1,1,1,1,1,1,1,1);
    
    waitTO(151);
    introCountdown();
   
    //ensure mosfets stay closed during long off time
    for (i = 0; i < 20; i++){
      waitTO(1670 + i*100);
      turnOff(1,1,1,1,1,1,0,0,0);
    }
    //##############

    
    //##############
    waitTO(3737);
    introBuildup();
    //##############


    //##############
    waitTO(4488);
    flagParade();
    //##############
    
    while(1);
}


//----------------------- Time FUNCTIONS ----------------------
void waitTO(uint32_t frames){
  while (returnFrameCount() < frames){
    continue;
  }
}

void startFrom(uint32_t delayFrames){
  beginningFrames = beginningFrames - delayFrames;
}

void waitForFrameChange(){
  frameCountPrev = returnFrameCount();
  while (frameCountPrev == returnFrameCount()) {
    continue;
  }
}

//----------------------- LED FUNCTIONS -----------------------
void fillRGB(CRGB *array, uint16_t from, uint16_t to, uint8_t r, uint8_t g, uint8_t b, uint16_t brightness = 255){
    uint8_t correction = 1;
    if ((r!=0)&&(g!=0)&&(b!=0)){
      correction = (r + g + b)/brightness;
    }
    for (uint16_t i = from; i <= to; i++){
        array[i].setRGB(r/correction, g/correction, b/correction);
    }
}

void fillHSV(CRGB *array, uint16_t from, uint16_t to, uint8_t h, uint8_t s, uint8_t v){
    for (uint16_t i = from; i <= to; i++){
        array[i].setHSV(h, s, v);
    }
}

void FillLEDsFromPalette(CRGB *array, uint16_t arraylength, uint8_t length, uint8_t colorIndex, uint8_t brightness = 255){
  for(uint16_t i = 0; i < arraylength; i++) {
    array[i] = ColorFromPalette(currentPalette, colorIndex, brightness, currentBlending);
    colorIndex += length; //1 -> 32 leds, 32 -> 1 led
  }
}

void setrgbledpins(byte side, uint8_t r, uint8_t g, uint8_t b){
    //0 = left
    //1 = right
    analogWrite(rgbledpins[side][0], r);
    analogWrite(rgbledpins[side][1], g);
    analogWrite(rgbledpins[side][2], b);
}

void setwhiteledpins(byte side, uint8_t b){
    //0 = front left
    //1 = front right
    //2 = back left
    //3 = back right
    analogWrite(whiteledpins[side], b);
}

void turnOff(byte a, byte b, byte c, byte d, byte e, byte f, byte g, byte h, byte i){
  if (a){ setwhiteledpins(0, 0); }
  if (b){ setwhiteledpins(1, 0); }
  if (c){ setwhiteledpins(2, 0); }
  if (d){ setwhiteledpins(3, 0); }

  if (e){ setrgbledpins(0, 0, 0, 0); }
  if (f){ setrgbledpins(1, 0, 0, 0); }

  if (g){
    fill_solid(STleds, NUM_LEDS_ST, CHSV(0,0,0));
    FastLED[0].showLeds();
  }
  if (h){
    fill_solid(TRleds, NUM_LEDS_TR, CHSV(0,0,0));
    FastLED[1].showLeds();
  }
  if (i){
    SQleds.DrawFilledRectangle(0, 0, 55, 7, CRGB(0,0,0));
    FastLED[2].showLeds();
  }
}

//------------------------- INTERRUPTS ------------------------
ISR(TIMER5_COMPA_vect) {   //This is the interrupt request
  static byte cycleCount = 24;
  //adjustment to compensate for 32768 not being evenly divisible by 25
  //counter will count ( (7 * 1310) + (18 * 1311) ) = 32768 pulses over 25 frames
  if ((cycleCount & 0x03) == 0) {
    OCR5A = 1309; //1310 clock pulses
  } else {
    OCR5A = 1310; //1311 clock pulses
  }
  if (cycleCount == 0) {
    cycleCount = 25;
  }
  cycleCount--;

  frameCounter++; //actual frame counter
}

void updateFrameCount(){
  noInterrupts();
  frameCount = frameCounter - beginningFrames;
  interrupts();
}

uint32_t returnFrameCount(){
  noInterrupts();
  uint32_t returnvalue = frameCounter - beginningFrames;
  interrupts();
  return returnvalue;
}
//---------------------------- SHOW ---------------------------
void introCountdown(){
  turnOff(1,1,1,1,1,1,1,1,1);
  k = 0;
  n = 0;
  
  const CRGB blue = CRGB(10, 0, 255);
  const CRGB red = CRGB(255, 0, 0);
  const CRGB black = CRGB::Black;
  currentPalette = CRGBPalette16(blue,  black,  black,  black,
                                 blue,  black,  black,  black,
                                 blue,  black,  black,  black,
                                 blue,  black,  black,  black);
                                 
  currentBlending = NOBLEND;

  updateFrameCount();
  frameCountPrev = frameCount;
  
  letter1[0] = {6 + '0'};
  letter2[0] = {0 + '0'};
  letter3[0] = {6 + '0'};
  letter4[0] = {0 + '0'};
  
  Holder1.SetText((unsigned char *)letter1, 1);
  Holder1.UpdateText();
  Holder2.SetText((unsigned char *)letter2, 1);
  Holder2.UpdateText();
  Holder6.SetText((unsigned char *)letter3, 1);
  Holder6.UpdateText(); 
  Holder7.SetText((unsigned char *)letter4, 1);
  Holder7.UpdateText(); 
  
  FastLED[2].showLeds(64);

  uint8_t blinked = 0;
  bool blinking = false;
  uint8_t d = 5, u = 9;
  
  setrgbledpins(1, 0, 10, 40);
  setrgbledpins(0, 0, 8, 50);
  while (frameCount < 1650){
    updateFrameCount();
    if (frameCount != frameCountPrev) {
      //blink 4 times in each group
      if ((frameCount == 244+(94*n) || blinking) && (frameCount < 850 || frameCount > 1050)){
        if (frameCount == 244+(94*n)+6*(blinked-1)+3){
          turnOff(1,1,1,1,0,0,0,1,0);
          if (frameCount >= 898){
            setrgbledpins(0, 40, 0, 0);
            setrgbledpins(1, 40, 0, 0);
          }else{
            setrgbledpins(0, 0, 8, 50);
            setrgbledpins(1, 0, 10, 40);
          }
        }

        if (frameCount == 244+(94*n)+6*blinked){
          blinked++;
          switch (blinked){
          case 1:
            blinking = true;
            setrgbledpins(1, 80, 80, 80);
            setrgbledpins(0, 80, 80, 80);
            break;
          case 2:
            setwhiteledpins(1, 45);
            setwhiteledpins(0, 45);
            break;
          case 3:
            setwhiteledpins(3, 45);
            setwhiteledpins(2, 45);
            break;
          case 4:
            fill_solid(TRleds, NUM_LEDS_TR, CHSV(0,0,255));
            FastLED[1].showLeds();
            setrgbledpins(1, 80, 80, 80);
            setrgbledpins(0, 80, 80, 80);
            break;
          case 5:
            n++;
            blinked = 0;
            blinking = false;
            break;
          }
        }
      }
      if (frameCount == 850){
        n = n+2;
      }

      //countdown
      if ((frameCount-150) % 25 == 0){
        letter1[0] = {d + '0'};
        letter2[0] = {u + '0'};
        letter3[0] = {d + '0'};
        letter4[0] = {u + '0'};
        
        Holder1.SetText((unsigned char *)letter1, 1);
        Holder1.UpdateText();
        Holder2.SetText((unsigned char *)letter2, 1);
        Holder2.UpdateText();
        Holder6.SetText((unsigned char *)letter3, 1);
        Holder6.UpdateText(); 
        Holder7.SetText((unsigned char *)letter4, 1);
        Holder7.UpdateText(); 
        FastLED[2].showLeds(64);
        
        if (u == 0){
          d--;
          u = 9;
        }else{
          u--;
        }
        
      }

      //flowing
      if (frameCount == 898){
        currentPalette = CRGBPalette16(red,  black,  black,  black,
                                       red,  black,  black,  black,
                                       red,  black,  black,  black,
                                       red,  black,  black,  black);
        setrgbledpins(1, 40, 0, 0);
        setrgbledpins(0, 40, 0, 0);
      }
      FillLEDsFromPalette(STleds, NUM_LEDS_ST, 1, k, 255);
      k = k-4; //move pattern
      FastLED[0].showLeds();
    }
    frameCountPrev = frameCount;
  }
  
  //flash
  setwhiteledpins(0, 255);
  setwhiteledpins(1, 255);
  setwhiteledpins(2, 255);
  setwhiteledpins(3, 255);
  waitTO(1651);
  turnOff(1,1,1,1,1,1,1,1,1);
}
void introBuildup(){
    //flash 4 times different areas
    for(n = 0; n < 4; n++){
      //flash
      waitTO(3739 + 47*n);
      value = 220;
      switch (n){
        case 0:
          fill_solid(STleds, NUM_LEDS_ST, CRGB(value, value, value));
          FastLED[0].showLeds();
          break;
        case 1:
          setwhiteledpins(0, value);
          setwhiteledpins(1, value);
          break;
        case 2:
          setrgbledpins(1, value, value, value);
          setrgbledpins(0, value, value, value);
          break;
        case 3:
          SQleds.DrawFilledRectangle(0, 0, 55, 7, CRGB(value,value,value));
          FastLED[2].showLeds();
          break;
      }
      waitForFrameChange();
      waitForFrameChange();
      //dim
      for(k = 1; k <= 10; k++){
        value = value - k*4;
        switch (n){
          case 0:
            fill_solid(STleds, NUM_LEDS_ST, CRGB(value, value, value));
            FastLED[0].showLeds();
            break;
          case 1:
            setwhiteledpins(0, value);
            setwhiteledpins(1, value);
            break;
          case 2:
            setrgbledpins(1, value, value, value);
            setrgbledpins(0, value, value, value);
            break;
          case 3:
            SQleds.DrawFilledRectangle(0, 0, 55, 7, CRGB(value,value,value));
            FastLED[2].showLeds();
            break;
        }
        waitForFrameChange();
      }
    }

    t = 0;
    d = 0;
    const CRGB white = CRGB(255, 255, 255);
    const CRGB black = CRGB::Black;
    currentPalette = CRGBPalette16(black,  white,  white,  white,
                                   white,  white,  white,  white,
                                   black,  white,  white,  white,
                                   white,  white,  white,  white);
    waitTO(3926);
    updateFrameCount();
    frameCountPrev = frameCount;
    while (frameCount < 4114){
      updateFrameCount();
      if (frameCount != frameCountPrev) {
        //sparkling
        for(k = 0; k < NUM_LEDS_ST/10; k++){
          value = random8(0,2)*random8(2,4)*30;
          for(n = 0; n < 10; n++){
            STleds[k*10+n].setHSV(0, 0, value);
          } 
        }
        for(k = 0; k < NUM_LEDS_TR/18; k++){
          value = random8(0,2)*255;
          for (n = 0; n < 18; n++){
            TRleds[k*18+n].setHSV(0, 0, value);
          }
        }
          
        //blink 4 times
        if (frameCount == 3927 || frameCount == 3973 || frameCount == 4020 || frameCount == 4067){
          setrgbledpins(0, 255, 255, 255);
          setrgbledpins(1, 255, 255, 255);
        }
        if (frameCount == 3927+2 || frameCount == 3973+2 || frameCount == 4020+2 || frameCount == 4067+2){
          setrgbledpins(0, 0, 0, 0);
          setrgbledpins(1, 0, 0, 0);
        }

        //load animation over 108 frames
        if (frameCount > 4005){
          FillLEDsFromPalette(STleds, t, 8, d, 64);
          d = d - (frameCount - 3800)/70;
          t = t+2;
          fillHSV(STleds, NUM_LEDS_ST - 3, NUM_LEDS_ST - 1, 0, 0, 0);
          for (c = 0; c < middle; c++){ STleds[NUM_LEDS_ST - c - 4] = STleds[c]; } //mirror
        }
        
        FastLED[0].showLeds();
        FastLED[1].showLeds();
      }
      frameCountPrev = frameCount;
    }

    fillHSV(TRleds, 0, NUM_LEDS_TR - 1, 0, 0, 0);
    fillHSV(STleds, 0, NUM_LEDS_ST - 1, 0, 0, 255);
    FastLED[1].showLeds();
    FastLED[0].showLeds();
    
    toggle = 0;
    dim = 0;
    value = 220;
    updateFrameCount();
    frameCountPrev = frameCount;
    while (frameCount < 4465){
      updateFrameCount();
      if (frameCount != frameCountPrev) {
        FillLEDsFromPalette(STleds, middle, 8, d, 64);
        d = d - (frameCount - 3800)/70;
        for (t = 0; t < middle; t++){ STleds[NUM_LEDS_ST - t - 4] = STleds[t]; } //mirror
        fillHSV(STleds, NUM_LEDS_ST - 3, NUM_LEDS_ST, 0, 0, 0);
        FastLED[0].showLeds();

        if (((frameCount - 4115) % 11 == 0) && frameCount < 4292){
          value = 220;
          switch (toggle){
          case 0:
            fillRGB(TRleds, 0, NUM_LEDS_TR - 1, value, value, value);
            FastLED[1].showLeds();
            toggle++;
            break;
          case 1:
            setwhiteledpins(0, value/6);
            setwhiteledpins(1, value/6);
            toggle++;
            break;
          case 2:
            setwhiteledpins(2, value/6);
            setwhiteledpins(3, value/6);
            toggle++;
            break;
          case 3:
            SQleds.DrawFilledRectangle(0, 0, 55, 7, CRGB(value/2,value/2,value/2));
            FastLED[2].showLeds();
            
            toggle = 0;
            break;
          }
          dim = 1;
          k = 1;
        }

        if (dim == 1 && frameCount < 4292){
          value = value - k*4;
          switch (toggle){
          case 0:
            SQleds.DrawFilledRectangle(0, 0, 55, 7, CRGB(value/2,value/2,value/2));
            FastLED[2].showLeds();
            break;
          case 1:
            fillRGB(TRleds, 0, NUM_LEDS_TR - 1, value, value, value);
            FastLED[1].showLeds();
            break;
          case 2:
            setwhiteledpins(0, value/6);
            setwhiteledpins(1, value/6);
            break;
          case 3:
            setwhiteledpins(2, value/6);
            setwhiteledpins(3, value/6);
            break;
          }
          if (value == 0){
            dim = 0;
          }
          k++;
        }
       
        if (frameCount == 4292){
          SQleds.DrawFilledRectangle(0, 0, 55, 7, CRGB(0,0,0));
          FastLED[2].showLeds();
        }
        
        if (frameCount >= 4292 && ((frameCount - 4292) % 6 == 0) && frameCount < 4395){
          value = 220;
          switch (toggle){
          case 0:
            setwhiteledpins(0, value/6);
            setwhiteledpins(2, value/6);
            toggle++;
            break;
          case 1:
            setwhiteledpins(1, value/6);
            setwhiteledpins(3, value/6);
            toggle = 0;
            break;
          }
          dim = 1;
          k = 1;
        }

        if (dim == 1 && frameCount > 4292){
          value = value - k*22;
          switch (toggle){
          case 0:
            setwhiteledpins(1, value/6);
            setwhiteledpins(3, value/6);
            break;
          case 1:
            setwhiteledpins(0, value/6);
            setwhiteledpins(2, value/6);
            break;
          }
          if (value == 0){
            dim = 0;
          }
          k++;
        }

        if (frameCount > 4398){
          fillHSV(TRleds, 0, NUM_LEDS_TR - 1, 0, 0, 255);
          if (toggle == 0){
            setrgbledpins(0, 255, 255, 255);
            setwhiteledpins(0, 60);
            setwhiteledpins(2, 60);
            toggle = 1;
          }else{
            setrgbledpins(1, 255, 255, 255);
            setwhiteledpins(1, 60);
            setwhiteledpins(3, 60);
            toggle = 0;
          }
          FastLED[1].showLeds();
          waitForFrameChange();
          turnOff(1,1,1,1,1,1,1,1,1);
          waitForFrameChange();
        }

      }
      frameCountPrev = frameCount;
    }
    turnOff(1,1,1,1,1,1,1,1,1);
    waitTO(4475);
    for(i = 1; i <= 12; i++){
      fillHSV(STleds, middle - i*18, middle - i*18 + 72 - 1, 0, 0, 255);
      SQleds.DrawFilledRectangle(28 - i*2, 0, 28, 7, CRGB(255,255,255));
      SQleds.DrawFilledRectangle(28, 0, 28 + i*2, 7, CRGB(255,255,255));
      for (t = 0; t < middle; t++){ STleds[NUM_LEDS_ST - t - 4] = STleds[t]; } //mirror
      fillHSV(STleds, NUM_LEDS_ST - 3, NUM_LEDS_ST - 1, 0, 0, 0);
      FastLED[0].showLeds();
      FastLED[2].showLeds();
      waitForFrameChange();
    }
    turnOff(1,1,1,1,1,1,1,0,1);
}
void flagParade(){
    c = 0;
    t = 0;
    d = 0;
    flag = 0;
    updateFrameCount();
    while (frameCount < 6558){
      updateFrameCount();
      if (frameCount != frameCountPrev) {
        if ((frameCount - 4489) % 94 == 0){
          //update letters
          letter1[0] = {countries[c*3]};
          letter2[0] = {countries[c*3+1]};
          letter3[0] = {countries[c*3+2]};
          //flags
          switch (c){
            case 0:
              //armenia
              fillHSV(TRleds, 0, 69, 35, 255, 255); //yellow
              fillHSV(TRleds, 70, 89, 149, 255, 232); //blue
              fillHSV(TRleds, 90, 125, 255, 255, 245); //red
              fillHSV(TRleds, 126, 145, 149, 255, 232); //blue
              fillHSV(TRleds, 146, 161, 35, 255, 255); //yellow
              
              SQleds.DrawFilledRectangle(0, 0, 7, 1, CHSV(35, 255, 255)); //yellow
              SQleds.DrawFilledRectangle(0, 2, 7, 5, CHSV(149, 255, 232)); //blue
              SQleds.DrawFilledRectangle(0, 6, 7, 7, CHSV(255, 255, 245)); // red
              break;
            case 1:
              //australia
              fillHSV(TRleds, 0, 161, 161, 255, 188); //blue
              fillHSV(TRleds, 83, 88, 0, 0, 255); //white
              fillHSV(TRleds, 90, 100, 0, 0, 255); //white
              fillHSV(TRleds, 86, 87, 1, 255, 255); //red
              fillHSV(TRleds, 92, 98, 1, 255, 255); //red
              TRleds[58].setHSV(0,0,255); //white
              TRleds[63].setHSV(0,0,255); //white
              TRleds[130].setHSV(0,0,255); //white
              TRleds[154].setHSV(0,0,255); //white
              TRleds[156].setHSV(0,0,255); //white
              
              SQleds.DrawFilledRectangle(0, 0, 7, 7, CHSV(161, 255, 188)); //blue
              SQleds(2,2) = CHSV(0, 0, 255); //white
              SQleds(5,1) = CHSV(0, 0, 255); //white
              SQleds(7,4) = CHSV(0, 0, 188); //white
              SQleds(0,5) = CHSV(0, 0, 188); //white
              SQleds(0,7) = CHSV(0, 0, 188); //white
              SQleds(4,5) = CHSV(0, 0, 188); //white
              SQleds(4,7) = CHSV(0, 0, 188); //white
              SQleds.DrawLine(0, 6, 4, 6, CHSV(1, 255, 255)); //red
              SQleds(2,7) = CHSV(1, 255, 255); //red
              SQleds(2,5) = CHSV(1, 255, 255); //red
              break;
            case 2:
              //azerbaijan
              fillHSV(TRleds, 0, 69, 90, 255, 209); //green
              fillHSV(TRleds, 70, 89, 250, 255, 255); //red
              fillHSV(TRleds, 90, 125, 141, 255, 255); //lightblue
              fillHSV(TRleds, 126, 145, 250, 255, 255); //red
              fillHSV(TRleds, 146, 161, 90, 255, 209); //green
              
              SQleds.DrawFilledRectangle(0, 0, 7, 1, CHSV(90, 255, 209)); //green
              SQleds.DrawFilledRectangle(0, 2, 7, 5, CHSV(250, 255, 255)); //red
              SQleds.DrawFilledRectangle(0, 6, 7, 7, CHSV(141, 255, 255)); //lightblue
              SQleds.DrawFilledRectangle(4, 3, 5, 4, CHSV(0, 0, 200)); //white
              SQleds(3,2) = CHSV(0, 0, 200); //white
              SQleds(3,5) = CHSV(0, 0, 200); //white
              SQleds(2,3) = CHSV(0, 0, 200); //white
              SQleds(2,4) = CHSV(0, 0, 200); //white
              break;
            case 3:
              //belarus
              fillHSV(TRleds, 0, 44, 96, 220, 255); //green
              fillHSV(TRleds, 146, 161, 96, 220, 255); //green
              fillHSV(TRleds, 45, 145, 254, 255, 255); //red
              for (uint8_t led = 47; led < 76; led = led + 3){
                TRleds[led].setHSV(0,0,200); //white
              }
              
              SQleds.DrawFilledRectangle(2, 0, 7, 2, CHSV(96, 220, 255)); //green
              SQleds.DrawFilledRectangle(0, 3, 7, 7, CHSV(254, 255, 255)); //red
              SQleds(1,0) = CHSV(254, 255, 255); //red
              SQleds(0,1) = CHSV(254, 255, 255); //red
              SQleds(1,2) = CHSV(254, 255, 255); //red
              SQleds(0,0) = CHSV(0, 0, 128); //white
              SQleds(1,1) = CHSV(0, 0, 128); //white
              SQleds(0,2) = CHSV(0, 0, 128); //white
              SQleds(1,3) = CHSV(0, 0, 128); //white
              SQleds(0,4) = CHSV(0, 0, 128); //white
              SQleds(1,5) = CHSV(0, 0, 128); //white
              SQleds(0,6) = CHSV(0, 0, 128); //white
              SQleds(1,7) = CHSV(0, 0, 128); //white
              break;
            case 4:
              //belgium
              fillHSV(TRleds, 38, 90, 0, 0, 0); //black
              fillHSV(TRleds, 16, 37, 51, 255, 255); //yellow
              fillHSV(TRleds, 91, 124, 51, 255, 255); //yellow
              fillHSV(TRleds, 0, 15, 254, 255, 255); //red
              fillHSV(TRleds, 125, 161, 254, 255, 255); //red
              
              SQleds.DrawFilledRectangle(0, 0, 1, 7, CHSV(0,0,0)); //black
              SQleds.DrawFilledRectangle(3, 0, 5, 7, CHSV(51, 255, 255)); //yellow
              SQleds.DrawFilledRectangle(6, 0, 7, 7, CHSV(254, 255, 255)); //red
              break;
            case 5:
              //bulgaria
              fillHSV(TRleds, 0, 69, 3, 255, 235); //red
              fillHSV(TRleds, 70, 89, 120, 255, 255); //green
              fillHSV(TRleds, 90, 125, 0, 0, 215); //white
              fillHSV(TRleds, 126, 145, 120, 255, 255); //green
              fillHSV(TRleds, 146, 161, 3, 255, 235); //red
              
              SQleds.DrawFilledRectangle(0, 0, 7, 1, CHSV(3, 255, 235)); //red
              SQleds.DrawFilledRectangle(0, 2, 7, 5, CHSV(120, 255, 255)); //green
              SQleds.DrawFilledRectangle(0, 6, 7, 7, CHSV(0, 0, 215)); //white
              break;
            case 6:
              //cyprus
              fillHSV(TRleds, 0, 161, 0, 0, 225); //white
              fillHSV(TRleds, 75, 88, 40, 255, 255); //orange
              fillHSV(TRleds, 122, 132, 40, 255, 255); //orange
              
              SQleds.DrawFilledRectangle(0, 0, 7, 7, CHSV(0, 0, 180)); //white
              SQleds.DrawFilledRectangle(1, 3, 6, 5, CHSV(40, 255, 255)); //orange
              SQleds(6,6) = CHSV(40, 255, 255); //orange
              SQleds(7,6) = CHSV(40, 255, 255); //orange
              SQleds.DrawFilledRectangle(2, 1, 6, 2, CHSV(86, 220, 200)); //green
              SQleds(3,2) = CHSV(40, 255, 255); //orange
              SQleds(1,2) = CHSV(86, 220, 200); //green
              SQleds(6,3) = CHSV(86, 220, 200); //green
              SQleds(1,5) = CHSV(0, 0, 180); //white
              SQleds(6,1) = CHSV(0, 0, 180); //white
              break;
            case 7:
              //denmark
              fillHSV(TRleds, 0, 161, 250, 255, 247); //red
              fillHSV(TRleds, 32, 38, 0, 0, 225); //white
              fillHSV(TRleds, 76, 84, 0, 0, 225); //white
              fillHSV(TRleds, 87, 100, 0, 0, 225); //white
              fillHSV(TRleds, 131, 139, 0, 0, 225); //white
              
              SQleds.DrawFilledRectangle(0, 0, 7, 7, CHSV(250, 255, 247)); //red
              SQleds.DrawFilledRectangle(2, 0, 3, 7, CHSV(0, 0, 225)); //white
              SQleds.DrawFilledRectangle(0, 3, 7, 4, CHSV(0, 0, 225)); //white
              break;
            case 8:
              //finland
              fillHSV(TRleds, 0, 161, 0, 0, 225); //white
              fillHSV(TRleds, 31, 47, 157, 255, 215); //blue
              fillHSV(TRleds, 70, 102, 157, 255, 215); //blue
              fillHSV(TRleds, 122, 140, 157, 255, 215); //blue

              SQleds.DrawFilledRectangle(0, 0, 7, 7, CHSV(0, 0, 225)); //white
              SQleds.DrawFilledRectangle(2, 0, 3, 7, CHSV(157, 255, 215)); //blue
              SQleds.DrawFilledRectangle(0, 3, 7, 4, CHSV(157, 255, 215)); //blue
              break;
            case 9:
              //greece
              fillHSV(TRleds, 0, 161, 147, 255, 215); //lightblue
              fillHSV(TRleds, 57, 63, 0, 0, 225); //white
              fillHSV(TRleds, 70, 76, 0, 0, 225); //white
              fillHSV(TRleds, 97, 103, 0, 0, 225); //white
              fillHSV(TRleds, 112, 118, 0, 0, 225); //white
              fillHSV(TRleds, 152, 158, 0, 0, 225); //white
              fillHSV(TRleds, 139, 145, 0, 0, 225); //white
              fillHSV(TRleds, 125, 131, 0, 0, 225); //white
              fillHSV(TRleds, 112, 118, 0, 0, 225); //white
              
              SQleds.DrawLine(0, 0, 7, 0, CHSV(0, 0, 225)); //white
              SQleds.DrawLine(0, 1, 7, 1, CHSV(147, 255, 215)); //lightblue
              SQleds.DrawLine(0, 2, 7, 2, CHSV(0, 0, 225)); //white
              SQleds.DrawFilledRectangle(0, 3, 7, 7, CHSV(147, 255, 215)); //lightblue
              SQleds.DrawLine(0, 5, 4, 5, CHSV(0, 0, 225)); //white
              SQleds.DrawLine(2, 3, 2, 7, CHSV(0, 0, 225)); //white
              SQleds.DrawLine(5, 4, 7, 4, CHSV(0, 0, 225)); //white
              SQleds.DrawLine(5, 6, 7, 6, CHSV(0, 0, 225)); //white
              break;
            case 10:
              //italy
              fillHSV(TRleds, 38, 90, 100, 255, 255); //green
              fillHSV(TRleds, 16, 37, 0, 0, 255); //white
              fillHSV(TRleds, 91, 124, 0, 0, 255); //white
              fillHSV(TRleds, 0, 15, 0, 250, 255); //red
              fillHSV(TRleds, 125, 161, 0, 250, 255); //red
              
              SQleds.DrawFilledRectangle(0, 0, 1, 7, CHSV(100, 255, 255)); //green
              SQleds.DrawFilledRectangle(2, 0, 5, 7, CHSV(0, 0, 225)); //white
              SQleds.DrawFilledRectangle(6, 0, 7, 7, CHSV(0, 250, 255)); //red
              break;
            case 11:
              //lithuania
              fillHSV(TRleds, 0, 69, 0, 250, 225); //red
              fillHSV(TRleds, 70, 89, 100, 255, 209); //green
              fillHSV(TRleds, 90, 125, 37, 255, 255); //yellow
              fillHSV(TRleds, 126, 145, 100, 255, 209); //green
              fillHSV(TRleds, 146, 161, 0, 250, 225); //red
              
              SQleds.DrawFilledRectangle(0, 0, 7, 1, CHSV(0, 250, 225)); //red
              SQleds.DrawFilledRectangle(0, 2, 7, 5, CHSV(100, 255, 209)); //green
              SQleds.DrawFilledRectangle(0, 6, 7, 7, CHSV(37, 255, 255)); //yellow
              break;
            case 12:
              //malta
              fillHSV(TRleds, 0, 26, 0, 250, 255); //red
              fillHSV(TRleds, 108, 161, 0, 250, 255); //red
              fillHSV(TRleds, 27, 107, 0, 0, 255); //white
              
              SQleds.DrawFilledRectangle(0, 0, 3, 7, CHSV(0, 0, 225)); //white
              SQleds.DrawFilledRectangle(4, 0, 7, 7, CHSV(0, 250, 255)); //red
              SQleds.DrawLine(0, 6, 2, 6, CHSV(0, 0, 0)); //black
              SQleds.DrawLine(1, 5, 1, 7, CHSV(0, 0, 0)); //black
              break;
            case 13:
              //norway
              fillHSV(TRleds, 0, 28, 254, 255, 247); //red
              fillHSV(TRleds, 29, 41, 0, 0, 255); //white
              fillHSV(TRleds, 32, 38, 160, 255, 255); //blue
              fillHSV(TRleds, 42, 72, 254, 255, 247); //red
              fillHSV(TRleds, 73, 107, 0, 0, 255); //white
              fillHSV(TRleds, 76, 83, 160, 255, 255); //blue
              fillHSV(TRleds, 88, 100, 160, 255, 255); //blue
              fillHSV(TRleds, 108, 127, 254, 255, 247); //red
              fillHSV(TRleds, 128, 142, 0, 0, 255); //white
              fillHSV(TRleds, 132, 139, 160, 255, 255); //blue
              fillHSV(TRleds, 143, 161, 254, 255, 247); //red
              
              SQleds.DrawLine(0, 2, 7, 2, CHSV(0, 0, 225)); //white
              SQleds.DrawLine(0, 5, 7, 5, CHSV(0, 0, 225)); //white
              SQleds.DrawLine(1, 0, 1, 7, CHSV(0, 0, 225)); //white
              SQleds.DrawLine(4, 0, 4, 7, CHSV(0, 0, 225)); //white
              SQleds.DrawFilledRectangle(0, 3, 7, 4, CHSV(160, 255, 255)); //blue
              SQleds.DrawFilledRectangle(2, 0, 3, 7, CHSV(160, 255, 255)); //blue
              SQleds.DrawFilledRectangle(5, 0, 7, 1, CHSV(254, 255, 247)); //red
              SQleds.DrawFilledRectangle(5, 6, 7, 7, CHSV(254, 255, 247)); //red
              SQleds.DrawLine(0, 0, 0, 1, CHSV(254, 255, 247)); //red
              SQleds.DrawLine(0, 6, 0, 7, CHSV(254, 255, 247)); //red
              break;
            case 14:
              //poland
              fillHSV(TRleds, 0, 79, 250, 255, 247); //red
              fillHSV(TRleds, 136, 161, 250, 255, 247); //red
              fillHSV(TRleds, 80, 135, 0, 0, 255); //white
              
              SQleds.DrawFilledRectangle(0, 0, 7, 3, CHSV(250, 255, 247)); //red
              SQleds.DrawFilledRectangle(0, 4, 7, 7, CHSV(0, 0, 225)); //white
              break;
            case 15:
              //russia
              fillHSV(TRleds, 0, 69, 0, 250, 255); //red
              fillHSV(TRleds, 70, 89, 160, 255, 255); //blue
              fillHSV(TRleds, 90, 125, 0, 0, 215); //white
              fillHSV(TRleds, 126, 145, 160, 255, 255); //blue
              fillHSV(TRleds, 146, 161, 0, 250, 255); //red
              
              SQleds.DrawFilledRectangle(0, 0, 7, 1, CHSV(0, 250, 255)); //red
              SQleds.DrawFilledRectangle(0, 2, 7, 5, CHSV(160, 255, 255)); //blue
              SQleds.DrawFilledRectangle(0, 6, 7, 7, CHSV(0, 0, 215)); //white
              break;
            case 16:
              //slovenia
              fillHSV(TRleds, 0, 69, 0, 255, 255); //red
              fillHSV(TRleds, 70, 89, 150, 255, 255); //blue
              fillHSV(TRleds, 90, 125, 0, 0, 215); //white
              fillHSV(TRleds, 126, 145, 150, 255, 255); //blue
              fillHSV(TRleds, 146, 161, 0, 255, 255); //red
              
              SQleds.DrawFilledRectangle(0, 0, 7, 1, CHSV(0, 255, 255)); //red
              SQleds.DrawFilledRectangle(0, 2, 7, 5, CHSV(150, 255, 255)); //blue
              SQleds.DrawFilledRectangle(0, 6, 7, 7, CHSV(0, 0, 215)); //white
              SQleds(2,5) = CHSV(0, 0, 215); //white
              SQleds(2,6) = CHSV(150, 255, 255); //blue
              break;
            case 17:
              //spain
              fillHSV(TRleds, 0, 64, 0, 255, 255); //red
              fillHSV(TRleds, 96, 119, 0, 255, 255); //red
              fillHSV(TRleds, 151, 161, 0, 255, 255); //red
              fillHSV(TRleds, 65, 95, 41, 255, 255); //yellow
              fillHSV(TRleds, 120, 150, 41, 255, 255); //yellow
              
              SQleds.DrawFilledRectangle(0, 0, 7, 2, CHSV(0, 255, 255)); //red
              SQleds.DrawFilledRectangle(0, 6, 7, 7, CHSV(0, 255, 255)); //red
              SQleds.DrawFilledRectangle(0, 2, 7, 5, CHSV(41, 255, 255)); //yellow
              SQleds.DrawFilledRectangle(1, 3, 2, 4, CHSV(0, 0, 0)); //black
              break;
            case 18:
              //sweden
              fillHSV(TRleds, 0, 161, 147, 255, 215); //lightblue
              fillHSV(TRleds, 32, 42, 51, 255, 255); //yellow
              fillHSV(TRleds, 74, 100, 51, 255, 255); //yellow
              fillHSV(TRleds, 130, 141, 51, 255, 255); //yellow
              
              SQleds.DrawFilledRectangle(0, 0, 7, 7, CHSV(147, 255, 215)); //lightblue
              SQleds.DrawFilledRectangle(2, 0, 3, 7, CHSV(51, 255, 255)); //yellow
              SQleds.DrawFilledRectangle(0, 3, 7, 4, CHSV(51, 255, 255)); //yellow
              break;
            case 19:
              //switzerland
              fillHSV(TRleds, 0, 161, 0, 255, 255); //red
              fillHSV(TRleds, 21, 32, 0, 0, 255); //white
              fillHSV(TRleds, 73, 86, 0, 0, 255); //white
              fillHSV(TRleds, 100, 115, 0, 0, 255); //white
              fillHSV(TRleds, 129, 142, 0, 0, 255); //white
              
              SQleds.DrawFilledRectangle(0, 0, 7, 7, CHSV(0, 255, 255)); //red
              SQleds.DrawFilledRectangle(3, 1, 4, 6, CHSV(0, 0, 225)); //white
              SQleds.DrawFilledRectangle(1, 3, 6, 4, CHSV(0, 0, 225)); //white
              break;
            case 20:
              //the netherlands
              fillHSV(TRleds, 0, 69, 165, 255, 235); //blue
              fillHSV(TRleds, 70, 89, 00, 0, 255); //white
              fillHSV(TRleds, 90, 125, 0, 250, 255); //red
              fillHSV(TRleds, 126, 145, 0, 0, 255); //white
              fillHSV(TRleds, 146, 161, 165, 255, 235); //blue
              
              SQleds.DrawFilledRectangle(0, 0, 7, 1, CHSV(165, 255, 235)); //blue
              SQleds.DrawFilledRectangle(0, 2, 7, 5, CHSV(0, 0, 225)); //white
              SQleds.DrawFilledRectangle(0, 6, 7, 7, CHSV(0, 250, 255)); //red
              break;
            case 21:
              //ukraine
              fillHSV(TRleds, 0, 79, 64, 255, 255); //yellow
              fillHSV(TRleds, 136, 161, 64, 255, 255); //yellow
              fillHSV(TRleds, 80, 135, 147, 255, 215); //lightblue
              
              SQleds.DrawFilledRectangle(0, 0, 7, 3, CHSV(64, 255, 255)); //yellow
              SQleds.DrawFilledRectangle(0, 4, 7, 7, CHSV(147, 255, 215)); //lightblue
              break;
          }
          
          c++;
          flag = 1;
          t = 0;
        }
        //show and hide letters
        if (((((frameCount - 4489 - 45 + 94) % 94 == 0) && flag == 1) || (((frameCount - 4489 - 88 + 94) % 94 == 0) && flag == 2)) || ((t > 0) && (t <= 6))){
          if (t < 6){
            if (random8(0,2)){
              Holder3.SetText((unsigned char *)letter1, 1);
              Holder3.UpdateText();
            }else{
              SQleds.DrawFilledRectangle(16, 0, 23, 7, CRGB(0,0,0));
            }
            if (random8(0,2)){
              Holder4.SetText((unsigned char *)letter2, 1);
              Holder4.UpdateText();
            }else{
              SQleds.DrawFilledRectangle(24, 0, 31, 7, CRGB(0,0,0));
            }
            if (random8(0,2)){
              Holder5.SetText((unsigned char *)letter3, 1);
              Holder5.UpdateText();
            }else{
              SQleds.DrawFilledRectangle(32, 0, 39, 7, CRGB(0,0,0));
            }
            t++;
          }
          if (t == 6){
            if (flag == 2){
              SQleds.DrawFilledRectangle(16, 0, 39, 7, CRGB(0,0,0));
              flag = 0;
              t = 0;
            }
            if (flag == 1){
              Holder3.SetText((unsigned char *)letter1, 1);
              Holder3.UpdateText();
              Holder4.SetText((unsigned char *)letter2, 1);
              Holder4.UpdateText();
              Holder5.SetText((unsigned char *)letter3, 1);
              Holder5.UpdateText();
              flag = 2;
              t = 0;
            }
          }
        }
        FastLED.show();
      }
      frameCountPrev = frameCount;
    }
}

Those country flags look like a good place to start. Each flag could be stored as data in a separate file on an SD card with file names like "country16.dat" for Slovenia. The file could simply contain 3 bytes for each pixel in the matrix, i.e. 56 X ? X 3 bytes (the matrix height is -8? What does that mean?) and each 3 bytes represent the r, g, b values of that led. You could move the code above to another sketch which would render each flag in turn, then save each to the appropriate file name. Your main sketch could then simply read each file as required and display it.

Maybe other images that appear on the matrix could be stored in files this way too. Only one function would be needed to read any given file and display the image on the matrix.

This is a very clean way to do it, I have to get the SD module and try it out.

For the library that I'm using it simply means that (0,0) is the bottom-left corner (8 would be top-left) which makes more sense to me.

It's a good start but even though it's a big chunk of data (around 7K) it surely won't save enough to make the whole thing fit. In fact I wasn't planning to use this technique for other parts of the show, it was just for the flags at the beginning, so it would only save on those 7K.

Any other ideas while I try this out?

I realised later that most of the flag images only contain a small number of different colours: red, white, yellow etc. So instead of 3 bytes for each pixel in the SD card files, you could just use 1 character per pixel, encoding 'R' for red, 'W' for white, '.' for black/off and so on. Each file would contain 8 lines of 56 characters. This would mean you could easily create them on the PC using Notepad and save them to the SD card. The Arduino would read the file line by line, column by column and colour the pixel red when it sees an 'R' etc.

Getting more sophisticated, a single file could contain many images (8 rows of 56 characters) in sequence with control codes between them describing how many frames each image should be displayed for, and maybe other commands to control the other led strips. In effect, you would invent your own little 'language' to use inside the file. The Arduino would read the file and execute the sequences of commands in it. The file really would be like the music roll in a player piano.

For example, you might use '@' at the start of a line in the file followed by a number:

@4488

The Arduino would read the '@' and then know to read a number. It would then call waitTO(number)

You might use a '$' like this

$1, 80, 80, 80

The Arduino would read the '$' and know to read 4 numbers after it. It would then call setrgbledpins(1, 80, 80, 80) .

I have to thank you for your help but after some tinkering with your approach, I've decided to switch to the Teensy. I don't have the time and coding experience to develop this custom language and interpreter. It's a super cool and neat solution but very long to make and test, for which I don't have enough free time.
I've tried and my code compiles with just a few warnings on the Teensyduino so it would just have to fix a few lines and switch some electronics. The incentive to do this is also the increased processing power which can allow for cooler complex animations without having to worry much about optimization, which for a long unique show would take too much time.
Again, thank you for your input, maybe I'll revisit this approach in the future.

I disagree, but it's your project and your decision. Good luck.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.