MicroBahner/MobaTools

Stepper movement only works if I have some serial.Print commands running in the code

Opened this issue · 9 comments

The stepper.moveTo() commands reliably as long as I have serial commands running in the loop(). If I remove all serial.Print commands or edited out the serial.Begin() line in setup() the steppers motors don't run correctly anymore. One of three motors randomly will get stuck running and continue running past any software limits I've put in the code.

Attached is the current code with the some serial.Print commands left in:

RemoteArm_Smooth.txt

and here's a video demonstrating the behavior: yotube.

I tried adding a delay(1); without any serial.Print commands, and that didn't fix it.

Hi Tristanc,
which board are you using? I suppose a Mega?

You serial.println() statements add a considerable delay to your loop cycle. Much more than dela(1) because of your low baudrate. Because the printing is done with every loop, you overrun the serial buffer, and this adds a delay of 1ms for every char printed ( about 8ms for your println(a2).
I suppose without that delay your lowSteps and highSteps jitter too much.

Some additional comments:
You shouldn't do so:

while (digitalRead(3)== HIGH) {
    stepperHigh.rotate(-1);
}

what calls rotate every few µseconds. You need to call it only once:

stepperHigh.rotate(-1);
while (digitalRead(3)== HIGH) ;

And I don't know what you want to achieve with this:

stepperHigh.setZero();
stepperHigh.writeSteps(1750);
stepperHigh.setZero(1750);

it should be the same as

stepperHigh.setZero(1750);
stepperHigh.writeSteps(0);

And moveTo() needs long as parameter, not float.

Yeah, it's a mega. I was just doing the calculations and realized how much delay I had in there with that print statement. It actually runs OK like that. But a better solution would probably be to use a slower speed on the steppers?

And I did have some problems with homing and setting the positions. I don't think I really understood the documentation when writing those sections. Thanks for the tips on how to clean everything up :)

I'm trying to get everything to respond to the movement of the remote arm smoothly, but also feel fast enough that it doesn't feel sluggish. This is my first time using mobatools, so any tips on tuning speed/ramps/etc. would be great appreciated.

Thanks!

I just tried removing the serial commands, and setting the speed for all the steppers to 1000, and it moves much slower, but still has the behavior where one of the steppers just gets stuck on and rotates continuously until I cut power.

This never happens during the homing sequences, and it won't happen immediately with normal movement, but it happens within a few seconds fairly reliably.

I changed all the steps to be longs instead of floats, that didn't have an impact. And I experimented with replacing the serial.Print with different length delay() commands. 10 milliseconds would delay the problem, but it happened a little while later. a delay(20); seems to "fix" it just like the serial.Print.

I have I believe the same issue. My program moves a sliding carriage controlled by turning an encoder knob. Ever time I turn the the knob, it updates the target position for myStepper.moveTo and also serial prints the new target position to the terminal.
I have to add either a delay(7) to not have the stepper runaway as well as not randomly jerk backwards, or with serial at 115200, add a 68 character string to serial.println in order for the stepper to work normally.

Starting at row 47 I show the different delays or serial print strings I've tried, along with what they did.

I'm pretty new with Adruino and C++, hopefully I provided something useful.

#include <MobaTools.h>

// parameters for quadrature encoder knob
static int pinA = 2; // Our first hardware interrupt pin is digital pin 2
static int pinB = 3; // Our second hardware interrupt pin is digital pin 3
volatile byte aFlag = 0; // let's us know when we're expecting a rising edge on pinA to signal that the encoder has arrived at a detent
volatile byte bFlag = 0; // let's us know when we're expecting a rising edge on pinB to signal that the encoder has arrived at a detent (opposite direction to when aFlag is set)
volatile int encoderPos = 0; //this variable stores our current value of encoder position. Change to int or uin16_t instead of byte if you want to record a larger range than 0-255
volatile int oldEncPos = 0; //stores the last encoder position value so we can compare to the current reading and see if it has changed (so we know when to print to the serial monitor)
volatile byte reading = 0; //somewhere to store the direct values we read from our interrupt pins before checking to see if we have moved a whole detent

// defines stepper pin numbers
const byte stepPin = 52;
const byte dirPin  = 53;
const byte enaPin = 36;
const int stepsPerRev = 400;   // Steps per Revolution ( example with 1/4 microsteps )
long  targetPos = 0;         // stepper moves between 0 and targetpos

MoToStepper myStepper ( stepsPerRev, STEPDIR );

int stepperSpeed = 4000;

void setup(){
// setup encoder pins and interrupts
  pinMode(pinA, INPUT_PULLUP); // set pinA as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  pinMode(pinB, INPUT_PULLUP); // set pinB as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  attachInterrupt(0,PinA,RISING); // set an interrupt on PinA, looking for a rising edge signal and executing the "PinA" Interrupt Service Routine (below)
  attachInterrupt(1,PinB,RISING); // set an interrupt on PinB, looking for a rising edge signal and executing the "PinB" Interrupt Service Routine (below)
  Serial.begin(115200); // start the serial monitor link

// setup stepper
  myStepper.attach( stepPin, dirPin );
  myStepper.attachEnable( enaPin, 10, LOW );        // Enable active
  myStepper.setSpeed( stepperSpeed);
  myStepper.setRampLen( 100 );
}

void loop(){

  funcMotorControl();

  if(oldEncPos != encoderPos) {
    Serial.println(encoderPos);
    oldEncPos = encoderPos;
  }

//  delay(5); // does not stop "runaway"
//  delay(6); // stops "runaway", except stepper has random jerks in the wrong direction
//  delay(7);  // stops "runaway", motor works as expected.
//  Serial.println("1234567890123456789012345678901234567890123456789012345678901"); // does not stop "runaway", motor has random jerks in the wrong direction
//  Serial.println("12345678901234567890123456789012345678901234567890123456789012"); // stops "runaway", except motor has random jerks in the wrong direction
  Serial.println("12345678901234567890123456789012345678901234567890123456789012345678"); // stops "runaway", motor works as expected.

}

void funcMotorControl(){
  if ( myStepper.currentPosition() != targetPos) {

      myStepper.moveTo( targetPos );
    }
}

//interrupt functions for encoder knob
void PinA(){
  cli(); //stop interrupts happening before we read pin values
  reading = PINE & 0x30; // read all eight pin values then strip away all but pinA and pinB's values
  if(reading == B00110000 && aFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
    encoderPos --; //decrement the encoder's position count
    targetPos = encoderPos;
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00010000) bFlag = 1; //signal that we're expecting pinB to signal the transition to detent from free rotation
  sei(); //restart interrupts
}

void PinB(){
  cli(); //stop interrupts happening before we read pin values
  reading = PINE & 0x30; //read all eight pin values then strip away all but pinA and pinB's values
  if (reading == B00110000 && bFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
    encoderPos ++ ; //increment the encoder's position count
    targetPos = encoderPos;
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00100000) aFlag = 1; //signal that we're expecting pinA to signal the transition to detent from free rotation
  sei(); //restart interrupts
}

Hi,
the problem here is, you set a new target very often and very fast *). If you do so, obviously there is a problem, that the direction is randomly set wrong, so that the target can never be reached. The motor spins endlessly.
I was able to reproduce the error at my testset and will have a closer look at it.

*) and you change the target in very small ( maybe single ) steps very fast with an activated ramp. This seems to be the main problem ( and that's not the intended use of MoToStepper ;) . But of course it should not lead to endless movement.

I appreciate your work on this library. In my use, I am jogging a cart into place manually (based on unique size of physical objects) before starting a routine that would have a generally set distance. How would you suggest jogging? Should I look at a different library?

Are you moving the stepper manually via a joystick ( or your encoder ) ?
At least it is always a mechanical system, And I don't think changing the target too often does make any sense.
I would set rampLen to zero and a moderate speed when you move it via your encoder. And actually change the target only every 30 or 50ms and not with any change of the encoder.
And what type of encoder do you use? I don't see any debouncing - which may be a big prolem, especially when using interrupts.

It may take a while until I can publish a fixed version.

I'm using a 6 pin rotary pulse generator knob used on CNC machine pendants.
I tested with updating the targetPos in the main loop only after 200ms or more and still had the issue if I had the rampLen 1.
As soon as I set rampLen to 0, my original sketch seems to be fine even randomly moving the job wheel for a couple minutes both in same direction as well as changing directions.

Yes, the problem pops up, if ramplen is >0. If the new target ist too near to the actual position, no useful ramp can be computed, and this seems to be the problem to solve. That's why I suggested to set rampLen to zero while positioning manually. ( You can change ramplen at any time, so set it to the desired value after manual positioning ).