0

Motor Shield V2.3 Speed Changes With Multiple Steppers
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Motor Shield V2.3 Speed Changes With Multiple Steppers

by BeeCoughs on Mon Aug 19, 2019 11:30 am

Hi there.

I recently bought a shield with intention to create a set of syringe-type pumps using stepper motors to be controlled via TTLs. I'm using the AccelStepper library and just two motors (https://www.adafruit.com/product/324) hooked up to a shield on an Arduino Uno, but would like to be able to expand to four with a stacked shield. However, in my initial tests I am running into some problems I can't wrap my head around.

There are considerable speed changes when running two steppers simultaneously versus one alone. I double checked that the power adapter is well suited to cover the max current draw from two steppers (12V, 3A) and that I'm using speed/acceleration settings that others have had success with. However, the pumps slow down when running both regardless. Even if one is completely unwired from the shield, the hooked up one will slow when receiving a TTL for the second. More confusing to me, it still happens when I set the speed to half the rate since I would think it should be querying whether or not there is a step due at least as often as for two steps on one motor at full speed (if that makes sense). So I am guessing it is a limitation of the libraries or at least how I am implementing them, but I am having trouble figuring out how to solve the issue.

My code is as follows (adapted from the Accel_MultiStepper code from the Adafruit examples):
Code: Select all | TOGGLE FULL SIZE
#include <Wire.h>
#include <AccelStepper.h>
#include <Adafruit_MotorShield.h>

// I2C clock frequency for higher stepping speeds
int clockFrequency = 400000;

// Set input pins
const int RetractPin = 3;
const int Pump1Pin = 4;
const int Pump2Pin = 5;

// Variables for button presses
int RBreading = LOW;
int B1reading = LOW;
int B2reading = LOW;

// Define shields according to I2C address
Adafruit_MotorShield AFMSbot(0x60); // Default address, no jumpers

// Connect one stepper with 200 steps per revolution (1.8 degree)
// to the bottom shield
Adafruit_StepperMotor *myStepper1 = AFMSbot.getStepper(200, 1);
Adafruit_StepperMotor *myStepper2 = AFMSbot.getStepper(200, 2);

// you can change these to DOUBLE or INTERLEAVE or MICROSTEP!
// wrappers for the first motor!
void forwardstep1() {
  myStepper1->onestep(FORWARD, SINGLE);
}
void backwardstep1() {
  myStepper1->onestep(BACKWARD, SINGLE);
}
// wrappers for the second motor!
void forwardstep2() {
  myStepper2->onestep(FORWARD, SINGLE);
}
void backwardstep2() {
  myStepper2->onestep(BACKWARD, SINGLE);
}

// Now we'll wrap the 4 steppers in an AccelStepper object
AccelStepper stepper1(forwardstep1, backwardstep1);
AccelStepper stepper2(forwardstep2, backwardstep2);

void setup()
{

  Wire.setClock(clockFrequency);

  Serial.begin(9600);

  pinMode(Pump1Pin, INPUT);
  pinMode(Pump2Pin, INPUT);
  pinMode(RetractPin, INPUT);

  AFMSbot.begin(); // Start the bottom shield

  stepper1.setSpeed(100.0);
  stepper1.setAcceleration(500.0);

  stepper2.setSpeed(100.0);
  stepper2.setAcceleration(500.0);

}

void loop()
{
  RBreading = digitalRead(RetractPin);
  B1reading = digitalRead(Pump1Pin);
  B2reading = digitalRead(Pump2Pin);


  // --------------Retract button------------------
  //  Use with other pump pins to retract
  if (RBreading == HIGH) {
    if (B1reading == HIGH) {
      if (stepper1.distanceToGo() <= 250) {
        stepper1.move(1000);
      }
    }
    if (B2reading == HIGH) {
      if (stepper2.distanceToGo() <= 250) {
        stepper2.move(1000);
      }
    }
  }

  // -------------Infusion buttons----------------
  // Pump 1 - continuous infusion on button press
  if (B1reading == HIGH && RBreading == LOW) {
    if (stepper1.distanceToGo() >= -250) {
      stepper1.move(-1000);
    }
  }
  if (B1reading == LOW) {
    stepper1.move(0);
  }

  // Pump 2 - continuous infusion on button press
  if (B2reading == HIGH && RBreading == LOW) {
    if (stepper2.distanceToGo() >= -250) {
      stepper2.move(-1000);
    }
  }
  if (B2reading == LOW) {
    stepper2.move(0);
  }
 
  // Debug: Print distance to go over serial
  int D2G[2] = {stepper1.distanceToGo(), stepper2.distanceToGo()};
  Serial.print(D2G[0]);
  Serial.println(D2G[1]);

  // Never delay so these are called at the baud rate
  stepper1.run();
  stepper2.run();
}


The serial printouts look as expected, but the behavior of the motors is still strange as I describe above. If we are trying to calibrate infusion size based on TTL length, and might have multiple infusions happening simultaneously, it seems like that won't be doable because of this problem. If it's possible to avoid hard-coding the infusion sizes (steps traveled) and keep this "gate-based" approach that would be great, but I'm beginning to wonder if it's time to abandon it for a different method of setting them for the sake of consistency.

BeeCoughs
 
Posts: 3
Joined: Mon Aug 19, 2019 10:26 am

Re: Motor Shield V2.3 Speed Changes With Multiple Steppers

by adafruit_support_bill on Mon Aug 19, 2019 11:43 am

The maximum combined step-rate for all motors is limited by the speed of the i2c bus. Since it is a shared bus, this applies to applications with multiple shields also.

It is possible to increase the speed of the bus as described in the FAQ: https://learn.adafruit.com/adafruit-mot ... aq-2861894
Some users have reported running successfully at 1MHz.

Further speed increases are possible with some tweaks to the library:
viewtopic.php?f=31&t=57041&p=292119

adafruit_support_bill
 
Posts: 74337
Joined: Sat Feb 07, 2009 10:11 am

Re: Motor Shield V2.3 Speed Changes With Multiple Steppers

by BeeCoughs on Mon Aug 19, 2019 1:49 pm

Yes. I had seen some of the i2c timing tweaks and have that implemented in the code I posted.

I still am unsure why I'd see bandwidth problems after dropping the original speed by half (for 2 motors), since I would think that would require the same bandwidth as running one motor at the original speed (please correct me if I'm wrong).

BeeCoughs
 
Posts: 3
Joined: Mon Aug 19, 2019 10:26 am

Re: Motor Shield V2.3 Speed Changes With Multiple Steppers

by adafruit_support_bill on Mon Aug 19, 2019 2:07 pm

i2c communication is not the only limiting factor. There is execution overhead in the code as well.

In particular, there is this code:
Code: Select all | TOGGLE FULL SIZE
  // Debug: Print distance to go over serial
  int D2G[2] = {stepper1.distanceToGo(), stepper2.distanceToGo()};
  Serial.print(D2G[0]);
  Serial.println(D2G[1]);

  // Never delay so these are called at the baud rate
  stepper1.run();
  stepper2.run();


If by "baud rate" you mean the i2c clock frequency, that will not be the limiting factor. In your setup you set the serial baud rate to 9600:
Serial.begin(9600);

This translates to roughly 1 millisecond per character. So that serial output will impose a minimum of 4 milliseconds between the calls to run();

adafruit_support_bill
 
Posts: 74337
Joined: Sat Feb 07, 2009 10:11 am

Re: Motor Shield V2.3 Speed Changes With Multiple Steppers

by BeeCoughs on Mon Aug 19, 2019 4:09 pm

Ah I was very much confused about clock rates and didn't realize I was shooting myself in the foot by adding those debug lines. I meant "max rate" when I said baud before, but I guess I was correct in that comment without realizing it!

I also saw I had implemented the change to the i2c bus speed incorrectly according the other forum post you linked me to: mine was initially before the call to begin() for the shield which apparently resets it to the default rate. I used the line suggested from that thread to set the speed: TWBR = ((F_CPU / 400000l) - 16) / 2 rather than Wire.setClock(400000). I'm not sure if that made a difference, but it seems to be working now. Thanks for your help, Bill!

BeeCoughs
 
Posts: 3
Joined: Mon Aug 19, 2019 10:26 am

Re: Motor Shield V2.3 Speed Changes With Multiple Steppers

by adafruit_support_bill on Mon Aug 19, 2019 4:34 pm

Good to hear that's working for you. Thanks for the update.
The setClock() command was added to the Wire library after that post was written. It should do the same thing - only it will work on other processors instead of only the Atmega 328P.

adafruit_support_bill
 
Posts: 74337
Joined: Sat Feb 07, 2009 10:11 am

Please be positive and constructive with your questions and comments.