LED matrix control using arrays and state machine?

I actually wrote that before I knew what a state machine was lol

The title of this thread is a question, I'm asking how to change what I'm doing to work more like a state machine, if that's what's required :slight_smile:

Still too much for interfacing 3.3V logic.

Absolutely! I'm glad nothing was damaged. I'm switching to ground now :slight_smile:

I got this change to work as you recommended. Not sure what I was doing wrong before!

So some progress last night. I was able to run an array sequence (which I call an animation, not sure if that's an appropriate term) using millis

I put

unsigned long time_now = 0; in before setup

time_now = millis(); in loop

and changed this

  if (totalState == 6){ //turn brake
               while(millis() < time_now + 20)
                RenderFrame (ledarraytb1);
               while(millis() < time_now + 40) 
                RenderFrame (ledarraytb2);
               while(millis() < time_now + 60)                 
                RenderFrame (ledarraytb3);
               while(millis() < time_now + 80)                 
                RenderFrame (ledarraytb4);
               while(millis() < time_now + 100)                 
                RenderFrame (ledarraytb5);
               while(millis() < time_now + 120)                 
                RenderFrame (ledarraybrake);
                }

This runs the animation, but it still can't be interrupted by a change of "int totalState"

So it's great that I'm not using delay, but this basically acts the same. :frowning:

I feel like this is getting "close" but I'm obviously still missing some fundamentals here

Well, you created blocking code... you're not following the design pattern of checking the time stamp, and moving on if it's not expired. Instead you hang around in a while loop until it expires. So basically the same as delay().

That's pretty much what I figured was going on.
Do you have any advice on how to adjust this?

You have to fully absorb the concept and methodology in the tutorials that were linked to. They do a better job than the potential 30 posts here from people trying to explain it.

Considering your actual problem, what I would do, is put your arrays together in a 2 dimensional array so they can be indexed. Then just set up a counter to loop through them using the counter as an index. Use the usual blink without delay code to make it happen every (for you) 60ms.

Or, if they really must be named, create another array of pointers to your arrays.

Getting rid of the pedestrian sequence of array1, wait, array2, wait and so on will dramatically simplify the timing logic.

You could do it with a state machine, but a state machine that simply goes through the same sequence of same steps and repeats without any differences, is not worth the effort. That is better done with simple indexing.

While you're at it, ditch the unnecessary and cumbersome assignment of input switch readings to binary weighted states that you later test. It almost completely obfuscates the meaning of the test conditions. Just read the switch states and test the variables that you read, directly.

I haven't tried any indexing or 2d arrays yet. Reading up on that now.

Are there specific examples or tutorials you know of?

Thank you

Sorry to complicate things... but your arrays should also be in PROGMEM because they are large and could use up all the available memory. What processor are you using?

I thought putting them as "const uint32_t" vs "uint32_t" accomplished basically the same thing?
When I changed them to that, the report after verify sketch shows the data has moved from dynamic memory to program storage space.

I'm using a nano lot33, with all my arrays it says I've used only 23%, before was maxing out dynamic....

Yes, as you have discovered. Actually it's doing a whole lot more pixel pumping, but you don't see that and it doesn't matter. The key thing is that it is locked in the sequence and has no opportunity to check the input conditions.

In something like your code I had limping along this below. Where you had a literal multistep delay-mediated animation, I placed a call to a frame step function.

   if (state1 + state2 + state3 + state4 == 2) {  //turn
     turnAnimationStep();
   }

so just call turnAnimationStep(). Which I wrote on autopilot. I do not recommend that you proceed along this path I lay out here, but I believe it would be valuable for you to see it working literally, so you can run run fingers through it and so forth.

void turnAnimationStep()
{
	static byte frame = 0;		// reset to replay
	static unsigned long lastTime = 0;
	static unsigned long tween = 0;
	
	if (millis() - lastTime < tween)
    return;

  lastTime = millis();
  switch (frame) {
  case 0 :
    RenderFrame (ledarrayt0);
    tween = 60;	
    frame++;
    break;

  case 1 :
    RenderFrame (ledarrayt1);
    tween = 60;	
    frame++;
    break;

  case 2 :
    RenderFrame (ledarrayt2);
    tween = 60;	
    frame++;
    break;

  case 3 :
    RenderFrame (ledarrayt3);
    tween = 60;	
    frame++;
    break;

  case 4 :
    RenderFrame (ledarrayt4);
    tween = 60;	
    frame++;
    break;

  case 5 :
    RenderFrame (ledarrayt5);
    tween = 60;	
    frame++;
    break;

  case 6 :
    RenderFrame (ledarrayoff);
    tween = 60;	
    frame = 0;
    break;
	}
}

This task and the data you supply allow for many creative and beautiful solutions. Again, I do not suggest that you do all the if-tested animations with a cut-and-paste edited version of the one function I show above, but it would certainly work and not use delay and not interfere with response to system inputs.

And something like it will be at the heart of a fancier way of organizing your data and expressing your animations.

When I press 1, I get a loop time of about 13 ms most of which is just the delay() in the == 1 thing.

When I press 2, the loop time is < 2 ms, most of the time the calls to turnAnimationStep do nothing.

I removed the delay(10); you had after looking at a digital input. It seemed superstitious to me.

What timing and signals are coming in on the four inputs? Like times between high and low and any contact noise or whatever. Anything you can tell us.

HTH

a7

Not applicable on the SAMD processor used in the Nano 33 IOT.
On a Nano (atmega328 processor), you can also reduce the size of the arrays 25% by using __uint24 (24 bit unsigned int) instead of uint32_t. Rearrange the color order in the __uint24 array and you can use memcpy_P for a direct copy from PROGMEM to the LED array (integers are stored Least Significant Byte first).

Essentially correct, the const lets the compiler know that the data will not be altered, so it can store it in program memory. You could specify PROGMEM in the code, but it does nothing on the board you are using. Useful if you are writing portable code than needs to be compatible with boards that do implement PROGMEM.

Yes, PROGMEM is only necessary because the Harvard architecture of the AVR series, does not permit data to be accessed directly from ROM. Traditionally, any good C/C++ compiler will place constant data items in ROM.

I'm trying to test the function you showed.

Replacing my function "turn" with that, I get a "RenderFrame" not declared again.

Does this all need to exist as a single function? Or is some of this supposed to go in setup etc?

Do I need to declare the "RenderFrame" differently?

Sorry, this is a difficult learning curve for me.... Reminds me of when I was learning CAD :sweat_smile:

This works will some animation zones left empty

I'm sure this is sloppy and I have a lot of room to improve, but damn its working!

//tail lamp dev v3

#include <Adafruit_NeoPixel.h>
#include  "taillightarrays.h"

#define PIN 3

const int sensorPin1 = A0;    // select a input pin for control 1 RUN LAMP
const int sensorPin2 = A1;    // select a input pin for control 2 TURN SIGNAL
const int sensorPin3 = A2;    // select a input pin for control 3 BRAKE LIGHT
const int sensorPin4 = A3;    // select a input pin for control 4 REVERSE

int sensorValue1 = A0;  // variable to store the value coming from the sensor
int sensorValue2 = A1;  // variable to store the value coming from the sensor
int sensorValue3 = A2;  // variable to store the value coming from the sensor
int sensorValue4 = A3;  // variable to store the value coming from the sensor


const int NUM_PIXELS = 256;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_PIXELS, PIN, NEO_GRB);


void setup()
{
  strip.begin();
  strip.setBrightness(255);
  strip.show();   // Initialize all pixels to 'off'
  pinMode(A0, INPUT_PULLUP);
  pinMode(A1, INPUT_PULLUP);
  pinMode(A2, INPUT_PULLUP);
  pinMode(A3, INPUT_PULLUP);




}

void loop()
{
  

    int state1 = 0; // initialize state1 value to zero
    int state2 = 0; // initialize state2 value to zero
    int state3 = 0; // initialize state3 value to zero
    int state4 = 0; // initialize state4 value to zero


    if (digitalRead(sensorPin1) == LOW) // read the value from the sensor1:
      state1 = 1;
    else state1 = 0;


    if (digitalRead(sensorPin2) == LOW) // read the value from the sensor2:
      state2 = 2;
    else state2 = 0;


    if (digitalRead(sensorPin3) == LOW) // read the value from the sensor3:
      state3 = 4;
    else state3 = 0;


    if (digitalRead(sensorPin4) == LOW) // read the value from the sensor4:
      state4 = 8;
    else state4 = 0;


    int totalState = state1 + state2 + state3 + state4; //total of states to a single int

    //totals of state for which frame to render


    if (totalState == 0) { //off
      RenderFrame (ledarrayoff);
    }


    if (totalState == 1) { //run
      RenderFrame (ledarrayrun);
    }


    if (totalState == 2 or totalState == 3) { //turn anime turn run anime

      { void turnAnimeStep();
      
        static byte frame = 0;    // reset to replay
        static unsigned long lastTime = 0;
        static unsigned long tween = 0;

        if (millis() - lastTime < tween)
          return;

        lastTime = millis();
        switch (frame) {
          case 0 :
            RenderFrame (ledarrayt1);
            tween = 200;
            frame++;
            break;

          case 1 :
            RenderFrame (ledarrayt2);
            tween = 15;
            frame++;
            break;

          case 2 :
            RenderFrame (ledarrayt3);
            tween = 15;
            frame++;
            break;

          case 3 :
            RenderFrame (ledarrayt4);
            tween = 15;
            frame++;
            break;

          case 4 :
            RenderFrame (ledarrayt5);
            tween = 15;
            frame++;
            break;

          case 5 :
            RenderFrame (ledarrayt6);
            tween = 15;
            frame++;
            break;

          case 6 :
            RenderFrame (ledarrayt7);
            tween = 15;
            frame++;;
            break;

          case 7 :
            RenderFrame (ledarrayt8);
            tween = 15;
            frame++;;
            break;

          case 8 :
            RenderFrame (ledarrayt9);
            tween = 15;
            frame++;;
            break;            
          case 9 :
            RenderFrame (ledarrayt10);
            tween = 15;
            frame++;
            break;

          case 10 :
            RenderFrame (ledarrayt11);
            tween = 15;
            frame++;
            break;

          case 11 :
            RenderFrame (ledarrayt12);
            tween = 15;
            frame++;
            break;

          case 12 :
            RenderFrame (ledarrayt13);
            tween = 15;
            frame++;
            break;

          case 13 :
            RenderFrame (ledarrayt14);
            tween = 15;
            frame++;
            break;

          case 14 :
            RenderFrame (ledarrayt15);
            tween = 15;
            frame++;
            break;

          case 15 :
            RenderFrame (ledarrayt16);
            tween = 15;
            frame++;;
            break;

          case 16 :
            RenderFrame (ledarrayt17);
            tween = 15;
            frame++;;
            break;

          case 17 :
            RenderFrame (ledarrayt18);
            tween = 15;
            frame++;;
            break;    

          case 18 :
            RenderFrame (ledarrayt19);
            tween = 15;
            frame++;
            break;

          case 19 :
            RenderFrame (ledarrayt20);
            tween = 15;
            frame++;
            break;

          case 20 :
            RenderFrame (ledarrayt21);
            tween = 15;
            frame++;;
            break;

          case 21 :
            RenderFrame (ledarrayt22);
            tween = 15;
            frame++;;
            break;

          case 22 :
            RenderFrame (ledarrayt23);
            tween = 15;
            frame++;;
            break;

          case 23 :
            RenderFrame (ledarrayt24);
            tween = 15;
            frame = 0;
            break;                                
        }
      }

  }

  if (totalState == 4){ //brake
    RenderFrame (ledarraybrake);
  }

  if (totalState == 5){ //run brake
    RenderFrame (ledarraybrake);
  }
  
  if (totalState == 6){ //turn brake
}

  if (totalState == 8){ // reverse
    RenderFrame (ledarrayreverse);
  }


  if (totalState == 9){ // run reverese
    RenderFrame (ledarrayrunreverse);
  }

if (totalState == 10 or totalState == 11) {// turn reverse and run turn reverse
}

  if (totalState == 12 or totalState == 12){ // brake reverse
    RenderFrame (ledarraybrakereverse);
  }

  if (totalState == 13){ // run brake reverse
    RenderFrame (ledarraybrakereverse);
  }
  

if (totalState == 14 or totalState == 15){ // turn brake reverse
}


  
}

void RenderFrame(const uint32_t *arr)
{
  for (uint16_t t = 0; t < 256; t++)
  {
    strip.setPixelColor(t, arr[t]);
  }
  strip.show();

}

This, or whatever you have done to normalize it

if(state1 + state2 + state3 + state4 == 2) //turn
RenderFrame (ledarrayt0),
delay(60),
RenderFrame (ledarrayt1),
delay(60),
RenderFrame (ledarrayt2),
delay(60),
RenderFrame (ledarrayt3),
delay(60),
RenderFrame (ledarrayt4),
delay(60),
RenderFrame (ledarrayt5),
delay(60),
RenderFrame (ledarrayoff),
delay(60);

should be replaced by

   if (state1 + state2 + state3 + state4 == 2) {  //turn
     turnAnimationStep();
   }

and all of

void turnAnimationStep()
{
	static byte frame = 0;		// reset to replay
	static unsigned long lastTime = 0;
	static unsigned long tween = 0;
	
	if (millis() - lastTime < tween)
    return;

  lastTime = millis();
  switch (frame) {
  case 0 :
    RenderFrame (ledarrayt0);
    tween = 60;	
    frame++;
    break;

  case 1 :
    RenderFrame (ledarrayt1);
    tween = 60;	
    frame++;
    break;

  case 2 :
    RenderFrame (ledarrayt2);
    tween = 60;	
    frame++;
    break;

  case 3 :
    RenderFrame (ledarrayt3);
    tween = 60;	
    frame++;
    break;

  case 4 :
    RenderFrame (ledarrayt4);
    tween = 60;	
    frame++;
    break;

  case 5 :
    RenderFrame (ledarrayt5);
    tween = 60;	
    frame++;
    break;

  case 6 :
    RenderFrame (ledarrayoff);
    tween = 60;	
    frame = 0;
    break;
	}
}

added to the program as a function, functions go at the highest level in your program and ends up looking like setup() or loop(). It is wholly without their braces. I cannot be sure but it sounds like you've made an error of syntax, dunno where you are on how to use and declare functions. If you can't use your best common sense and reason by pattern here, post the code you are working with albeit a version that doesn't compile and/or work.

Is there any video example of something that you are trying to copy? Or have you made any of your own? That I can't run this for real is unfortunate.

a7

It's WORKING :slight_smile: :grinning:

Please see my last (updated) post

I had made some simple errors and now have a working base to build on

From here I'll build more frames for the other animations, and try to integrate your genius solution to all of them!

Thanks for all the help so far. I can feel my brain swelling as I try to absorb all this :joy:

WORKING with two anime frames now

//tail lamp dev v3

#include <Adafruit_NeoPixel.h>
#include  "taillightarrays.h"

#define PIN 3

const int sensorPin1 = A0;    // select a input pin for control 1 RUN LAMP
const int sensorPin2 = A1;    // select a input pin for control 2 TURN SIGNAL
const int sensorPin3 = A2;    // select a input pin for control 3 BRAKE LIGHT
const int sensorPin4 = A3;    // select a input pin for control 4 REVERSE

int sensorValue1 = A0;  // variable to store the value coming from the sensor
int sensorValue2 = A1;  // variable to store the value coming from the sensor
int sensorValue3 = A2;  // variable to store the value coming from the sensor
int sensorValue4 = A3;  // variable to store the value coming from the sensor


const int NUM_PIXELS = 256;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_PIXELS, PIN, NEO_GRB);


void setup()
{
  strip.begin();
  strip.setBrightness(255);
  strip.show();   // Initialize all pixels to 'off'
  pinMode(A0, INPUT_PULLUP);
  pinMode(A1, INPUT_PULLUP);
  pinMode(A2, INPUT_PULLUP);
  pinMode(A3, INPUT_PULLUP);




}

void loop()
{


  int state1 = 0; // initialize state1 value to zero
  int state2 = 0; // initialize state2 value to zero
  int state3 = 0; // initialize state3 value to zero
  int state4 = 0; // initialize state4 value to zero


  if (digitalRead(sensorPin1) == LOW) // read the value from the sensor1: //run
    state1 = 1;
  else state1 = 0;


  if (digitalRead(sensorPin2) == LOW) // read the value from the sensor2: //turn
    state2 = 2;
  else state2 = 0;


  if (digitalRead(sensorPin3) == LOW) // read the value from the sensor3: //brake
    state3 = 4;
  else state3 = 0;


  if (digitalRead(sensorPin4) == LOW) // read the value from the sensor4: //reverse
    state4 = 8;
  else state4 = 0;


  int totalState = state1 + state2 + state3 + state4; //total of states to a single int

  //totals of state for which frame to render


  if (totalState == 0) { //off
    RenderFrame (ledarrayoff);
  }


  if (totalState == 1) { //run
    RenderFrame (ledarrayrun);
  }


  if (totalState == 2 or totalState == 3) { //turn anime turn run anime

    { void turnAnimeStep();

      static byte frame = 0;    // reset to replay
      static unsigned long lastTime = 0;
      static unsigned long tween = 0;

      if (millis() - lastTime < tween)
        return;

      lastTime = millis();
      switch (frame) {
        case 0 :
          RenderFrame (ledarrayt1);
          tween = 200;
          frame++;
          break;

        case 1 :
          RenderFrame (ledarrayt2);
          tween = 15;
          frame++;
          break;

        case 2 :
          RenderFrame (ledarrayt3);
          tween = 15;
          frame++;
          break;

        case 3 :
          RenderFrame (ledarrayt4);
          tween = 15;
          frame++;
          break;

        case 4 :
          RenderFrame (ledarrayt5);
          tween = 15;
          frame++;
          break;

        case 5 :
          RenderFrame (ledarrayt6);
          tween = 15;
          frame++;
          break;

        case 6 :
          RenderFrame (ledarrayt7);
          tween = 15;
          frame++;
          break;

        case 7 :
          RenderFrame (ledarrayt8);
          tween = 15;
          frame++;
          break;

        case 8 :
          RenderFrame (ledarrayt9);
          tween = 15;
          frame++;
          break;
          
        case 9 :
          RenderFrame (ledarrayt10);
          tween = 15;
          frame++;
          break;

        case 10 :
          RenderFrame (ledarrayt11);
          tween = 15;
          frame++;
          break;

        case 11 :
          RenderFrame (ledarrayt12);
          tween = 15;
          frame++;
          break;

        case 12 :
          RenderFrame (ledarrayt13);
          tween = 15;
          frame++;
          break;

        case 13 :
          RenderFrame (ledarrayt14);
          tween = 15;
          frame++;
          break;

        case 14 :
          RenderFrame (ledarrayt15);
          tween = 15;
          frame++;
          break;

        case 15 :
          RenderFrame (ledarrayt16);
          tween = 15;
          frame++;
          break;

        case 16 :
          RenderFrame (ledarrayt17);
          tween = 15;
          frame++;
          break;

        case 17 :
          RenderFrame (ledarrayt18);
          tween = 15;
          frame++;
          break;

        case 18 :
          RenderFrame (ledarrayt19);
          tween = 15;
          frame++;
          break;

        case 19 :
          RenderFrame (ledarrayt20);
          tween = 15;
          frame++;
          break;

        case 20 :
          RenderFrame (ledarrayt21);
          tween = 15;
          frame++;
          break;

        case 21 :
          RenderFrame (ledarrayt22);
          tween = 15;
          frame++;
          break;

        case 22 :
          RenderFrame (ledarrayt23);
          tween = 15;
          frame++;
          break;

        case 23 :
          RenderFrame (ledarrayt24);
          tween = 15;
          frame = 0;
          break;
      }
    }

  }

  if (totalState == 4) { //brake
    RenderFrame (ledarraybrake);
  }

  if (totalState == 5) { //run brake
    RenderFrame (ledarraybrake);
  }

  if (totalState == 6) { //turn brake
    { void turnbrakeAnimeStep();

      static byte frame1 = 0;    // reset to replay
      static unsigned long lastTime1 = 0;
      static unsigned long tween1 = 0;

      if (millis() - lastTime1 < tween1)
        return;

      lastTime1 = millis();
      switch (frame1) {
        case 0 :
          RenderFrame (ledarraytb1);
          tween1 = 200;
          frame1++;
          break;

        case 1 :
          RenderFrame (ledarraytb2);
          tween1 = 15;
          frame1++;
          break;

        case 2 :
          RenderFrame (ledarraytb3);
          tween1 = 15;
          frame1++;
          break;

        case 3 :
          RenderFrame (ledarraytb4);
          tween1 = 15;
          frame1++;
          break;

        case 4 :
          RenderFrame (ledarraytb5);
          tween1 = 15;
          frame1++;
          break;

        case 5 :
          RenderFrame (ledarraytb6);
          tween1 = 15;
          frame1++;
          break;

        case 6 :
          RenderFrame (ledarraytb7);
          tween1 = 15;
          frame1++;
          break;

        case 7 :
          RenderFrame (ledarraytb8);
          tween1 = 15;
          frame1++;
          break;

        case 8 :
          RenderFrame (ledarraytb9);
          tween1 = 15;
          frame1++;
          break;
          
        case 9 :
          RenderFrame (ledarraytb10);
          tween1 = 15;
          frame1++;
          break;

        case 10 :
          RenderFrame (ledarraytb11);
          tween1 = 15;
          frame1++;
          break;

        case 11 :
          RenderFrame (ledarraytb12);
          tween1 = 15;
          frame1++;
          break;

        case 12 :
          RenderFrame (ledarraytb13);
          tween1 = 15;
          frame1++;
          break;

        case 13 :
          RenderFrame (ledarraytb14);
          tween1 = 15;
          frame1++;
          break;

        case 14 :
          RenderFrame (ledarraytb15);
          tween1 = 15;
          frame1++;
          break;

        case 15 :
          RenderFrame (ledarraytb16);
          tween1 = 15;
          frame1++;
          break;

        case 16 :
          RenderFrame (ledarraytb17);
          tween1 = 15;
          frame1++;
          break;

        case 17 :
          RenderFrame (ledarraytb18);
          tween1 = 15;
          frame1++;
          break;

        case 18 :
          RenderFrame (ledarraytb19);
          tween1 = 15;
          frame1++;
          break;

        case 19 :
          RenderFrame (ledarraytb20);
          tween1 = 15;
          frame1++;
          break;

        case 20 :
          RenderFrame (ledarraytb21);
          tween1 = 15;
          frame1++;
          break;

        case 21 :
          RenderFrame (ledarraytb22);
          tween1 = 15;
          frame1++;
          break;

        case 22 :
          RenderFrame (ledarraytb23);
          tween1 = 15;
          frame1 = 0;
          break;
      }
    }

  }

  if (totalState == 8) { // reverse
    RenderFrame (ledarrayreverse);
  }


  if (totalState == 9) { // run reverese
    RenderFrame (ledarrayrunreverse);
  }

  if (totalState == 10 or totalState == 11) {// turn reverse and run turn reverse
  }

  if (totalState == 12) { // brake reverse
    RenderFrame (ledarraybrakereverse);
  }

  if (totalState == 13) { // run brake reverse
    RenderFrame (ledarraybrakereverse);
  }


  if (totalState == 14 or totalState == 15) { // turn brake reverse
  }



}

void RenderFrame(const uint32_t *arr)
{
  for (uint16_t t = 0; t < 256; t++)
  {
    strip.setPixelColor(t, arr[t]);
  }
  strip.show();

}

You've taken my idea like a football and you are running for the goal but… you may be running towards the sidelines or the wrong and zone.

Please reread my post(s) carefully before you go any further.

Literally. All that should be in the loop() if things will look like this:

   if (state1 + state2 + state3 + state4 == 2) {  //turn
     turnAnimationStep();
   }

That is a "call" to another function, external to the loop() function.

Then we provide such functions, like the bigger block in my post is a function, in its entirety, which appears elsewhere in your sketch. So an Eagle eye view of your sketch would be

    void setup()
    {
            // blah blah blah all that setup stuff

    } // end of setup

    void loop()
    {

            // blah blah blah all that loop stuff including, however controlled calls like

       if (state1 + state2 + state3 + state4 == 2) {  //turn
          turnAnimationStep();
       }

// and

       if (state1 + state2 + state3 + state4 == 7) {  // whatever
           aDifferentAnimationStep();
       }



    } // end of loop

// animation state machines. call them frequently

    void turnAnimationStep() 
    {
            // blah blah blah, all the logic to take the next step if it is time, of ONE animation

    } // end of turnAnimationStep

// and if you go that direction

    void aDifferentAnimationStep()
    {
            // blah blah blah, all the logic for a different animation

    } // end of aDifferentAnimationStep

So that's four functions, the two you've worked with and two new ones that enjoy the same status as setup() and loop(). Call them when you need them.

Perhaps google

 functions in C/C++

and get a different view point.

If you think this was fun, wait until you see how this can all fold up into almost no code at all, at least by comparison. If you want a head start on that, google

 arrays in C/C++

Remember, I distinctly told you that I do not recommend this path. I do however think that getting it to work and enjoying the moment is prefectly OK. :expressionless:

Now we need video of what it is supposed to do actually now that it kinda sorta does it.

HTH

a7