TPIC6B595 Shift Registers Controlling Multiple Steppers

Leo, you are a clever and a very kind person.
You did post your complete code and I did ask for help on that code, only you answered.
For this project I could use the 24BYj's as bipolar (saving pins) and a couple of Mega's but the whole idea was to use your board, neat clean, and no drivers. IMHO chopping up your original code is a messy mistake.
That's why in #7 I asked you to please just show me the code to fill all the shift registers with the correct bits to move all steppers together to a certain position. No acceleration, or deceleration... etc.
Regards.

Can't do that.
No acceleration/deceleration almost certainly means missing steps.
But don't worry about that. The goPos() function takes care of that in the background.

As said, you just need to fill the newPos array with new positions, nothing else.

Or, what I think you want to do,
calculate the newPos array from a stored EEPROM positions array and some new move.
We can't advise you anything until you post your best code attempt.
Leo..

Ok here's my code, please see the commented out part "where I'm lost".
Thanks again.

/*
  Nano | TPIC6B595
    5V | 2 (VCC)
   GND | GND
    D8 | 8 (SRCLR)
   D10 | 12 (RCK)
   D11 | 3 (SER IN) of the first chip
   D13 | 13 (SRCK)
       | 9 to GND
       | 10, 11,19 to GND
       | 18 (SER OUT) to 3 of the next chip
*/

#include <SPI.h>
#include <EEPROM.h>

const byte motors = 41; // sets of four motors
const byte clrPin = 8; // TPIC6B595 SRCLR pin8
const byte latchPin = 10; // TPIC6B595 RCK pin12

int CurrPos;  // current position
int NewPos;  // new position to go to
int RandNum;  // random number
int steps;   // will hold number of steps to reach a target position

int sensor = 2;              // the pin that the sensor is atteched to
int state = LOW;             // by default, no motion detected
int val = 0;                 // variable to store the sensor status (value)


int TargetPos[] = {550, 1900, 3080, 2048, 5000, 5500, 6028, 7500}; // hypotetical positions, just for testing

void setup() {
  SPI.begin();
  pinMode(clrPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  digitalWrite(clrPin, HIGH); // clear all shift register data


  EEPROM.get( 0, CurrPos );  // read tha last known position of the steppers
}

void loop() {

  val = digitalRead(sensor);   // read sensor value
  if (val == HIGH) {           // check if the sensor is HIGH
    Go_To_Target_Pos();
    
  }
}

void Go_To_Target_Pos() {

  randomSeed(analogRead(0));  // generate a random number
  RandNum = random(0, 7); // a numbers equal to numer of positions in array TargetPos
  delay(50);
  NewPos = (TargetPos[RandNum]);

  steps = (NewPos - CurrPos);  // number of steps needed to go from current position to new position

  if (steps < 0) {
  steps = steps * - 1;  // go backwards
}

/*  **********************************************
 >>>>>>   Here's where I'm lost  <<<<<<<<<<

without shift registers I'd write direction pin HIGH/LOW to go forward
or backward, then :

for (int x = 0; x < steps + 1; x++) {
    digitalWrite(step_pin, HIGH);
    delay(10);
    digitalWrite(step_pin, LOW);
}

>>>>>  Using shift registers what is the procedure to send the correct bits 
        to all shift registers to meve all steppers to the NewPos ?

   ***********************************
*/

EEPROM.update(0, NewPos);
state = LOW;       // update variable state to LOW

}

I don't know where to start. Your code is lacking the step sequence and SPI writes.
Again, stick with the goPos() function I wrote (see it as a library).

I also don't see you write random/new positions to all 41 motors.

Writing to EEPROM every loop is also not possible.
It's slow (affecting motor movements), and you will wear out the chip very fast.

Maybe you should look at battery power for the setup (no EEPROM writes in loop).
Current draw is very low (only the Arduino) after the motors have reached their destination,
because the goPos() function also sleeps the motors.
You could still write to EEPROM for a controlled shut down, with a button.

Please explain exactly what you want to do with the motors.
I can then try to advise you how to tackle the problem.
Leo.

That's where I'm lost!

I started by using a micro SD module but was told I could write to EEPROM 100,000 times before wearing it out.

I read the last known position of the steppers (it's the same for all motors, so just one integer) from EEPROM or SD card. I want to move all the steppers, at the same time, from their current position to a new position. If the new position > current position then turn forward else turn backwards.
I hope it's clear now.

The motors take one step each loop (~2ms), so if you move the motors one turn and write to EEPROM each loop, then you have already used 2000 writes.
So the chip could be failing after 50 motor moves. Same problem with an SD card.
If you want to write to EEPROM, you should do that once, after the move is completed.
For example in a next switch statement.

I would forget the EEPROM for now, and try to code the motor movements first.
If that works, then you can think of writing to EEPROM.

The goPos() function also takes care of that. Just enter a new position.
That new position can also be negative (forwards or backwards from the start position).

The goPos() function moves all motors at the same time once new positions are programmed.
I assume you want different (and random) new positions for all motors.

I will try to post some code when I have time.
Leo..

I programmed two cases here.
One that moves all motors to a different random position,
and one that moves them to a common random position.
Compiles, but not hardware tested.
Read all the comments.
Leo..

/*
  Nano | TPIC6B595
    5V | 2 (VCC)
   GND | GND
    D8 | 8 (SRCLR)
   D10 | 12 (RCK)
   D11 | 3 (SER IN) of the first chip
   D13 | 13 (SRCK)
       | 9 to GND
       | 10, 11,19 to GND
       | 18 (SER OUT) to 3 of the next chip
*/

#include <SPI.h>
const byte motors = 48; // sets of four motors
// must be sets of four motors for SPI.transfer16, so 44 for 41 motors !
// but why not keep it simply the board number of 48
const byte clrPin = 8; // TPIC6B595 SRCLR pin8
const byte latchPin = 10; // TPIC6B595 RCK pin12
const byte rampSteps = 7; // accel/decel steps
byte rampDelay[motors]; // accel/decel delay between steps
byte lag[motors]; // speed reduction
int velocity[motors]; // signed, for direction
int nowPos[motors], newPos[motors]; // int is 16 rotations max (2.5m string with a 50mm dia wheel)
unsigned long prevMillis, prevMicros, interval; // timing
byte val[4]; // TPIC write bytes
bool torque; // reduced during homing
byte sequence, index; // patterns, motor index
int randNumber;

void setup() {
  SPI.begin();
  pinMode(clrPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  digitalWrite(clrPin, HIGH); // clear all shift register data
  randomSeed(analogRead(0));
  // load 41 (or 48) eeprom nowPos[] integers (= two bytes) here
}

void loop() {
  switch (sequence) {
    case 0: // load random positions for each motor into the new position array
      for (byte i = 0; i < motors; i++) {
        newPos[i] = random(-500, 500); // about a quarter turn max, right or left from start position
      }
      // eepromSave(); // could call eeprom write function here
      sequence = 1; // go to next case
      prevMillis = millis(); // but first mark the time
      break;
    case 1: // move all motors the same random direction/steps
      if (millis() - prevMillis > 8000) { // after 8 seconds
        randNumber = random(-1024, 1024); // max half a turn
        for (byte i = 0; i < motors; i++) {
          newPos[i] = randNumber; // all motors the same (random) new position
        }
        // eepromSave(); // could call eeprom write function here
        sequence = 2; // next case
        prevMillis = millis();
      }
      break;
    case 2: // run sequence(s) again
      if (millis() - prevMillis > 20000) { // after 20 sec
        sequence = 1;
        prevMillis = millis(); // mark
      }
      break;
  }
  goPos(); // let's step, one step each loop
}

void goPos() {
  while (micros() - prevMicros < 1953); // step interval, motor RPM limitation, 1/(15rpm/60sec*2048steps)= ~1953ms, wait if arrived here too early
  prevMicros = micros(); // update, for next interval
  for (int i = 0; i < motors; i++) {
    if (rampDelay[i]) rampDelay[i]--; // subtract one, do nothing
    else { // step
      if (newPos[i] > nowPos[i]) { // request forwards
        if (velocity[i] >= 0) { // if standing still, or going forwards
          nowPos[i]++; // one step forward
          if (newPos[i] - nowPos[i] < rampSteps) velocity[i]--; // reduce speed if almost there
          else if (velocity[i] < min(rampSteps, rampSteps - lag[i])) velocity[i]++; // increase speed if not there yet
          rampDelay[i] = max(lag[i], rampSteps - velocity[i]); // insert ramp delay
        } else { // if wrong direction
          velocity[i]++; // reduce reverse speed
          if (velocity[i] < 0) nowPos[i]--; // if still reverse, keep on going the wrong way
          rampDelay[i] = rampSteps - abs(velocity[i]); // insert ramp delay
        }
      }
      if (newPos[i] < nowPos[i]) { // request reverse
        if (velocity[i] <= 0) { // if standing still, or going reverse
          nowPos[i]--; // one step reverse
          if (nowPos[i] - newPos[i] < rampSteps) velocity[i]++; // reduce speed if almost there
          else if (abs(velocity[i]) < min(rampSteps, rampSteps - lag[i])) velocity[i]--; // increase speed if not there yet
          rampDelay[i] = max(lag[i], rampSteps + velocity[i]); // insert ramp delay
        } else { // wrong direction
          velocity[i]--; // reduce forward speed
          if (velocity[i] > 0) nowPos[i]++; // if still reverse, keep on going the wrong way
          rampDelay[i] = rampSteps - velocity[i]; // insert ramp delay
        }
      }
    }
    if (newPos[i] == nowPos[i])val[i & 3] = 0; // cut coil power when there
    else {
      if (torque) { // this block for full torque
        switch (nowPos[i] & 3) { // the two LSB translated to motor coils (full step)
          case 0: val[i & 3] = B00000011; break; // two coils at the time
          case 1: val[i & 3] = B00000110; break;
          case 2: val[i & 3] = B00001100; break;
          case 3: val[i & 3] = B00001001; break;
        }
      } else { // this block for low power
        switch (nowPos[i] & 3) {
          case 0: val[i & 3] = B00000001; break; // one coil at the time
          case 1: val[i & 3] = B00000010; break;
          case 2: val[i & 3] = B00000100; break;
          case 3: val[i & 3] = B00001000; break;
        }
      }
    }
    if ((i & 3) == 3) { // process set of four motors (faster)
      unsigned int chain = val[0] << 12 | val[1] << 8 | val[2] << 4 | val[3]; // concat
      SPI.transfer16(chain); // transfer 4-motor int
    }
  }
  digitalWrite(latchPin, HIGH); // transfer to TPIC driver outputs
  digitalWrite(latchPin, LOW); // latch time ~8us
}

Leo,
You are my Superman, thanks a million times.
Please forgive me for not explaining my project. I took it for granted that you knew what I was doing from another tread I posted. Please take a look (scroll down) at this art work in the following video.

I made a replica of this with 3D printed gears instead of laser cut wooden gears used by the artist and I wanted to motorize it. I couldn't find anyone to design and laser cut the gears and the 3D printing approach failed. The gear passes were too large, no smooth movement and there were alignment problems. So after multiple attempts and throwing away hundreds of 3D printed gears I had an idea. Since the patterns are made only by the white part of the rotating gears why not just 3D print half circles (no gears) in white with a shaft and attached them to a stepper. Now my problem was controlling 41 steppers and that's where I thought of using one of your boards. Please note that in the video, when the gears are turned, you get an interesting pattern only at certain positions. That's why I thought of an array of prefixed positions. And to make things more interesting I'd throw in a PIR sensor to sense someone in front of this hanging on the wall, then a random index number which will point to one of these prefixed positions. Please forgive my long thread but I owed it to you and I hope it clears up some doubts.

Back to your code, it works fine. I made a couple of changes which need your approval.
first I commented out tis part:

void loop() {
  switch (sequence) {
    case 0: // load random positions for each motor into the new position array
      //   for (byte i = 0; i < motors; i++) {
      //     newPos[i] = random(-500, 500); // about a quarter turn max, right or left from start position
      //      }
      // eepromSave(); // could call eeprom write function here
      sequence = 1; // go to next case
      prevMillis = millis(); // but first mark the time
      break;

Since I need all steppers to go to the same random position. works fine but is it ok ?

then I defined an array of prefixed TargetPositions[500, 850, 1020, etc.] and changed your code to generate a random number from 0 to n (number of the values in TargetPos. This random number points to / picks one of those positions as the new position from the array.

 case 1: // move all motors the same random direction/steps
      if (millis() - prevMillis > 8000) { // after 8 seconds

        //      randNumber = random(-1024, 1024); // max half a turn
        
        randNumber = random(0, 7);  // to be decided , just an example

        for (byte i = 0; i < motors; i++) {
          // newPos[i] = randNumber; // all motors the same (random) new position

          newPos[i] = TargetPos[randNumber]; // all motors the same (random) new position

        }
        // eepromSave(); // could call eeprom write function here
        sequence = 2; // next case
        prevMillis = millis();
      }
      break;

and this also works fine.

If you agree with these changes, the next step is to check to see why I have a pretty long delay after the motors have reached their new position ? Any idea ? The ideal scenario will be for the motors to stay in their new position until the PIR sensor is triggered again. Logically the PIR sensor HIGH/LOW must be tested after the motors have reached the new position and not while they are moving.

I agree with you and was my intention to write the position to EEPROM not during the stepping loop but only after the motors are at their destination.
Maybe even better is to check if the PIR sensor is not triggered within a certain time (no one is if front of the panel) then write the last position to EEPROM , cut current to the motors and maybe even put Arduino in the sleep mode.

thank you.

The default mode for the steppers is "single coil drive", which is low (half) power (current).
unless the "torque" variable is set to "true" (dual coil drive),
which was done in case 0 of the original sketch (lifting heavy objects on a string).
The motors are not stressed in your project, so they can be left in low power mode.

As said before, the motors are individually switched off (zero current) when they have reached their destination, all done by the goPos() function.
if (newPos[i] == nowPos[i])val[i & 3] = 0; // cut coil power when there

I think I understand your requirement, and will try to upload some coding ideas for that tomorrow.
Leo..

About saving to eeprom.
I think you should include a setup button to your project,
so you can ignore the saved eeprom values during homing/resetting/zeroing of the motors.

Here are some (untested) ideas for "ignore/load" that you can add to setup().

And a function that you can use to write to eeprom.
Leo..

#include <EEPROM.h>
int eeAddress = 0; // address offset
const byte zeroPin = 6; // button between pin and ground

void setup() {
  pinMode(zeroPin, INPUT_PULLUP);
  if (digitalRead(zeroPin)) { // if not pressed
    for (int i = 0; i < motors; i++) {
      EEPROM.get(eeAddress, nowPos[i]);
      eeAddress += 2; // int is 2 bytes
    }
  }
}

void eeSave() {
  for (int i = 0; i < motors; i++) {
    EEPROM.put(eeAddress, nowPos[i]);
    eeAddress += 2;
  }
}

I fail to see the use of a button. Let me explain what I have in mind.
I intend to just write 100 on EEPROM (without uploading your sketch) which will be my starting point and will be included in TargetPos as a position. Then before the very first run, I'll align all the half circles manually in the same position, let's say facing upwards. Then load your code and start the "show". A button will surely be helpful in case one or any of the steppers misstepped after a while and if I had 41 end stop/sensors , one on each motor. Otherwise, in case of a misstep pushing a button will not re align the motors in the 0 position.
I'm surely putting a lot of faith in cheap 24BYJ's because if any of them missteps the whole "show" will fail. I've tried only 10 motors, ordered the others, must finish 3D printing the half circles, get a panel laser cut with holes, mount everything, do the wiring with correct power supply. Only then I'll know if I face misstepping problem and waisted my time --- and yours (sigh !).

Changing EEPROM address is an excellent idea, thanks. Guess I'll have to put in an if statement to overcome the overflow ?

if (address == EEPROM.length()) { address = 0;}

Regards.

The button code is transparent, so no harm done if you include the code and don't install or press the button. I thought it would be handy to ignore any stored values if you need to re-align.

I have used my 48-stepper setup for hours at the time, and did not notice any missed steps. Even if I did slam the motors in reverse without stopping them first (goPos() should also take care of that).

PIR pause code could be inserted at the start of a case.
Leave both pots on the PIR sensor in the middle, so 'on' time is about 2-3minutes.

    case 1: // move all motors the same random direction/steps
      if (digitalRead(pirPin) && millis() - prevMillis > 8000) { // after 8 seconds
        randNumber = random(-1024, 1024); // max half a turn
        for (byte i = 0; i < motors; i++) {
          newPos[i] = randNumber; // all motors the same (random) new position
        }
        // eepromSave(); // could call eeprom write function here
        sequence = 2; // next case
        prevMillis = millis();
      }

I have the feeling you want the motors to behave as the mechanically linked "patturn".
Individually programmed motors can do so much more than that.
Have fun, and not too many headaches.
Leo..

Thanks again Leo, I hope to post a video of the finished project soon with credits to you.