TPIC6B595 Shift Registers Controlling Multiple Steppers

Hi,
Last year I posted a thread asking for help on how to make a small copy of the BMW kinetic sculpture

Lots of excellent ideas and discussions.
Two fellows, Crossroad (Robert) and wawa (Leo) actually made some prototypes.
Leo, made boards with 48 chips (TPIC6B595) driving 28BYJ-48 steppers.
He then kindly sent me 2 of his boards and posted a sketch.
Unfortunately, I've never been able to make them work.
I've followed his instructions on wiring and used his sketch.

Can someone please tell me why I can't get the steppers to move ?
I'd hate to throw the boards away after all the work by wawa and his kindness for sending them to me.

I'm using the 5v version of 28BYj's, don't know if wawa used the same version.

Here's his code

/*
 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
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

void setup() {
 SPI.begin();
 pinMode(clrPin, OUTPUT);
 pinMode(latchPin, OUTPUT);
 digitalWrite(clrPin, HIGH); // clear all shift register data
 for (byte i = 0; i < motors; i++) nowPos[i] = 1024; // homing all motors 1/2 turn
}

void loop() {
 switch (sequence) {
   case 0: // break free from the top, all motors
     if (nowPos[0] == newPos[0]) { // finished homing
       for (byte i = 0; i < motors; i++) nowPos[i] = -128; // 1/16 turn
       torque = true; // was low power during homing
       sequence = 1; // case 0 is not called anymore
       prevMillis = millis(); // so mark here
     }
     break;
   case 1: // 4.5 turns down, all motors
     if (millis() - prevMillis > 8000) {
       for (byte i = 0; i < motors; i++) newPos[i] = 9216; // 4.5 * 2048
       sequence = 2;
     }
     break;
   case 2: // all return to zero, one after the other
     if (millis() - prevMillis > 30000 + interval) {
       if (index < motors) {
         newPos[index] = 0;
         interval += 500;
         index += 1;
       } else { // when done
         index = 0;
         interval = 0;
         sequence = 3;
       }
     }
     break;
   case 3: // run sequence(s) again
     if (millis() - prevMillis > 75000) {
       prevMillis = millis();
       sequence = 1;
     }
     break;
 }
}

Where does your code make an attempt to move the steppers?

This is not my code. It's copied from the thread I mentioned.
Steppers should move by

I can't see why steppers should move by that code, and you cannot see them move. So we agree that something essential is missing from your code.

Please take a look at reply 275 of the following thread. There's also a video of the spheres moving.

Did you or anyone find what's missing in that code ?

Yes, a big part seems to be missing.
This is the full code.
Working, nothing wrong with is.
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
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

void setup() {
  SPI.begin();
  pinMode(clrPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  digitalWrite(clrPin, HIGH); // clear all shift register data
  for (byte i = 0; i < motors; i++) nowPos[i] = 1024; // homing all motors 1/2 turn
}

void loop() {
  switch (sequence) {
    case 0: // break free from the top, all motors
      if (nowPos[0] == newPos[0]) { // finished homing
        for (byte i = 0; i < motors; i++) nowPos[i] = -128; // 1/16 turn
        torque = true; // was low power during homing
        sequence = 1; // case 0 is not called anymore
        prevMillis = millis(); // so mark here
      }
      break;
    case 1: // 4.5 turns down, all motors
      if (millis() - prevMillis > 8000) {
        for (byte i = 0; i < motors; i++) newPos[i] = 9216; // 4.5 * 2048
        sequence = 2;
      }
      break;
    case 2: // all return to zero, one after the other
      if (millis() - prevMillis > 30000 + interval) {
        if (index < motors) {
          newPos[index] = 0;
          interval += 500;
          index += 1;
        } else { // when done
          index = 0;
          interval = 0;
          sequence = 3;
        }
      }
      break;
    case 3: // run sequence(s) again
      if (millis() - prevMillis > 75000) {
        prevMillis = millis();
        sequence = 1;
      }
      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
}

Now everybody can see that just this essential line is missing from the code in #1 as suspected in #2?

@DrDiettrich Thanks. I had copied the code blindly.

Thank you Wawa, I can now stop banging my head against the wall.
Still one thing is not clear to me, the steppers are fed with 12v even if they are the 5v version ? Or I'd fry the chips ?

5volt stepper motors and a 12volt supply will of course fry the chips, and eventually the motors.
For 5volt stepper motors you must use a 5volt supply (ignore the 12volt marking on the board).
It is wise to use a separate 5volt motor supply, and a separate supply for the Uno (or Nano).
A 5volt cellphone supply with USB socket is perfect for the Uno. Just plug the USB lead into it.
Leo..

Success ! As Galileo said "E pur si muove", "And yet it moves".
Thank you Wawa.

Can someone please help me modify/expand Wawa's code ?
for example how would I move all steppers together instead of one by one?
I've tried with no success.

Learn about switch statements.
I have used it to step through events (you can have many).
Look at case 1

for (byte i = 0; i < motors; i++) newPos[i] = 9216;

this 'for' loop selects all motors, and directs them to a "newPosition 9216"

  1. Wait until it's time to do something (8 seconds from start).
  2. Do something. 9216 steps away from position zero = 4.5 turns (down) for all motors.
  3. Go to case 2, where you wait for the next event.

Leo..

Thanks Leo, I understand switch statements.
To learn how shift registers work, I read articles and followed the tutorials I could find.
All the tutorials on the net regarding shift registers refer to LED's.
I followed one of these, hooked up some LED's and learned the principles.

Then I tried to modify your code for another project I'm doing ( I've postponed the BMW one cause I don't think I'm up to it for now).
In this project, much simpler, I need to move 41 steppers all together at slow speed to an array of prefixed positions and stay there until a PIR sensor is triggered, then move to the next prefixed position and so on.
I tried to simplify your code, taking out sequences, millis, micros, speed and torque keeping the essential part of your goPos function but somehow I'm goofing up somewhere.

I just need to read the current position from the EEPROM on power up, calculate the steps I need to go to one of the prefixed positions and move all the steppers there. Then update the EEPROM with the new position.

Don't understand why you want to do this.
That goPos() block takes care of all motor movements/speed/braking/overshoot, with minimal load on the MCU. Taking things out will surely result in missing steps.

Can't you just write the nowPos[] array to EEPROM, and load it back in setup().
Leo..

Because your code does more than I need and does it differently. For example "homing" and "breaking away" and the part where all motors go to "home position" one by one.

Maybe I wasn't clear enough describing my project.
I have a prefixed array (example) TargetPos[100, 165, 220, 350 ....., 490].
At boot up, in the void setup, I read the last known position (one integer) of all steppers (they all move together and to the same position) from the EEPROM.
In the void loop I keep checking if a PIR sensor is triggered. if yes, then I'd call my GoToPos function. This function generates a random number (n), calculates the number of half steps need to reach TargetPos[n], moves all steppers to that position and stays there. It then writes the nowPos to EEPROM and maybe cut off current to the steppers. Back to void loop checking the the PIR sensor.

I've got everything figured out except the part where I send Hex code to all shift registers to activate the coils of the steppers for "x" steps or half steps to go to TargetPos[n].

CurrentPos = x
NewPos = y
StepsToTake = y - x
if StepsToTake > 0 then go forward, else go backward.

Please tell me the code to move all steppers -> StepsToTake slowly.

Thank you.

Don't mess with the goPos() block, and make sure loop() runs fast (<= 1953us).

Just fill the newPos array with positions you want the motors to go to.
Like

newPos[] = {100, 165, 220, 350 ....., 490};

Then the goPos() block will take care of the actual motor movements.
Leo..

Leo,
I already tried that approach, it won't work. The motors do a half turn then just sit there.
I realize that due to my lack of knowledge I can't do this by myself and also I hate to keep bothering you. I'd like to post in the "paid job" section of the forum but I read you have to ask a moderator to move the thread to "gigs" ?!?
How does it work ?

Post your code here.
There are way more clever people than me on this forum that could help you.
And your posts might inspire other people to build something similar.
Leo..