LED matrix control using arrays and state machine?

Ahh I see!

That makes sense!

Do the names here need to change for each animation? IE change "lastTime" to "lastTime1", tween to tween1 or something like that?

Or because they are in their own function, is that not needed? Just change the void "turnAnimeStep" each and use that name in the loop.

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

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

Thanks for all your help

Yes, the only thing that has to change is the name seen from the outside like turnAnimationStep.

The variables declared inside a function's braces are as used here private and persistent. Each function if you go cut and paste crazy has its own copies without names getting confused. Only its outside name is important and must be unique.

Obvsly the insides of a cloned function would have to be changed to perform a different animation. I think you see the pattern, anyway.

a7

1 Like

Awesome! It's working the way you prescribed, and that's a lot easier to manage. Learning more and more on this stuff every day :slight_smile:

OKAY,

This is working will all my frames and animation, well at least for the left side tail lamp. Need to make all the right side animation frames now :sweat_smile:

Please critique:

//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
  turnAnimeStep();
  }

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


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


  if (totalState == 6 or totalState ==7) { //turn brake
    turnbrakeAnimeStep();
  }
    

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


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


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


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


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


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

}



//Anime state machines

//turn anime machine
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;
      }
    }

//turn brake anime machine
void turnbrakeAnimeStep()
{
      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 (ledarraytb1);
          tween = 200;
          frame++;
          break;

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

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

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

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

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

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

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

        case 8 :
          RenderFrame (ledarraytb9);
          tween = 15;
          frame++;
          break;
          
        case 9 :
          RenderFrame (ledarraytb10);
          tween = 15;
          frame++;
          break;

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

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

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

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

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

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

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

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

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

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

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

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

        case 22 :
          RenderFrame (ledarraytb23);
          tween = 15;
          frame = 0;
          break;
      }
}

//turn brake reverse anime machine
void turnbrakeReverseAnimeStep()
{
      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 (ledarraytbr1);
          tween = 200;
          frame++;
          break;

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

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

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

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

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

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

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

        case 8 :
          RenderFrame (ledarraytbr9);
          tween = 15;
          frame++;
          break;
          
        case 9 :
          RenderFrame (ledarraytbr10);
          tween = 15;
          frame++;
          break;

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

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

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

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

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

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

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

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

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

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

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

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

        case 22 :
          RenderFrame (ledarraytbr23);
          tween = 15;
          frame = 0;
          break;
      }
}

//turn reverse anime machine
void turnReverseAnimeStep()
{
      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 (ledarraytr1);
          tween = 200;
          frame++;
          break;

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

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

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

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

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

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

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

        case 8 :
          RenderFrame (ledarraytr9);
          tween = 15;
          frame++;
          break;
          
        case 9 :
          RenderFrame (ledarraytr10);
          tween = 15;
          frame++;
          break;

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

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

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

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

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

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

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

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

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

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

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

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

        case 22 :
          RenderFrame (ledarraytr23);
          tween = 15;
          frame = 0;
          break;
      }
}



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

}

Id like to post some videos here but not sure how, I assume external hosting is required

I don't know to whom I may have sold my soul for being able to, but putting things on youtube is very easy.

I point out that


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

and the other lines like it don't need the else part, the variable is already 0. Minor.

Now you are charging ahead on a bad path (I said so), but it is nice to see things working. I can't, so my efforts have been to get it to run with less memory.

You need to stop making progress and start learning about arrays. Anytime your code has variables that have numbers as part of their names is a chance to see if arrays could be used instead.

Also, you may see that all your state machine functions look very similar, no surprise there, and within them, the cases all look very similar.

Recognizing and exploiting patterns is key to making progress as a programmer. Currently my code to see your patterns has only one animation state machine and it is ready to handle anything that isn't too far out of the patterns you are working with.

Also also, I did manage to cut the data down considerably.

You only use three different pixel colors. A scheme allowing for 16 different colors code could encode each pixel as only 4 bits instead of 32.

But it gets better! You only have 5 unique 16 pixel lines in all the data. This means a 256 pixel pattern can be stored as just a list of 16 numbers, the numbers 0..4 corresponding to which of the five different lines is next in the pattern.

At the expense of some fiddling around during the rendering, for which there is plenty of time, I have reduced all the data to less than 500 bytes. Here is my data section. YES I have ditched a great deal of general flexibility.

/* these are the unique colors from the pattern arrays */

# define aBK	0		// a black color
# define aRD	1		// a red color
# define aWH	2		// a white color

uint32_t myColors[3] = {0x0, 0xff0000, 0xffffff};

/* these are the unique lines from the pattern arrays
	"................",
	"..====....====..",
	"==....====....==",
	"================",
	"XXXXXXXXXXXXXXXX",
*/

unsigned char uniqueLines[5][16] = {
	{aBK, aBK, aBK, aBK, aBK, aBK, aBK, aBK, aBK, aBK, aBK, aBK, aBK, aBK, aBK, aBK, },
	{aBK, aBK, aRD, aRD, aRD, aRD, aBK, aBK, aBK, aBK, aRD, aRD, aRD, aRD, aBK, aBK, },
	{aRD, aRD, aBK, aBK, aBK, aBK, aRD, aRD, aRD, aRD, aBK, aBK, aBK, aBK, aRD, aRD, },
	{aRD, aRD, aRD, aRD, aRD, aRD, aRD, aRD, aRD, aRD, aRD, aRD, aRD, aRD, aRD, aRD, },
	{aWH, aWH, aWH, aWH, aWH, aWH, aWH, aWH, aWH, aWH, aWH, aWH, aWH, aWH, aWH, aWH, },
};

# define NPATTERNS	22

unsigned char frames[NPATTERNS][16] = {
// pattern  0
	{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },

// pattern  1
	{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, },

// pattern  2
	{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 3, 3, 3, },

// pattern  3
	{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, },

// pattern  4
	{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, },

// pattern  5
	{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 3, 3, 3, },

// pattern  6
	{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, },

// pattern  7
	{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, },

// pattern  8
	{2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, },

// pattern  9
	{2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },

// pattern 10
	{2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },

// pattern 11
	{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },

// pattern 12
	{3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 0, 0, 3, 3, 3, },

// pattern 13
	{3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 0, 0, 3, 3, 3, },

// pattern 14
	{3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 0, 0, 3, 3, 3, },

// pattern 15
	{3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 3, 3, 3, },

// pattern 16
	{3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 3, 3, 3, },

// pattern 17
	{2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 4, 4, 0, 0, 0, },

// pattern 18
	{2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 4, 4, 0, 0, 0, },

// pattern 19
	{2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, },

// pattern 20
	{2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, },

// pattern 21
	{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
};

I hope this inspires you. I understand you may only want whizzy lighting system for your car, but if you do more projects you will def want to know more about arrays, functions and a few other steps up the big ladder.

a7

1 Like

First video demo. More to work on with the CAD part of the project, and i'll do some more fun stuff with the animations.

I'll be working on the code more to with what you suggested, but it's nice to have it working as desired now :slight_smile:

Nice, THX for going to the trouble.

The patterns make more sense at 32 x 8, haha, missed that tots and was just making the code work.

Now it is I who is inspired.

So… one Arduino controller in each taillight?

a7

1 Like

Yes, I'm working on "plug and play" units.

Each tail lamp assembly with have it's own nano, mini 5 way relay board, and 12-5v converter so it can just plug into the original wiring and work.

I'll do custom styles / options for people, different animations sequences, colors etc.

It's awesome to be able to integrate my CAD / 3d printing work with these new skills. The possibilities are endless :slight_smile:

The next part of this project would be able to upload / change patterns over bluetooth!

Without any ado I link here a wokwi of a taillight program which uses no data but instead places the focus on algorithmic determination of the pixel values.

At a huge loss of generality, yes. But looking at the video you posted made the whole project click different. The bit encoding of the sensors can be exploited - there is not much going on there when you look at it from a functional spec point of view, which is what your video finally provided.

Sadly my idea of fun on a rainy Sunday morning.

This thing could be squeezed onto an ATtiny, good thing I do not have any around. :expressionless:

LL&P

a7

1 Like

Okay, I'm back with more questions :slight_smile:

To prevent any wiring changes required in the car, Id like to make this work so that it finishes any animation sequence before returning to "off" mode

This should still be instantly interrupted by a change of display.

I was thinking, because I'm using an actual "blank" (all black) array as "off" the change need to be in here?

Thanks, I've got the prototypes in the car and they look great! Ready to advance to a more production level soon.

That doesn't sound advisable for an automotive safety signal light. Especially if you're planning to sell it as a commercial product.

It's still instantly switching from brake to turn or reverse or whatever, just finishing a sequence before turning off - modern cars with sequential turn signals do this already.

In fact, I'd probably ONLY do it for the turn signal. Any other sequence can stop immediately.

As far as safety - While the cars I mostly work with are old enough to be exempt from DOT / safety regs (25 years I think), the factory lights on these cars are TERRIBLE generally. I've sold something like 40 sets of simple LED housings that are a fantastic upgrade and far safer, brighter.

I take safety into the highest consideration, the entire point of this project was to make them safer by not using any delays between change of function.

Anyway I hope that explains what I'm trying to do here a little better - There will be no delay in the brake lamps etc coming ON, just an "extension" of the function to finish a turn signal animation (which lasts half a second)

Yeah, I hear you... but I owned one car that had a delay in turning off the turn signal. So if you hit the wrong direction briefly, it would continue to indicate the wrong direction for a moment. I thought it was a bad idea... at least for the intense traffic conditions I live with. That was a production vehicle, no custom.

Why would continuation of the sequence be important, though? Driver can't even see it.

That's actually a good point - and I could definitely add an instant cancel function for an input from the other signal. I wouldn't mind running wiring back and forth anyway, as I could probably run both lamps with a single controller :wink:

I understand the aesthetics.

What happens with “real” sequencing turn lights? I might bet they just get chopped off in midst cycle if the turn signal is removed.

But there is no real trouble with making sure things run to a good stopping point before doing.

Your animations cycle continuously because as long as conditions merit, you call the stepping function:

What you can do in a case like this is cede some control to the animation function, so

  • the input signals are used to initiate a sequence

  • the sequence will terminate when the input signals change AND the sequence is at step 0 or wherever stopping makes sense.

I did something like that with my version, the below are untested but should give an idea of an approach. The essence is that machines should be callable at all times - if they aren't busy, they just don't do anything.

If you write the step function to return a value saying if it is busy or not

// command 0 - initialise this machine.
// command 1 - normal step this machine, return 0 when done/no longer busy

unsigned char turnMachineStep(unsigned char command)
{
  static byte frame = 0;
  static unsigned long lastTime = 0;
  static unsigned long tween = 0;

  static unsigned char busy = 0;		// not busy!
  static unsigned char returnValue = 0; // not done - will change if not

  if (command == 0) {
    frame = 0;
    lastChange = millis();
    tween = 1;		// next call gets immediate enough attention.
    busy = 1;
    return (1);
  }

  if (!busy)
    return (0);

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

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

    case 1 : /* case 1 - 21 elided here */

    case 22 :
      RenderFrame (ledarraytr23);
      tween = 15;
      frame = 0;

      returnVal = 1;		// we done
      busy = 0;				// we not
      
      break;
  }

  return returnVal;
}

and call it like this:

  if (!turnMachineStep(1) and (totalState == 2 or totalState == 3)) {
  	turnMachineStep(0);
  }

it should finish any animation it started, and cycle if the signals remain valid.

Observe: the machine is called every time you loop(), whether or not you are animating.

If it is called AND is finished AND you want the animation to run, the (re)initialising call is made.

I have always said I launched you on a bad path… sometimes only when you see something working do you realize a little detail was left out of the design. Sometime a hack can be squeezed in; sometimes it will make sense to take a breath and start writing version 2. :expressionless:

I'd test the above code but I am off to see Rick Wakeman. YES, that Rick Wakeman. :wink:

a7

OK tested. With some obvious errors and some not-so-obvious errors.

This was harder to get working than I expected, a certain sign of sleep deprivation and bad no design ahead of coding…

The animation runs as long as the button is pressed. The animation completes itself if the button is released within the sequence.

void setup() {
  Serial.begin(115200);
  Serial.println("Hello World!");

  pinMode(3, INPUT_PULLUP);

  turnMachineStep(2);   // leave it as if it had run once? I give up
}

void loop() {
  if (!turnMachineStep(1) and !digitalRead(3)) {
  	turnMachineStep(0);
  }
}

// command 0 - reset this machine.
// command 1 - normal step this machine, return 0 when done/no longer busy
// command 2 - initialise this machine - mystery? kludged here

unsigned char turnMachineStep(unsigned char command)
{
  static byte frame = 0;
  static unsigned long lastTime = 0;
  static unsigned long tween = 1;

  static unsigned char busy = 0;        // not busy!
  static unsigned char returnValue = 0; // not done - will change if not

// I give up, pound a nail in it.

  if (command == 2) {
      tween = 1;
      frame = 0;

      returnValue = 1;	// we done
      busy = 0;				// we not
 //     initialised = true;
      return returnValue;
  }

 if (command == 0) {
    frame = 0;
    lastTime = millis();
    tween = 200;		// next call gets immediate enough attention.
    busy = 1;

    return (1);
  }

  if (!busy) 
    return (0);

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

  lastTime = millis();

  switch (frame) {
    case 0 :
      Serial.println("               render frame 0");
      tween = 200;
      frame++;
      break;

    case 1 ... 8 : /* case 1 - 21 elided here */
      Serial.print("               render frame ");
      Serial.println(frame);
      tween = 200;
      frame++;
      break;


    case 9 :
      Serial.println("               render frame 9. Done?");
      tween = 15;
      frame = 0;

      returnValue = 1;		// we done
      busy = 0;				// we not busy
      break;
  }

  return returnValue;
}

Into the wokwi:

Sry, this is a kludge on top of a kluge.

FWIW Wakeman brought it, once again.

a7

That is an obvious contradiction. :upside_down_face:

Once you lose power from either the turn signal or brake (or whatever), you clearly cannot complete a sequence unless you have a separate, non-switched power source, and such a power source is not available unless you add the necessary wiring to the car. :astonished:

That is a deliberate design feature of virtually all recent vehicles. Tapping the turn signal for a lane change always gives at least three one-second flashes in order to comply with traffic regulations.

Yep. On my Hyundai, the only way to "cancel" is to indicate the opposite direction!

On the Corolla, if you actually push the indicator lever fully to the "hold" position and back to centre, it cancels immediately.

Such vehicles now use "fly by wire" or CANbus so the light unit is always powered and responds to the central computer (wherever that is) which in turn responds to the indicator lever however it chooses, not dependent in any way from it for power to continue operation.

I believe the OP is hijacking the signals. So he can read them, and decide what to do with his lights. In this case smart pixels, which are powered always the car is on.

We don't need no stinking' CANbus! Cannabis, maybe.

a7