All Arduino Mega Pins + 5 shift Registers - 6x8 Grid Animation Code Help

I have an Arduino Mega wired to 12 8 Channel SSR relay boards. This uses all of the analog pins and digital pins 1 - 50. Digital pins 51, 52 and 53 are used to control 5 shift registers in parallel.

The relays control line voltage light arranged in a 2 level 6 x 8 grid.

I want to make simple animations flow through the grid of lights.

Many people use multiplexing for grids of leds but that will not work for this since I have grids of relays. It is actually 2 6x8 grids on top of each other os each grid space has a top and bottom.

Questions:

  • How can I set up the animations to cascade across the grid?
  • How can I included the shift register pins and the regular Arduino pins together efficiently? I included some sample code below. It is intentionally sparse so I do not make any assumptions.

I can provide more information as needed but am not really sure where to start. I assume I need to set up an x,y grid and then type in the different pin states in a pattern.

Frame 1
11111110
11111110
11111110
11111110
11111110
11111110

Frame 2
11111101
11111101
11111101
11111101
11111101
11111101

Frame 3
11111011
11111011
11111011
11111011
11111011
11111011

etc... Plus other patterns and gradients.

#include <ShiftRegister74HC595.h>

int numberOfShiftRegisters = 5; // number of shift registers attached in series
int serialDataPin = 51;
int clockPin = 52; 
int latchPin = 53; 
ShiftRegister74HC595 sr (numberOfShiftRegisters, serialDataPin, clockPin, latchPin); 

int lightPins[] = {A0,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,A13,A14,A15,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,,37,38,39,40,41,42,43,44,45,46,47,48,49,50}; //An array to hold the pin each LED is connected to

// lights 1 - 66 use pins from Arduinio Mega
// lights 67 - 96 use pins from Shift registers in series
// lights 67 - 96 use shift register pins 1 - 30

void setup () {

  //Set each pin connected to light string to output mode 
  for (int i = 0; i <= 66; i++)
  {
    pinMode(lightPins[i], OUTPUT);
  }

  // Prevent relays from starting up engaged
  for (int i = 0; i <= 66; i++)
  {
    digitalWrite(lightPins[i], LOW);
  }
  
}

void loop() {
  
// way to turn on a single light string with lightPins 1 - 66 from Arduino Mega
digitalWrite(lightPins[23], HIGH);

//way to turn on a single light string from Shift Registers in series 
// (light strings 67 - 96 but shift register pins 1 - 30)

sr.set(1,HIGH);

}

Wiring of Arduino Mega and SSR Relays

Visualization of stacked 2 level 6 x 8 grid

Would using a multidimensional array help?

Below is an array with the pin numbers. I think there might be an issue looping through the arrays to make animation patterns since the shift registers use a different function than DigitalWrite to set their pins.

I have an idea for making a more multi dimensional array that will also store whether or not a pin is on the top or the bottom, what row column it is in, what pin number it is, whether is is an Arduino pin or a shift register pin.

Then all of those values could be fed into a function. That seems overly complicated and I imagine I am missing something obvious on how to go about this.

Thank you in advance.

int lightPinsTop[6][8] = {
  {A0,A1,A2,A3,A4,A5,A6,A7}, 
  {A8,A9,A10,A11,A12,A13,A14,A15}, 
  {1,2,3,4,5,6,7,8}, 
  {9,10,11,12,13,14,15,16},
  {17,18,19,20,21,22,23,24},
  {25,26,27,28,29,30,31,32}
};

int lightPinsBottom[6][8] = {
  {33,34,35,36,37,38,39,40},
  {41,42,43,44,45,46,47,48},
  {49,50,1,2,3,4,5,6}, // These 1 - 6 are shift register pins and use a different HIGH LOW function sr.set not sure how to indicate this
  {7,8,9,10,11,12,13,14}, // Shift Register Pins
  {15,16,17,18,19,20,21}, // Shift Register Pins
  {22,23,24,25,26,27,28} // Shift Register Pins
};

My question is why you are using an int array to store pin numbers. Have you got THAT many pins? Are you sure you don't need long or long long?

PaulS:
My question is why you are using an int array to store pin numbers. Have you got THAT many pins? Are you sure you don't need long or long long?

I have analog pins A0 - A15 for switching relays.
I have digital pins 1-50 for switching relays.
I have digital pins 51-53 for switching shift registers 1-30.

He's being sarcastic. You can declare the array as uint8_t, or uint16_t if you have more than 256 pins.

Not sure what you're looking for.

He's being sarcastic.

No, I'm trying to make OP think. If OP doesn't get it, OP should ask. Then. my next question would be what range of values can be store in an int, and then whether OP really needs to deal with negative pin numbers. At some point, I'd ask about the range that can be stored in other types. At some point, OP should realize that there are smaller types that are more appropriate.

And, that is going to be essential, given the amount of data he/she is going to be dealing with. The wrong type for any array is going to either waste memory, which will be in short supply, or cause overflow. It is, therefore, essential to understand, BEFORE writing a sh*tload of code, about how much memory is available, and how not to waste any of it.

PaulS:
At some point, OP should realize that there are smaller types that are more appropriate.

I agree, the type of variable should not be overly big if not necessary. I changed lots of int to byte where I could. I am now using a millis timer so there are some unsigned longs. Since I am using an Arduino Mega, I am still only at 3184 bytes (1%) of program storage space and 171bytes (2%) of dynamic memory.

I do think I will run into issues as the code gets longer but I will try to streamline as I go.


With the help of a friend and this forum, I set up a test of 48 relays and 3 shift registers. I have some simple animations going but nothing more complex than a light chase. One uses a delay but goes both directions. lineardance();

I got another one working without a delay but I cannot get it to go in reverse more than once. lineardancenodelay();

I am also interested in the individual rows of lights blinking in and out in sequence and at random intervals.

I also want to make the lights start in the middle and expand out like a ripple in a pond.

Do you have any suggestions on how to make the chase reverse in the no delay version or to make the other animations?

Thank you in advance

#include <ShiftRegister74HC595.h>

unsigned long currentMillis = 1;
unsigned long flashTime = 1;
unsigned long danceTime = millis();
unsigned long danceTimeReverse = millis();
int danceStepEnd = 48;
int danceStep = 0;
int i = 0;
int lightState = LOW; // Temp for additional blink without delay function

byte numberOfShiftRegisters = 3; // number of shift registers attached in series
byte serialDataPin = 51; // blue wire
byte clockPin = 52; // yellow wire
byte latchPin = 53; // green wire
ShiftRegister74HC595 sr (numberOfShiftRegisters, serialDataPin, clockPin, latchPin);

const byte numberOfLights = 48; // number of light strings 48 in testing 96 in production
byte numberOfArduinoPins = 24; // 24 in testing 66 in production
byte numberOfShiftRegisterPins = 24; // 24 in testing 30 in production
const byte lightPins[] = {A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, 1, 2, 3, 4, 5, 6, 7, 8};
// ,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50}; //An array to hold the pin each LED is connected to
// lights 1 - 66 use pins from Arduinio Mega
// lights 67 - 96 use pins from Shift registers in series
// lights 67 - 96 use shift register pins 1 - 30
const unsigned danceIntervals[numberOfLights] = {  // Milliseconds
  300, 290, 280, 270, 260, 250,
  240, 230, 220, 210, 220, 210,
   200, 190, 180, 170, 160, 150,
  140, 135, 130, 125, 120, 115,
   110, 105, 100, 95, 90, 85,
  80, 75, 70, 65, 60, 55,
   50, 45, 40, 35, 30, 25,
  20, 15, 10, 5, 4, 3
};

void setup () {

  //Set each pin connected to light string to output mode
  for (byte i = 0; i <= numberOfArduinoPins; i++)
  {
    pinMode(lightPins[i], OUTPUT);
  }

  // Prevent relays from starting up engaged
  for (byte i = 0; i <= numberOfArduinoPins; i++)
  {
    digitalWrite(lightPins[i], LOW);
  }

  // Prevent shift register controled relays from starting up engaged
  for (byte i = 0; i <= numberOfShiftRegisterPins; i++)
  {
    sr.set(i, HIGH); // HIGH in testing rig relays but LOW in production
  }
}

void loop() {
  currentMillis = millis();
 // newlineardance();
  lineardancenodelay();
  lineardancereversenodelay();
  blinkLightWithoutDelay(37);
  
  //lineardance();
  //or
  //outtoindance();
  //lightcontrolon(danceStep);
}

//Check if time expired function
boolean CheckTime(unsigned long &lastMillis, unsigned long wait)
{
  //is the time up for this task?
  if (currentMillis - lastMillis >= wait)
  {
    lastMillis += wait;  //get ready for the next iteration
    return true;
  }
  return false;
}
//END of CheckTime()

// linear run of lights in sequential order without delay
void lineardancenodelay() {
  if ((currentMillis - danceTime) >= danceIntervals[danceStep])
  {
    danceTime += danceIntervals[danceStep];
    lightcontroloff(danceStep); // Previous pin OFF
    danceStep = (danceStep + 1) % numberOfLights;
    lightcontrolon(danceStep);  // New pin ON
  }
}

// linear run of lights in reverse sequential order without delay but cannot figure out how to reverse
void lineardancereversenodelay() {
  if ((currentMillis - danceTimeReverse) >= danceIntervals[danceStepEnd])
  {
    danceTimeReverse += danceIntervals[danceStepEnd];
    lightcontroloff(danceStepEnd); // Previous pin OFF
    danceStepEnd = (danceStepEnd - 1) % numberOfLights;
    lightcontrolon(danceStepEnd);  // New pin ON
  }
}

// blink an individual light without delay
void blinkLightWithoutDelay(byte lightno) {
   if (CheckTime(flashTime, 500UL)) {
       flashTime = millis();
        if (lightState == LOW) {
          lightState = HIGH;
          lightcontrolon(lightno);        
        } else {
          lightState = LOW;
        lightcontroloff(lightno); 
        }   
      }
}

//just a linear test of lights in sequential order and then goes in reverse
void lineardance() {

  int lightpause = 20;
  for (byte i = 0; i <= numberOfLights; i++)
  {
    lightcontrolon(i);
    delay(lightpause);
    lightcontroloff(i);
    delay(lightpause);
  }

  for (byte i = numberOfLights; i <= numberOfLights; i--)
  {
    lightcontrolon(i);
    delay(lightpause);
    lightcontroloff(i);
    delay(lightpause);
  }
}

//lights on the outside light up, then turn off, while mid ones light up, then the inner ones
void outtoindance() {
  int lightpause = 100;

  //outer lights is first array group, mid lights our second group, and inner lights are third group
  int lightGroups[3][9] = {   {1, 2, 3, 4, 5, 6, 7, 8, 9} , {10, 11, 12, 13, 14, 15, 16, 17, 18}, {19, 20, 21, 22, 23, 24, 25, 26, 27} };
  for (int i = 0; i < 3; i++)
  {
    for (int j = 0; i < 9; j++) {
      lightcontrolon(lightGroups[i][j]);
    }
    delay(lightpause);
    for (int j = 0; i < 9; j++) {
      lightcontroloff(lightGroups[i][j]);
    }
  }
}

// turns lights on by pin and turns shift registers on by pin if greater than number of light pins
void lightcontrolon(byte lightno) {
  if (lightno < (numberOfArduinoPins + 1)) {
    digitalWrite(lightPins[lightno], HIGH); // HIGH trigger relays
  } else {
    byte shiftno = lightno - numberOfArduinoPins;
    sr.set(shiftno, LOW); // LOW in testing rig relays but HIGH in production
  }
}

// turns lights off by pin and turns shift registers off by pin if greater than number of light pins
void lightcontroloff(byte lightno) {
  if (lightno < (numberOfArduinoPins + 1)) {
    digitalWrite(lightPins[lightno], LOW); // HIGH trigger relays
  } else {
    byte shiftno = lightno - numberOfArduinoPins;
    sr.set(shiftno, HIGH); // HIGH in testing rig relays but LOW in production
  }
}

Currently I am only using 48 relays. I have 96 relays installed in the completed control board but do not have physical access to the 96 relay board during this code testing time.

So now I have a couple different patterns going. Some still have delays, others I have replaced with millis timers. I hope to replace all delays.

Am I going about it in the right way with the multi dimensional arrays standing in for each "frame" of animation? Is there a more efficient way?


lineardancenodelay();

This works and it repeats so it is good. I wish I could have it ramp up or have it be 4 lights long or increase its length like a snake.


lineardancereversenodelay();

This works only once. I can not figure out how to make it repeat.


blinkLightWithoutDelay(37);

This works and repeats.


rowWaveDance();
outtoindance();
lineardance();

These three work but have delays that I am looking to remove.

#include <ShiftRegister74HC595.h>

unsigned long currentMillis = 1;
unsigned long flashTime = 1;
unsigned long danceTime = millis();
unsigned long danceTimeReverse = millis();
int danceStepEnd = 48;
int danceStep = 0;
int i = 0;
int lightState = LOW; // Temp for additional blink without delay function

byte numberOfShiftRegisters = 3; // number of shift registers attached in series
byte serialDataPin = 51; // blue wire
byte clockPin = 52; // yellow wire
byte latchPin = 53; // green wire
ShiftRegister74HC595 sr (numberOfShiftRegisters, serialDataPin, clockPin, latchPin);

const byte numberOfLights = 48; // number of light strings 48 in testing 96 in production
byte numberOfArduinoPins = 24; // 24 in testing 66 in production
byte numberOfShiftRegisterPins = 24; // 24 in testing 30 in production
const byte lightPins[] = {A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, 1, 2, 3, 4, 5, 6, 7, 8};
// ,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50}; //An array to hold the pin each LED is connected to
// lights 1 - 66 use pins from Arduinio Mega
// lights 67 - 96 use pins from Shift registers in series
// lights 67 - 96 use shift register pins 1 - 30
const unsigned danceIntervals[numberOfLights] = {  // Milliseconds
  300, 290, 280, 270, 260, 250,
  240, 230, 220, 210, 220, 210,
   200, 190, 180, 170, 160, 150,
  140, 135, 130, 125, 120, 115,
   110, 105, 100, 95, 90, 85,
  80, 75, 70, 65, 60, 55,
   50, 45, 40, 35, 30, 25,
  20, 15, 10, 5, 4, 3
};

void setup () {

  //Set each pin connected to light string to output mode
  for (byte i = 0; i <= numberOfArduinoPins; i++)
  {
    pinMode(lightPins[i], OUTPUT);
  }

  // Prevent relays from starting up engaged
  for (byte i = 0; i <= numberOfArduinoPins; i++)
  {
    digitalWrite(lightPins[i], LOW);
  }

  // Prevent shift register controled relays from starting up engaged
  for (byte i = 0; i <= numberOfShiftRegisterPins; i++)
  {
    sr.set(i, HIGH); // HIGH in testing rig relays but LOW in production
  }
}

void loop() {
  currentMillis = millis();
  //newlineardance();
  //lineardancenodelay();
  //lineardancereversenodelay();
  //blinkLightWithoutDelay(37);
  rowWaveDance();
  outtoindance();
  lineardance();
  //lightcontrolon(danceStep);
}

//Check if time expired function
boolean CheckTime(unsigned long &lastMillis, unsigned long wait)
{
  //is the time up for this task?
  if (currentMillis - lastMillis >= wait)
  {
    lastMillis += wait;  //get ready for the next iteration
    return true;
  }
  return false;
}
//END of CheckTime()

// linear run of lights in sequential order without delay
void lineardancenodelay() {
  if ((currentMillis - danceTime) >= danceIntervals[danceStep])
  {
    danceTime += danceIntervals[danceStep];
    lightcontroloff(danceStep); // Previous pin OFF
    danceStep = (danceStep + 1) % numberOfLights;
    lightcontrolon(danceStep);  // New pin ON
  }
}

// linear run of lights in reverse sequential order without delay but cannot figure out how to reverse
void lineardancereversenodelay() {
  if ((currentMillis - danceTimeReverse) >= danceIntervals[danceStepEnd])
  {
    danceTimeReverse += danceIntervals[danceStepEnd];
    lightcontroloff(danceStepEnd); // Previous pin OFF
    danceStepEnd = (danceStepEnd - 1) % numberOfLights;
    lightcontrolon(danceStepEnd);  // New pin ON
  }
}

// blink an individual light without delay
void blinkLightWithoutDelay(byte lightno) {
   if (CheckTime(flashTime, 500UL)) {
       flashTime = millis();
        if (lightState == LOW) {
          lightState = HIGH;
          lightcontrolon(lightno);        
        } else {
          lightState = LOW;
        lightcontroloff(lightno); 
        }   
      }
}

//just a linear test of lights in sequential order and then goes in reverse
void lineardance() {

  int lightpause = 20;
  for (byte i = 0; i <= numberOfLights; i++)
  {
    lightcontrolon(i);
    delay(lightpause);
    lightcontroloff(i);
    delay(lightpause);
  }

  for (byte i = numberOfLights; i <= numberOfLights; i--)
  {
    lightcontrolon(i);
    delay(lightpause);
    lightcontroloff(i);
    delay(lightpause);
  }
}

//lights on the outside light up, then turn off, while mid ones light up, then the inner ones
void outtoindance() {
  int lightpause = 200;
  //outer lights is first array group, mid lights our second group, and inner lights are third group
  int lightGroups[4][16] = {   {0, 1, 2, 3, 4, 5, 6, 7, 40, 41, 42, 43, 44, 45, 46, 47} , {8, 9, 10, 11, 12, 13, 14, 15, 32, 33, 34, 35, 36, 37, 38, 39} , {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31} , {8, 9, 10, 11, 12, 13, 14, 15, 32, 33, 34, 35, 36, 37, 38, 39}  };
  for (int i = 0; i < 4; i++)
  {
    for (int j = 0; j < 16; j++) {
      lightcontrolon(lightGroups[i][j]);
    }
    delay(lightpause);
    for (int j = 0; j < 16; j++) {
      lightcontroloff(lightGroups[i][j]);
    }
  }
}

void rowWaveDance() {
  int lightpause = 200;
  //end lights is first array group, mid lights our second group, and far lights are third group and loop
  int lightGroups[10][8] = {   {0, 1, 2, 3, 4, 5, 6, 7} , {8, 9, 10, 11, 12, 13, 14, 15} , {16, 17, 18, 19, 20, 21, 22, 23} ,  {24, 25, 26, 27, 28, 29, 30, 31} , {32, 33, 34, 35, 36, 37, 38, 39} , {40, 41, 42, 43, 44, 45, 46, 47}, {32, 33, 34, 35, 36, 37, 38, 39} ,  {24, 25, 26, 27, 28, 29, 30, 31}, {16, 17, 18, 19, 20, 21, 22, 23}, {8, 9, 10, 11, 12, 13, 14, 15} };
  for (int i = 0; i < 10; i++)
  {
    for (int j = 0; j < 8; j++) {
      lightcontrolon(lightGroups[i][j]);
    }
    delay(lightpause);
    for (int j = 0; j < 8; j++) {
      lightcontroloff(lightGroups[i][j]);
    }
  }
}

// turns lights on by pin and turns shift registers on by pin if greater than number of light pins
void lightcontrolon(byte lightno) {
  if (lightno < (numberOfArduinoPins + 1)) {
    digitalWrite(lightPins[lightno], HIGH); // HIGH trigger relays
  } else {
    byte shiftno = lightno - numberOfArduinoPins;
    sr.set(shiftno, LOW); // LOW in testing rig relays but HIGH in production
  }
}

// turns lights off by pin and turns shift registers off by pin if greater than number of light pins
void lightcontroloff(byte lightno) {
  if (lightno < (numberOfArduinoPins + 1)) {
    digitalWrite(lightPins[lightno], LOW); // HIGH trigger relays
  } else {
    byte shiftno = lightno - numberOfArduinoPins;
    sr.set(shiftno, HIGH); // HIGH in testing rig relays but LOW in production
  }
}

PaulS:
No, I'm trying to make OP think. If OP doesn't get it, OP should ask. Then. my next question would be what range of values can be store in an int, and then whether OP really needs to deal with negative pin numbers. At some point, I'd ask about the range that can be stored in other types. At some point, OP should realize that there are smaller types that are more appropriate.

And, that is going to be essential, given the amount of data he/she is going to be dealing with. The wrong type for any array is going to either waste memory, which will be in short supply, or cause overflow. It is, therefore, essential to understand, BEFORE writing a sh*tload of code, about how much memory is available, and how not to waste any of it.

My bad, I saw your last sentence as a joke since 2^32 and 2^64 are A LOT of pins.

OP, for animations, since you have lots of free space, you could store lookup tables for different animations. If I read correctly, you have 192 led to be driven. So for 1 state or frame, you need 192bit = 24bytes. The mega has 256KB flash, let's reserve 56KB for program, so you have 200KB freespace.

200e3 bytes / 24 bytes = 8333 frames you can store in your progmem