Stepper Controls - Back To Zero Problem

Hi,
The following code should read the position of steppers from EEPROM, move them to the zero position, make a half turn then stop the motors. In case of an AC fail (opto coupler), it saves the "nowPos" of the steppers on EEPROM for the next boot up.
But if I set a target position of let's say 1024, simulate an AC fail at let's say at 500 and restart the motors go to zero, back to 500 then to 1024 position instead of going to zero and then to 1024.
Where is my mistake ?

/*
  Nano | TPIC6B595
    5V | 2 (VCC)
   GND | GND
    D8 | 8 (SRCLR) - blue
   D10 | 12 (RCK) - green
   D11 | 3 (SER IN) of the first chip - yellow
   D13 | 13 (SRCK) - orange
       | 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 = 96; // sets of four motors
const byte optoPin = 2; // interrupt pin
const byte clrPin = 8; // TPIC6B595 SRCLR pin8
const byte latchPin = 10; // TPIC6B595 RCK pin12
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
unsigned long prevMillis, prevMicros, interval, startMicros; // timing
byte val[4]; // TPIC write bytes
bool torque = true; // true is full power, false is half power
bool brownOut; // mains failure detected flag
byte sequence, index; // patterns, motor index
const unsigned int startAddress = 0; // option to change for wear leveling

int targetPos = 1024;

const byte Start = 0; 
const byte Go_To_Zero = 1;
const byte Go_To_Target = 2;
const byte Idle = 3;  
const byte Write_EEPROM = 255;


void setup() {
 Serial.begin(115200);
  Serial.println("Testing");
 
  SPI.begin();
  pinMode (optoPin, INPUT_PULLUP);
  pinMode(clrPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  digitalWrite(clrPin, HIGH); // clear all shift register data
  attachInterrupt(digitalPinToInterrupt(optoPin), trigger, RISING);
  EEPROM.get(startAddress, nowPos); // load the saved positions


}

void loop() {

switch (sequence) {
    
    case Start: 
      for (byte i = 0; i < motors; i++) {
        if (nowPos[i] != newPos[i]) break;} 
      sequence = Go_To_Zero; // case 0 is not called anymore
      prevMillis = millis(); // mark start of sequence 1
      break;

    case Go_To_Zero: 
      if (millis() - prevMillis > 5000) {  
        for (byte i = 0; i < motors; i++) newPos[i] = nowPos[i]* -1; 
         prevMillis = millis();
        sequence = Go_To_Target;  
      }
      break;

    case Go_To_Target:
    for (byte n = 0; n < motors; n++){
      nowPos[n] = 0;}

    if (millis() - prevMillis > 5000) {  
        for (byte i = 0; i < motors; i++) newPos[i] = targetPos; 
        prevMillis = millis();
        sequence = Idle;  
      }
      break;
     
    case Idle:
    sequence = 22; // no such sequence
    break;

    case Write_EEPROM: // eeprom write

    Serial.println("AC_FAIL");
    
      for (byte i = 0; i < motors; i++) {
        if (nowPos[i] != newPos[i]) break;
          EEPROM.put(startAddress, nowPos);
          while (1); // wait for a reset
      }
  }
  goPos(); 

}
  
void trigger() { // ISR
  startMicros = micros(); // mark the start of a zero crossing
}

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
  if (!brownOut && digitalRead(optoPin) && micros() - startMicros > 1200) { // no AC outside the zero crossing
    for (byte i = 0; i < motors; i++) {
      newPos[i] = nowPos[i]; // stay were you are, or get there after overshoot
      lag[i] = 0; // at full speed
    }
    torque = false; // low power deceleration, to maximise hold-up time
    // rampSteps = 3; // shorten deceleration time
    sequence = 255; // goto an unused case that manages the eeprom writes
    brownOut = true; // no way back, but keep on decelerating
  }
  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
}

It looks like you are losing bits 2 and 3 (MSB first). Should you & with F? (edit: 0xf)

Sorry, I don't understand you. The "goPos" function was kindly given to me by WAWA in this forum. I don't have enough programming knowledge to change it. While testing, I noticed loosing 1 step but my question is why I can't zero position the steppers correctly.

This is never going to work.

Pls explain why ?

What guarantees your motors are not moved when powered off?
What guarantees that the starting position is known?
...

Motors are set with the shaft upwards, they don't move when powered off.
The starting position is the last position read from EEPROM when AC failed.

That's an assumption that may or may not work for N amount of retries, not a guarantee.

I see some trouble here:

Maybe @Wawa can make clear the code chunk in post #2?

The goPos() function has been tested extensively, and contains no errors.
It all started in this thread, where we did establish that fast mains fail detection and the hold-up time of the supply would give us enough time to write stopped and powered down motor positions to EEPROM.

Serial.println("AC_FAIL"); is wasting time, which you don't have.
If you have to use a serial.print, then do it after you have written to EEPROM.

I think the main problem lies here.
void setup() {
EEPROM.get(startAddress, nowPos); // load the saved positions
EEPROM.get(startAddress, newPos); // newPos[] also has to be loaded

The for loop in case Start: becomes now obsolete.
Leo..

1 Like

The code I posted is a very short version of my original code.
Here, I'm testing with only 4 motors and not 96. The Serial.print was just to test when I hit that sequence, forgot to erase it.

I tested as your suggestion, i.e. reading newPos from EEPROM and no loop in case Start, but steppers won't home to zero. They do a half a turn from where they stopped at AC fail.

With those two EEPROM lines in setup there should be no movement on startup.
Because the same motor position that has been saved is loaded again (could be any position).

Remaining problems could be in your "Go_To_Zero" code.
Don't know what "zero" is, but can't you just use
for (byte i = 0; i < motors; i++) newPos[i] = 0; // go to zero
instead of
for (byte i = 0; i < motors; i++) newPos[i] = nowPos[i]* -1;
Leo..

1 Like

Motor positions are held in an int array, nowPos[].
Which one of the four motor coils of the 28BYJ-48 is powered is extracted from that motorPos[] array with bit masking (the two LSBits, & 3 or 0B11).
So if the nowPos value increases/decreases, so does the switch/case coil selector.
This simple bitwise code replaces the otherwise long code you see for these motors.
Leo..

1 Like

Your plan will ONLY work if you ONLY do full steps for each movement. Your motor will ALWAYS start on a full step position when power is first applied!

Correct. And the code is written for that (4-sequence, fullstep).

if (nowPos[i] != newPos[i]) break;
EEPROM.put(startAddress, nowPos);

Escape (break) until the motors have reached their destination.

if (newPos[i] == nowPos[i])val[i & 3] = 0; // cut coil power when there

Then write to EEPROM.
Leo..

1 Like

@Wawa, "There should be no movement on startup", but there is ! Given equal positions there should be no movement.
Here, in setup() I put 512 as newPos and nowPos to test.
Motors move 1/4 of a turn with or without the for loop in Start.

/*
  Nano | TPIC6B595
    5V | 2 (VCC)
   GND | GND
    D8 | 8 (SRCLR) - blue
   D10 | 12 (RCK) - green
   D11 | 3 (SER IN) of the first chip - yellow
   D13 | 13 (SRCK) - orange
       | 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 = 6; // sets of four motors
const byte optoPin = 2; // interrupt pin
const byte clrPin = 8; // TPIC6B595 SRCLR pin8
const byte latchPin = 10; // TPIC6B595 RCK pin12
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
unsigned long prevMillis, prevMicros, interval, startMicros; // timing
byte val[4]; // TPIC write bytes
bool torque = true; // true is full power, false is half power
bool brownOut; // mains failure detected flag
byte sequence, index; // patterns, motor index
const unsigned int startAddress = 0; // option to change for wear leveling

int targetPos = 1024;

const byte Start = 0; 
const byte Zero_At_Start = 1;
const byte Go_To_Target = 2;
const byte Idle = 3;  
const byte Write_EEPROM = 255;


void setup() {
 Serial.begin(115200);
  Serial.println("Testing");
 
  SPI.begin();
  pinMode (optoPin, INPUT_PULLUP);
  pinMode(clrPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  digitalWrite(clrPin, HIGH); // clear all shift register data
  attachInterrupt(digitalPinToInterrupt(optoPin), trigger, RISING);
  EEPROM.get(startAddress, nowPos); // load the saved positions
 EEPROM.get(startAddress, newPos);

for (byte i = 0; i < motors; i++) { // Just for testing
  nowPos[i] = 512;
  newPos[i] = 512;}


}

void loop() {

 switch (sequence) {
    
    case Start: 
    /*
      for (byte i = 0; i < motors; i++) {
        if (nowPos[i] != newPos[i]) break;} 
  */
      sequence = Zero_At_Start; // case 0 is not called anymore
      prevMillis = millis(); // mark start of sequence 1
      break;

    case Zero_At_Start: 
      
    if (millis() - prevMillis > 5000) {  
        for (byte i = 0; i < motors; i++) 
        newPos[i] = 0; //stopPos[i] * -1;

        prevMillis = millis();
        sequence = Idle; //Go_To_Target;  
        
      }
      break;

    case Go_To_Target:
    for (byte n = 0; n < motors; n++){
      nowPos[n] = 0;}

    if (millis() - prevMillis > 5000) {  
        for (byte i = 0; i < motors; i++) 
        newPos[i] = targetPos;

        prevMillis = millis();
        sequence = Idle;  
        
      }
      break;
     
    case Idle:
    sequence = 22; // no such sequence
    break;

    case Write_EEPROM: // eeprom write

      for (byte i = 0; i < motors; i++) {
        if (nowPos[i] != newPos[i]) break;
          EEPROM.put(startAddress, nowPos);
          while (1); // wait for a reset
      }
  }
  goPos(); 

}
  


Go 10 steps backwards, and see if this cut-down loop moves the motors.
Run for 10 seconds, then push the reset button.
The motors shouldn't move...

Changed nb_motors from 6 to 4, because that's what the code expects. Sets of 4.
Do you still have that mains opto in circuit. Otherwise connect pin2 to ground during the test.
Leo..

/*
  Nano | TPIC6B595
    5V | 2 (VCC)
   GND | GND
    D8 | 8 (SRCLR) - blue
   D10 | 12 (RCK) - green
   D11 | 3 (SER IN) of the first chip - yellow
   D13 | 13 (SRCK) - orange
       | 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 = 4; // sets of four motors
const byte optoPin = 2; // interrupt pin
const byte clrPin = 8; // TPIC6B595 SRCLR pin8
const byte latchPin = 10; // TPIC6B595 RCK pin12
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
unsigned long prevMillis, prevMicros, interval, startMicros; // timing
byte val[4]; // TPIC write bytes
bool torque = true; // true is full power, false is half power
bool brownOut; // mains failure detected flag
byte sequence, index; // patterns, motor index
const unsigned int startAddress = 0; // option to change for wear leveling

void trigger() { // ISR
  startMicros = micros(); // mark the start of a zero crossing
}

void setup() {
  Serial.begin(115200);
  Serial.println("Testing");
  SPI.begin();
  pinMode (optoPin, INPUT_PULLUP);
  pinMode(clrPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  digitalWrite(clrPin, HIGH); // clear all shift register data
  attachInterrupt(digitalPinToInterrupt(optoPin), trigger, RISING);
  EEPROM.get(startAddress, nowPos); // load the saved positions
  EEPROM.get(startAddress, newPos);
  prevMillis = millis(); // mark
}

void loop() {
  switch (sequence) {
    case 0:
      if (millis() - prevMillis > 5000) sequence = 255; // test 5 seconds for movement
      break;
    case 255: // eeprom write
      for (byte i = 0; i < motors; i++) if (nowPos[i] != newPos[i]) break;
      EEPROM.put(startAddress, nowPos);
      while (1); // wait for a reset
  }
  goPos();
}

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
  if (!brownOut && digitalRead(optoPin) && micros() - startMicros > 1200) { // no AC outside the zero crossing
    for (byte i = 0; i < motors; i++) {
      newPos[i] = nowPos[i]; // stay were you are, or get there after overshoot
      lag[i] = 0; // at full speed
    }
    torque = false; // low power deceleration, to maximise hold-up time
    // rampSteps = 3; // shorten deceleration time
    sequence = 255; // goto an unused case that manages the eeprom writes
    brownOut = true; // no way back, but keep on decelerating
  }
  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
}
1 Like

@Wawa,
I ran your code, I don't understand ..

no movements.

For this test, I'm not using the opto. Just a jumper wire from pin 2 to GND which I disconnect to simulate AC out.

In my full project, I had came up with the following code where instead of reading the nowPos from EEPROM I read a new array called the stopPos. The following code wrks. i.e. moves the steppers back to "zero" position. But only if after that I go to "idle". If after "zeroing" I try to move to a new position "TargetPos", It won't "zero" just goes to targetPos.
Please take a look, it's driving me nuts !

/*
  Nano | TPIC6B595
    5V | 2 (VCC)
   GND | GND
    D8 | 8 (SRCLR) - blue
   D10 | 12 (RCK) - green
   D11 | 3 (SER IN) of the first chip - yellow
   D13 | 13 (SRCK) - orange
       | 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 = 4; // sets of four motors
const byte optoPin = 2; // interrupt pin
const byte clrPin = 8; // TPIC6B595 SRCLR pin8
const byte latchPin = 10; // TPIC6B595 RCK pin12
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
unsigned long prevMillis, prevMicros, interval, startMicros; // timing
byte val[4]; // TPIC write bytes
bool torque = true; // true is full power, false is half power
bool brownOut; // mains failure detected flag
byte sequence, index; // patterns, motor index
const unsigned int startAddress = 0; // option to change for wear leveling

int stopPos[motors];

int targetPos = 1024;
byte m = 0;


const byte Start = 0; 
const byte Zero_At_Start = 1;
const byte Go_To_Target = 2;
const byte Idle = 6;  
const byte Write_EEPROM = 255;


void setup() {
 Serial.begin(115200);
  Serial.println("Testing");
 
  SPI.begin();
  pinMode (optoPin, INPUT_PULLUP);
  pinMode(clrPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  digitalWrite(clrPin, HIGH); // clear all shift register data
  attachInterrupt(digitalPinToInterrupt(optoPin), trigger, RISING);
  //EEPROM.get(startAddress, nowPos); // load the saved positions
 //EEPROM.get(startAddress, newPos);
  EEPROM.get(startAddress, stopPos);

 //Write_Read_EEPROM();

 
 
for (byte i = 0; i < motors; i++) {
  stopPos[i] = 512;
  nowPos[i] = 0;
 // newPos[i] = 512;
  }



}

void loop() {

switch (sequence) {
    
    case Start: 
    
      for (byte i = 0; i < motors; i++) {
        if (nowPos[i] != newPos[i]) break;} 
  
      sequence = Zero_At_Start; 
      prevMillis = millis(); 
      break;

    case Zero_At_Start:
    
    if (millis() - prevMillis > 3000) {  
        for (byte i = 0; i < motors; i++) 
        newPos[i] = stopPos[i]* -1;

        prevMillis = millis();
        sequence = Idle; // Go_To_Target;  <<<<<< !
    }
      
      break;
    

    case Go_To_Target:

    for (byte i = 0; i < motors; i++){
      nowPos[i] = 0;}

    if (millis() - prevMillis > 5000) {  
        for (byte i = 0; i < motors; i++) 
        newPos[i] = targetPos;

        prevMillis = millis();
        sequence = Idle;  
        
      }
      break;
     
    case Idle:
    sequence = 22; // no such sequence
    break;

    case Write_EEPROM: // eeprom write

      for (byte i = 0; i < motors; i++) {
        if (nowPos[i] != newPos[i]) break;
          EEPROM.put(startAddress, nowPos);
          while (1); // wait for a reset
      }
     
  }
  goPos(); 

}

Good.

With 10 steps back I meant, forget your sequence for now, and don't add any other band-aids.

Now replace loop() with this loop(). (with the sketch from post#18).
It should sweep between position 0 and 512, and restart exactly the same after a simulated power cut. Assuming the motors have the ability to move freely between those positions.
Leo..

void loop() {
  switch (sequence) {
    case 0:
      if (millis() - prevMillis > 5000) { // test 5 seconds for movement (should be none)
        for (byte i = 0; i < motors; i++) newPos[i] = 0;
        sequence = 1;
      }
      break;
    case 1:
      if (millis() - prevMillis > 5000) { // wait for previous motor movement to complete
        for (byte i = 0; i < motors; i++) newPos[i] = 512; // then go to 512
        sequence = 0; // repeat
      }
      break;
    case 255: // eeprom write
      for (byte i = 0; i < motors; i++) if (nowPos[i] != newPos[i]) break;
      EEPROM.put(startAddress, nowPos);
      while (1); // wait for a reset
  }
  goPos();
}
1 Like