New Stepper Motor Library

I just released version 2 of TeensyStep on GitHub

New features:
  • Improved rotational mode
  • Possibility to override speed and acceleration of a group of motors on the fly (rotational mode only)
  • Access to the current speed of the controllers
  • Callback when a target is reached
  • Additional examples

I also started a small documentation page which can be found here: https://luni64.github.io/TeensyStep/
 
Hi Luni
I wonder if you could give me some advice on my project?
I have a carriage on a 6m long guide. The carriage is pulled by a 8Nm stepper power by a 48v supply. The stepper has a 1:2 reduction and the looped belt has a ratio of 1:0.6. The carriage will move about 400mm per revolution of the motor.
The objective:
I have to accelerate the carriage in the first 2m to 8m/s then maintain that for 2m and then decelerate for the last 2m. This is done both ways.

I am having problems conceptualizing how to use the library to achieve this. If the acceleration could be specified for a distance or a duration, then it could be done.
Any suggestions will help please.
 
Sorry, not yet. The timers of the T4 behave differently from those of the T3s. So, I need to rethink a few concepts to utilize the speed of the T4 which will take some time.
 
Hello,

sorry for my english. Im german.

i have a problem with this library.
i have a motor with 800steps/revolution
i need 48khz (3600rpm) for output. this function is ok.
But i can max set the setAcceleration(); to 10000
I need 4-5seconds for full speed.
Can i change the teensystep.h or others to make higher the acceleration


can you me help?

regards
Stefan
 
For an acceleration from 0 to 48kHz in 4s you need a setting of 48'000/4 = 12'000 stp/s^2. TeensyStep allows values from 1 to 500'000 so this should be no problem.

But i can max set the setAcceleration(); to 10000
This is very unlikely. Can you post an -ideally very short- sketch showing the effect?
 
this is the code

Code:
#include "TeensyStep.h"
// stepper and  controller
constexpr int stpPin = A8, dirPin = 1;
Stepper motor(stpPin, dirPin);
//StepControl controller;
RotateControl controller;

constexpr int stopPin = 2;
const int ALARM = A5;
const int ENA = A4; 
int ENAstate=0;
long frq;
long acc;
long rpm;

// stopwatches 
elapsedMillis displayStopwatch = 0;  // timing the display of the current position
elapsedMillis blinkStopwatch = 0;    // timing the heartbeat LED
elapsedMillis debounceTimer = 0;     // debouncing input pins

int lastPos = 0;

void handlePins();
void handleCommands();

void setup()
{
    Serial.println("  m: move motor Pin A8");
    Serial.println("  r: RPM motor, r 1000");
    Serial.println("  a: acceleration, a 1000");
    Serial.println("  s: stop motor");
    Serial.println("  e: ENA motor Pin A4");            
    Serial.println("  x: emergency stop");
    Serial.println("  h: display this help");     
    motor.setMaxSpeed(1000);
    motor.setAcceleration(1000);
    pinMode(ALARM, INPUT_PULLUP); 
    pinMode(LED_BUILTIN, OUTPUT);
    pinMode(ENA, OUTPUT); 
    pinMode(stopPin, INPUT_PULLUP);  // touch the pin with GND to stop the motor
}


void loop()
{
    
    // handle incomming commands on the serial interface ------------------
    handleCommands();
     
    // handle input from pins ---------------------------------------------
    handlePins();

    // the usual heartbeat ------------------------------------------------
    if (displayStopwatch > 2000)
    {
     controller.stopAsync();    
        displayStopwatch = 0;
    }
}



void handleCommands()
{
    if (Serial.available() > 0)                 // skip if the serial buffer is empty
    {
      
       char cmd = Serial.read(); 
      displayStopwatch = 0;
      digitalWriteFast(LED_BUILTIN, 1);
      delay(10);
      digitalWriteFast(LED_BUILTIN, 0);
        switch (cmd)                            // ... and analyze it
        {
        case 'm':                               // move command
            if (!controller.isRunning())        // skip move command if motor is running already
            {
                controller.rotateAsync(motor);
                Serial.println("Started motor movement");
            }
            else
            {
                Serial.println("Ignored, motor is already running");
            }
            break;

        case 's':                               // stop command
            controller.stopAsync();             // initiate stopping procedure
            Serial.println("Stopping motor");
            break;
            
       case 'a':                               // stop command
          while (Serial.available()) {
        String read = Serial.readStringUntil('\n');
         if (read.substring(0, 1) == " ") {
            acc = read.substring(1, read.length()).toInt();
            motor.setAcceleration(acc);
            Serial.println(acc);
        
        }
    } //end Serial.available
        break;
        case 'r':                               // stop command
        while (Serial.available()) {
        String read = Serial.readStringUntil('\n');
          if (read.substring(0, 1) == " ") {
            rpm = read.substring(1, read.length()).toInt();
            frq=rpm/0.075;
            motor.setMaxSpeed(frq);
            Serial.println(frq);
          }
    } //end Serial.available
           break;
        case 'x':                               // emergency stop command
            controller.emergencyStop();
            Serial.println("Emergency Stop");
            break;

        case 'e':                               // ENA  command
            controller.emergencyStop();
            ENAstate=!ENAstate;
            digitalWrite(ENA,ENAstate);
            delay(100);  
            Serial.println("ENA");
            break;

        case 'h':                               // help / usage command
        case 'u':
            Serial.println("\nUsage:");
            Serial.println("  m: move motor Pin A8");
            Serial.println("  r: RPM motor, r 1000");
            Serial.println("  a: acceleration, a 1000");
            Serial.println("  s: stop motor");
            Serial.println("  e: ENA motor Pin A4");            
            Serial.println("  x: emergency stop");
            Serial.println("  h: display this help");            
            break;

        default:
            break;
        }
    }
    
}


void handlePins()
{    
    if (controller.isRunning() && !digitalReadFast(ALARM) && debounceTimer > 200)
    {
        debounceTimer = 0;                   
        controller.stopAsync();              // initiate stopping procedure
        Serial.println("Stopping motor");
              ENAstate=!ENAstate;
              digitalWrite(ENA,ENAstate);
              delay(1000);
              ENAstate=!ENAstate;
              digitalWrite(ENA,ENAstate);         
              delay(1000);            
                    controller.rotateAsync(motor);
    
    }
}
 
I tested your code and don't see a problem with acceleration.

I changed the following for my tests:

Code:
void setup()
{
  while(!Serial);                                      //<<<<<<<<<<<<<<<<<<<<<<<< Need to wait until the serial monitor is ready, otherwise the following text might not be displayed. 

  Serial.println("  m: move motor Pin A8");
  Serial.println("  r: RPM motor, r 1000");
  Serial.println("  a: acceleration, a 1000");
  Serial.println("  s: stop motor");
  Serial.println("  e: ENA motor Pin A4");
  Serial.println("  x: emergency stop");
  Serial.println("  h: display this help");
  motor.setMaxSpeed(48000);                     //<<<<<<<<<<<<<<<<<<  As you requested in the originial post
  motor.setAcceleration(100000);                //<<<<<<<<<<<<<<<<<<  Tested it with 10000, 20000 and 100000 all works as expected. 
  pinMode(ALARM, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(ENA, OUTPUT);
  pinMode(stopPin, INPUT_PULLUP); // touch the pin with GND to stop the motor
}

For testing I pressed "m" and recorded the signal on the STP pin. Here some screen shots:

Acceleration 100000stp/s^2, you can clearly see the acceleration
acc_100k_start.png

It should take t=v/a = 48000 / 100000 = 480ms to reach the speed of 48000 step/sec. Here a screenshot of the pulse sequence some 500ms after the start:
acc_100k_t=500ms.png
Showing 48kHz

As mentioned, I don't see anything strange.


Remark: It is quite confusing to use the "A" pin numbers for digital pins. Technically it doesn't matter but it is much clearer to say
Code:
int stpPin = 22

instead of
Code:
int stpPin = A8
 
Hello Luni.

Thanks. I will test.
and i will make a video when the test is not ok.
what do you have for a osszi? :) looks fine.
 
now is ok.
this was my failure.

I had thought wrong.
I thought the higher the value the longer the acceleration. This was not correct.
lower value = longer acceleration
 
getCurrentSpeed() issues

luni, thank you for your fabulous work on this library!

I'm using a Teensy 3.5 and programming it with the Arduino IDE. I'm working on a project using the TeensyStep library which will be documented here. I've run into two library issues to bring to your attention:

Issue 1
getCurrentSpeed() reports non-zero values when the motor is stopped. Before rotation begins, it reports 1. After rotation has been stopped using stop() or stopAsync(), it reports 5. I would expect zero in both cases.

Issue 2
getCurrentSpeed() continues to return the last running speed after an emergency stop (it is not reset to zero). This prevents us from being able to test for issue 1 when using emergencyStop().

The following example code can be used to demonstrate these issues. It also shows my crude initial method of deriving the motor RPM in real time, but I suspect my data types and math functions are not 100% optimal and probably cause rounding issues, hence the warning to prevent someone from blindly trusting this code for this purpose.

Code:
#include "TeensyStep.h"

Stepper motor(2, 3);
RotateControl ctrl(5, 5000);   // params are step pulse width (µs) and speed update period (µs) (defaults are 5,5000)

float motorsteps = 200.0;  // motor steps per rotation
float microstep = 1.0;     // match to DIP switch setting on microstep driver
float rpm = 1500.0;        // desired initial speed setting in RPM

int lastUpdateMillis = 0;
int updateInterval = 250;  // (ms) interval between serial monitor updates

void setup() {
  motor.setMaxSpeed((rpm / 60) * motorsteps * microstep);
  motor.setAcceleration(1000 * microstep);
  
  Serial.begin(9600);
  delay(1000);
  Serial.println("TeensyStep library: display current speed in serial monitor in PPS and RPM");
  Serial.println("(no need for an actual microstep driver or motor to be present)");
  Serial.println("(note that variable types and math functions are likely not ideal in this test code)");
  Serial.println("Send characters to cause events:");
  Serial.println(" 'r' to begin rotation with rotateAsync");
  Serial.println(" 'o' to override speed");
  Serial.println(" 's' for stopAsync (non-blocking)");
  Serial.println(" 'b' for stop (blocking)");
  Serial.println(" 'e' for emergency stop");
}


void loop() {

  if (millis() - updateInterval >= lastUpdateMillis)
  {
    lastUpdateMillis = millis();
    int calculatedrpm = ctrl.getCurrentSpeed() * 60 / motorsteps / microstep;
    Serial.printf("position: %d, pulses/sec: %d, rpm: %d\n", motor.getPosition(), ctrl.getCurrentSpeed(), calculatedrpm);
  }

  
  if (Serial.available()) 
  {
    char inChar = (char)Serial.read();

    if (inChar == 'r')  // rotate (initially to max speed specified)
      ctrl.rotateAsync(motor);

    if (inChar == 'o')  // change to a new speed
      ctrl.overrideSpeed(.5);

    if (inChar == 's')  // stop rotation with the non-blocking code
      ctrl.stopAsync();

    if (inChar == 'b')  // stop rotation with the blocking code
      ctrl.stop();

    if (inChar == 'e')  // emergency stop (instantaneous; no deceleration)
      ctrl.emergencyStop();
  }
}
 
I see; thanks! The underlying cause of not being able to set a timer to delay infinitely makes total sense, and that simple workaround addresses it perfectly.

I noticed is that when the motor has been rotated and stopped, I can resume rotation using overrideSpeed(). If I have never started rotation using rotateAsync(), calling overrideSpeed() has no effect and the motor remains stopped.

I also happened to notice that if I specify setAcceleration() a second time later in the program, it has no effect. This is not something that I expect will be necessary in my application, but I'm curious how tricky it would be to implement an overrideAcceleration() functionality. I imagine declaring the initial value does some fancy math that stores simpler values for repeated later efficient use, and recalculating on the fly would require more fancy math on the fly. I also expect that triggering a new acceleration ratio while the motor is currently accelerating would require being able to ramp smoothly from one acceleration rate to the new one, which would add a whole additional layer of complexity. Is this about right?
 
I noticed is that when the motor has been rotated and stopped, I can resume rotation using overrideSpeed(). If I have never started rotation using rotateAsync(), calling overrideSpeed() has no effect and the motor remains stopped.

Yes, OverrideSpeed(f) just changes the speed of a running motor by multiplying the set speed by the factor f. If you choose f to be zero the motor "runs" with speed 0. This is different from actually stopping the motor.

I also happened to notice that if I specify setAcceleration() a second time later in the program, it has no effect. This is not something that I expect will be necessary in my application,
All the "SetXX" functions only work with a stopped motor since they pre calculate all the parameters required for the move. As said above, overrideSpeed(0) will not stop the motor....

I'm curious how tricky it would be to implement an overrideAcceleration() functionality.
Not tricky anymore since it is already implemented in the development branch. This example https://luni64.github.io/TeensyStep/applications2/010_winder/winder makes heavy use of the feature when fine adjusting the pitch of a winding machine for violin strings.
 
If you choose f to be zero the motor "runs" with speed 0. This is different from actually stopping the motor.

As said above, overrideSpeed(0) will not stop the motor....
Understood. Here's the nuance I'm seeing that can be demonstrated using the code I posted above: If I power up the Teensy so the program starts fresh (and I don't call a rotateAsync() command) and I call overrideSpeed(), the motor does not move. If I stop a running motor using stopAsync() or stop() or emergencyStop(), I can then use overrideSpeed() to cause the motor to begin rotating again (at the specified speed factor) even though it appears "stopped".

So either the stop commands do not fully "stop" the motor in some manner, or rotateAsync() is capable of "starting" a stopped motor, but only if it has been called previously.

I think I will want to set a fairly high max speed initially, but be able to start rotation at a lower speed. I plan to simply call rotateAsync() and then immediately call overrideSpeed() to the lower starting speed. There should be so little delay between the two calls that the output signal will behave as if the new lower speed was the only thing it has been given. (yet to be tested with hardware, but it seems plausible)

Not tricky anymore since it is already implemented in the development branch. This example https://luni64.github.io/TeensyStep/applications2/010_winder/winder makes heavy use of the feature when fine adjusting the pitch of a winding machine for violin strings.
Is he changing the acceleration on the fly, or merely the speed? (and the acceleration has been set once at the beginning)

Either way, it sounds like I should figure out how to get the dev branch loaded onto my machine. ;-)
 
Hey @luni

2 Things

1. I have noticed in your discussions and github pages you show some timing and signals in a GUI interface that looks to be running on a desktop computer. I was wondering what software you were running to debug the stepper motor timing to produce those images.

2. Do you have an estimate for when the Teensy 4 will be supported?

Thanks
 
Hallo,

ich habe jetzt doch noch ein Problem mit dem Teensy (3.2,3.5).
Ich schreibe jetzt auch auf deutsch weil man das besser erklären kann.

Mein Programm funktioniert soweit nur habe ich das problem wenn ich die geschwindigkeit ändere stopt der motor und fährt dann von 0 auf die gewünschte geschwindigkeit. und nicht von 1000 auf 500.
das problem ist noch das die rampe (acc) bleiben muss, also er sollte langsam von z.b. 1000 auf 500 runterfahren und nicht ruckartig von z.b. 1000 auf 500 wenn ich die ACC ausschalte.

benutzte ich da vielleicht falsche befehle, async nehme ich.

grüße
Stefan
 
IMHO, it is not a good idea to write German posts in an English forum. If you feel uncomfortable you can always use https://www.deepl.com which does a great job translating technical texts in both directions

Hello,
I have a problem with the teensy (3.2,3.5) now after all. I write now also in german because it can be explained better.

My program works so far, but I have the problem that when I change the speed the motor stops and then goes from 0 to the desired speed. and not from 1000 to 500. The problem is that the ramp (acc) has to stay, so it should go down slowly from e.g. 1000 to 500 and not jerkily from e.g. 1000 to 500 when I switch off the ACC.

I might use wrong commands, async I use.

Greetings
Stefan

The main use case of TeensyStep is to move up to 10 motors per controller in such a way that the movement of the motors remains synchronized. If you would change the speed of a single motor, the synchronization would be lost. (The next version of the library will generate an error in such cases) Therefore, it is not possible to change the speed (or acceleration, or target position) of individual motors while moving.

If you need to change the speed during operation, you can use the OverrideSpeed function of the rotation controller (https://luni64.github.io/TeensyStep/documentation/rotateControl/). This will change the speed of all motors so that they keep their synchronization. (https://www.youtube.com/watch?v=HcaStXmkH2w)
 
Hello,

this is now a problem. I will have only for one Motor.

i always want to change the speed of one motor when i start the seriel Input, for example with "r 1000" and then go higher with "r 2000", but it must change slowly.

Do you have an idea?
i am grateful for any help.

do you have the code what i see in the video?
 
i have test with overide function.
this is ok for my motor.

can i set a range with override_speed?
i need from 0 - 3600rpm and i will change +100rpm or -100rpm
can only be changed in percent?
 
OverrideSpeed multiplies the override factor by the speed you set at startup. So, you can as well set the speed at startup to 1 and use the override factor to set the required speed.

i.e.

Code:
motor.setMaxSpeed(1);

...

ctrl.overrideSpeed(5000);

will set the actual speed to 5000stp/sec
 
Back
Top