0

Can't drive a stepper at high speed with Motor FeatherWing +
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Can't drive a stepper at high speed with Motor FeatherWing +

by antoine_costes on Thu Sep 16, 2021 2:38 pm

Hello !
I'm driving a stepper (position control) with a Huzzah32 and a DC Motor Featherwing.
It works well for one motor at low speeds but I get very non-linear and (almost not increasing) behavior beyond 250 steps/sec. This limit is even lower (approx 100s/s?) with two steppers running in parallel.
After some research I suspect this coming from the i2c rate (I'll come to that later).

I'm using the AccelStepper library (together with the Adafruit_Motor_Shield_V2_Library 1.0.11) as it is usually mentionned as quite reliable; I might switch to another library if needed but from what I understood so far the limitation does not come from AccelStepper.
I don't even have a stepper plugged, just observing how fast the position is changing according to the library.
Strangely could not see a difference between SINGLE and DOUBLE step types.
I wrote a test sketch to measure the time needed to travel a given distance at various maxspeeds (with a proportionnaly strong acceleration) to evaluate the real speed.
(for some reason the first travel is a bit slower, I take the second one and check that it remains consistent over the next ones)

Here are my results (with one motor):
1000 steps traveled in...
40.07sec for 25.00 steps/sec
20.07sec for 50.00 steps/sec
10.08sec for 100.00 steps/sec
6.74sec for 150.00 steps/sec
5.08sec for 200.00 steps/sec
4.08sec for 250.00 steps/sec
(all good so far)
====
3.93sec for 260.00 steps/sec => real speed = 254
3.79sec for 300.00 steps/sec => real speed = 263
3.73sec for 1000.00 steps/sec => real speed = 268
3.72sec for 2000.00 steps/sec => real speed = 269

Here's my test code
Code: Select all | TOGGLE FULL SIZE
#include <Wire.h>
#include <AccelStepper.h>
#include <Adafruit_MotorShield.h>

Adafruit_MotorShield AFMSbot(0x61); // Rightmost jumper closed
Adafruit_MotorShield AFMStop(0x60); // Default address, no jumpers

Adafruit_StepperMotor *myStepper1 = AFMStop.getStepper(200, 1);
Adafruit_StepperMotor *myStepper2 = AFMStop.getStepper(200, 2);

void forwardstep1() {
  myStepper1->onestep(FORWARD, DOUBLE); // replace with quickstep(FORWARD)
}
void backwardstep1() {
  myStepper1->onestep(BACKWARD, DOUBLE); // replace with quickstep(BACKWARD)
}
void forwardstep2() {
  myStepper2->onestep(FORWARD, DOUBLE);
}
void backwardstep2() {
  myStepper2->onestep(BACKWARD, DOUBLE);
}

AccelStepper stepper1(forwardstep1, backwardstep1);
AccelStepper stepper2(forwardstep2, backwardstep2);

float speed_1 = 250;
float speed_2 = 100;
long lastTime1 = 0;
long lastTime2 = 0;
long dist = 1000;

bool useStepper2 = false;

void setup()
{
  Serial.begin(115200);

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

  //Wire.setClock(400000L); // i2c overclocking

  stepper1.setMaxSpeed(speed_1);
  stepper1.setAcceleration(10*speed_1);
  stepper1.moveTo(dist);

  stepper2.setMaxSpeed(speed_2);
  stepper2.setAcceleration(10*speed_2);
  stepper2.moveTo(dist);

  delay(1000); // for serial console
  Serial.println("-------- START---------");
  Serial.println(String(dist)+" steps traveled in...");
}

void loop()
{
  // Change direction at the limits
  if (stepper1.distanceToGo() == 0)
  {
    float dt1 = 0.001f*(millis() - lastTime1);
    Serial.println(String(dt1)+ "sec for "+String(speed_1)+" steps/sec");
    lastTime1 = millis();
    stepper1.moveTo(stepper1.currentPosition()>0?0:dist);
  }
  stepper1.run();

  if (useStepper2)
  {
    if (stepper2.distanceToGo() == 0)
    {
      float dt2 = 0.001f*(millis() - lastTime2);
      Serial.println(String(dt2)+ "sec for "+String(speed_2)+" steps/sec");
      lastTime2 = millis();
      stepper2.moveTo(stepper2.currentPosition()>0?0:dist);
    }
   
    stepper2.run();
  }
/*

  long pos1 = stepper1.currentPosition();
  long pos2 = stepper2.currentPosition();

  float speed1 = stepper1.speed();
  float speed2  = stepper2.speed();

  Serial.println(String(speed1) + " - " + String(pos1) + " / " + String(pos2) + " - " + String(speed2));
*/
}

======================= So how can I fix that ?
This issue is not documented but described in two forum posts, which provide two hacks to raise up the threshold: i2C overclocking and quickstep method.
viewtopic.php?f=31&t=149633&p=742083&hilit=feather+motor#p738675
viewtopic.php?f=31&t=57041&start=15#p292119

I could reproduce Bill's results, namely reaching about 700-800steps/sec by overclocking the i2c bus from 100kHz to 400kHz with
Code: Select all | TOGGLE FULL SIZE
 Wire.setClock(400000L);

and raise up to about 1100steps/sec with the quickstep hack.
I could not see the difference between quickstep and onestep(DOUBLE) code, but it works so I probably missed it. By the ways I don't see the difference either between onestep(SINGLE) and onestep(DOUBLE), which might explain why I had the same behavior with both. Is the library flawd on that ?

So I guess 5 rounds/seconds is the maximum stepper speed with the featherwing ?
Why is the reliability so bad with 2 motors and how to mitigate it ?
Will the library behave differently with a motor plugged ? I would say no, the only difference being that depending on the load and power supply the real motor might miss steps, but the virtual position should not be affected, am I right ?

I actually had many questions initially but answered most of them by writing this post >< but if anyone has other suggestions, you are very welcome !
I also hope this recap will be useful to someone using the featherwing.

Cheers

antoine_costes
 
Posts: 5
Joined: Mon May 27, 2019 11:19 pm

Re: Can't drive a stepper at high speed with Motor FeatherWi

by antoine_costes on Thu Sep 16, 2021 3:58 pm

I just my test code tried to handle two featherwings while I have only one (with potentially two steppers). Yet I still get odd results with two steppers.
Here is my corrected (and hacked with i2c and quickstep) code:
Code: Select all | TOGGLE FULL SIZE
#include <Wire.h>
#include <AccelStepper.h>
#include <Adafruit_MotorShield.h>

Adafruit_MotorShield AFMS = Adafruit_MotorShield();

Adafruit_StepperMotor *myStepper1 = AFMS.getStepper(200, 1);
Adafruit_StepperMotor *myStepper2 = AFMS.getStepper(200, 2);

void forwardstep1() {
  myStepper1->quickstep(FORWARD);
}
void backwardstep1() {
  myStepper1->quickstep(BACKWARD);
}
void forwardstep2() {
  myStepper2->onestep(FORWARD, DOUBLE);
}
void backwardstep2() {
  myStepper2->onestep(BACKWARD, DOUBLE);
}

AccelStepper stepper1(forwardstep1, backwardstep1);
AccelStepper stepper2(forwardstep2, backwardstep2);

float speed_1 = 1000;
float speed_2 = 500;
long lastTime1 = 0;
long lastTime2 = 0;
long dist = 1000;

bool useStepper2 = true;

void setup()
{
  Serial.begin(115200);
  AFMS.begin();
  Wire.setClock(400000L); //i2c overclocking
 
  myStepper1->onestep(FORWARD, DOUBLE);

  stepper1.setMaxSpeed(speed_1);
  stepper1.setAcceleration(10*speed_1);
  stepper1.moveTo(dist);

  stepper2.setMaxSpeed(speed_2);
  stepper2.setAcceleration(10*speed_2);
  stepper2.moveTo(dist);

  delay(1000); // for serial console
  Serial.println("-------- START---------");
  Serial.println(String(dist)+" steps traveled in...");
}

void loop()
{
  if (stepper1.distanceToGo() == 0)
  {
    float dt1 = 0.001f*(millis() - lastTime1);
    Serial.println(String(dt1)+ "sec for "+String(speed_1)+" steps/sec");
    lastTime1 = millis();
    stepper1.moveTo(stepper1.currentPosition()>0?0:dist);
  }
  stepper1.run();

  if (useStepper2)
  {
    if (stepper2.distanceToGo() == 0)
    {
      float dt2 = 0.001f*(millis() - lastTime2);
      Serial.println(String(dt2)+ "sec for "+String(speed_2)+" steps/sec");
      lastTime2 = millis();
      stepper2.moveTo(stepper2.currentPosition()>0?0:dist);
    }
   
    stepper2.run();
  }
}

antoine_costes
 
Posts: 5
Joined: Mon May 27, 2019 11:19 pm

Re: Can't drive a stepper at high speed with Motor FeatherWi

by adafruit_support_bill on Thu Sep 16, 2021 4:03 pm

I could not see the difference between quickstep and onestep

The difference is that quickstep eliminates the overhead the microstepping PWM.

By the ways I don't see the difference either between onestep(SINGLE) and onestep(DOUBLE)

SINGLE energizes only one winding at a time. DOUBLE energizes both. So DOUBLE will have more torque.

So I guess 5 rounds/seconds is the maximum stepper speed with the featherwing

Sounds about right. With an actual motor connected, it might miss steps at that speed. The best I could achieve with our MEMA17 motor was about 250 RPM.

Why is the reliability so bad with 2 motors and how to mitigate it ?

Reliability should not be affected. The maximum combined step rate is limited, so both motors will have lower top-speeds.

Will the library behave differently with a motor plugged ? I would say no, the only difference being that depending on the load and power supply the real motor might miss steps, but the virtual position should not be affected, am I right ?

Yes. Accelstepper will not know when a step is missed.

The Wing (shield, HAT & Bonnet) are good hobby-level boards for experimenting with multiple motors. But, while i2c doesn't consume a lot of pins, it does become a performance bottleneck. For higher performance, I'd recommend having a look at some of the stepper drivers at www.pololu.com.

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

Re: Can't drive a stepper at high speed with Motor FeatherWi

by antoine_costes on Thu Sep 16, 2021 4:12 pm

Also I wonder if the limitation does not come from AccelStepper in the end.

From its documentation:
The fastest motor speed that can be reliably supported is about 4000 steps per second at a clock frequency of 16 MHz on Arduino such as Uno etc.

but also
setSpeed(): Speeds of more than 1000 steps per second are unreliable.


which makes me think that speed control is even worse than position control.
But the doc also states that:
Gregor Christandl reports that with an Arduino Due and a simple test program, he measured 43163 steps per second using runSpeed(), and 16214 steps per second using run();

What ??? Does anyone know anything about this guy and how he did exactly ? Google didn't help much on it... I could only find his repos https://bitbucket.org/christandlg/?sort=-updated_on

Also I could read this on some forum:
https://forum.arduino.cc/t/how-to-decid ... y/466769/4
Do not forget target.setMinPulseWidth(xx). Minimum pulse width keeps the drive from pulsing to quickly. start around 25 and move up or down from there. if this number is to small it will be weak and slow. if it is to large it will keep it from reaching the top speed.

Anyone having more clues about this ?

antoine_costes
 
Posts: 5
Joined: Mon May 27, 2019 11:19 pm

Re: Can't drive a stepper at high speed with Motor FeatherWi

by antoine_costes on Thu Sep 16, 2021 4:24 pm

Hey Bill, thanks for the swift reply !
And also for your explanations.
DOUBLE doubles the torque, not the speed ok.
Indeed in quickstep i misread the "if (currentstep < MICROSTEPS)" as "if (style == MICROSTEP)"
Indeed with two motorsspeed limit is simply lower, but it's reliable at low speeds.

And yes I will try out with a real motor to check if it does the job, otherwise I might get my good old DRV8825 drivers out of my compartment !

Cheers

antoine_costes
 
Posts: 5
Joined: Mon May 27, 2019 11:19 pm

Re: Can't drive a stepper at high speed with Motor FeatherWi

by antoine_costes on Sun Sep 19, 2021 5:40 pm

So I tried with a nema plugged, and it appears quickstep() has no effect on it for some reason, it won't move. Switching back to onestep() with i2c overcloking I could achieve about 500 steps/sec which is already a good achievement :)

antoine_costes
 
Posts: 5
Joined: Mon May 27, 2019 11:19 pm

Please be positive and constructive with your questions and comments.


cron