TeensyStep - How to switch from 1 speed to another

Status
Not open for further replies.
I don't understand the issue with homing. Can you do a quick sketch showing the problem?

See this diagram: my limit switches are marked red. I have to manually shunt the slide in this area with quite a lot of small movements (marked green) to arrange the winding wires or to thread in filaments etc., so I can't put the limit switch to a location like the one marked blue.
limit.jpg

can you add some typical numbers to the speed profile (time and velocities)?

A typical winding process has a duration of 20 seconds and motor speeds around 30kHz for motor1 and 5kHz for motor2. Maximal speeds are 56kHz for motor1 and 52kHz for motor2, motor1 being the rotation and motor2 being the linear rail for feeding the winding wires.

all controllers keep track of position. You do not need to stick with one controller and can switch to another after a move. Position Will be correct.

I was able to pass pre-calculated target positions to the controller, but the motor wouldn't stop spinning. Using a step controller setTargetRel() works, but then again I can't use the override commands:

Code:
#include "TeensyStep.h"

Stepper motor(22, 41);
RotateControl r_controller(5, 1000);
StepControl s_controller(5, 1000);

void setup() {
  rotate_mode();
  step_mode();
}

void rotate_mode() {
  motor.setTargetRel(300);                        // does not work
  r_controller.rotateAsync(motor); delay(2000);
  r_controller.overrideSpeed(2); delay(2000);
  r_controller.stop();
}

void step_mode() {
  motor.setTargetRel(2000);
  s_controller.moveAsync(motor); delay(500);
  s_controller.overrideSpeed(2); delay(500);      // does not compile
  s_controller.stop();
}

void loop() {}

Here some other observations I would like to share:

Code:
#include "TeensyStep.h"

Stepper motor(22, 41);
RotateControl controller(5, 1000);    // does work
RotateControl controller2();          // does not work

void setup() {
  motor
  .setMaxSpeed(30000)
  .setAcceleration(60000);
  controller.rotateAsync(motor);

  delay(200);
  controller.emergencyStop();         // does not work
}

void loop() {
  int pos = motor.getPosition();      // does compile
  int vel = motor.getCurrentSpeed();  // does not compile
  while (1);
}
 
See this diagram: my limit switches are marked red. I have to manually shunt the slide in this area with quite a lot of small movements (marked green) to arrange the winding wires or to thread in filaments etc., so I can't put the limit switch to a location like the one marked blue.

Sorry, my question was unclear. I was interested in some code showing this:

During acceleration/deceleration the limit switches would not fire: I tried that by polling the pin and also with pin interrupts without success. But with the motors driving slowly and without acceleration it was possible.



I was able to pass pre-calculated target positions to the controller, but the motor wouldn't stop spinning. Using a step controller setTargetRel() works, but then again I can't use the override commands:
For technical reasons I split up the old controller into two: StepControl and RotateControl. StepControl is for moving the motors to a fixed target. RotateControl is for rotational applications without a target. OverrideSpeed only works for the rotational modes. (The old controller faked rotational mode by using a target move to some very large target, the new controller implements this without that workaround)


Code:
RotateControl controller(5, 1000); // works
RotateControl controller2();          // does not work

The first one is correctly placing a RotateControl object on the stack and then calls the constructor of the object with parameters 5 and 1000. If you want to construct an object with a parameter less constructor
you simply say
Code:
RotateControl controller2;
without the parentheses

Your code
Code:
RotateControl controller2();
is correct syntactically, so the compiler does not complain. But, it probably does do not what you want: The compiler interprets this statement as a forward declaration of a function named controller2() which returns a RotateControl object. In fact this is a known syntactic ambiguity in c++ and often confuses users.

Code:
void loop() {
  int pos = motor.getPosition();      // does compile
  int vel = motor.getCurrentSpeed();  // does not compile
  while (1);
}

The Stepper class does not have a getCurrentSpeed method. In fact the Stepper class has no idea what speed means. Speed is determined by the controller by pulsing the motors. The actual motor speed is then a result of the acceleration- and Bresenham algorithms. You probably mixed that up with the controller.getCurrentSpeed() method.
 
Sorry, my question was unclear. I was interested in some code showing this:
During acceleration/deceleration the limit switches would not fire: I tried that by polling the pin and also with pin interrupts without success

OK here an example with bounce2:

Code:
#include "TeensyStep.h"
#include <Bounce2.h>

Stepper motor(22, 41);
RotateControl controller;

Bounce estop = Bounce();

void setup() {
  pinMode(7, INPUT_PULLUP);
  estop.attach(7);
  estop.interval(5);

  motor
  .setMaxSpeed(10000)
  .setAcceleration(1000);
  controller.rotateAsync(motor);
}

void loop() {
  estop.update();
  if (!estop.read()) controller.emergencyStop();
}

And here an example with interrupt:

Code:
#include "TeensyStep.h"

Stepper motor(22, 41);
RotateControl controller;

void setup() {
  pinMode(7, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(7), ISR, CHANGE);

  motor
  .setMaxSpeed(10000)
  .setAcceleration(1000);
  controller.rotateAsync(motor);
}

bool estop_flag;

void ISR() {
  estop_flag = 1;
}

void loop() {
  if (estop_flag) {
    controller.emergencyStop();
    estop_flag = 0;
  }
}

But I think it's not about the button, see this example:

Code:
#include "TeensyStep.h"

Stepper motor(22, 41);
RotateControl controller;

void setup() {
  motor
  .setMaxSpeed(10000)
  .setAcceleration(1000);
  controller.rotateAsync(motor);

  delay(2000);
  controller.emergencyStop();
}

void loop() {}
 
Ratio Adjustment using 2 controllers

Originally I didn't recommend using two controllers for your problem since the acceleration algorithm is integer based. To fix the issue you found (small accelerations rounded down to zero) I needed to change the calculations to float. Good thing is that this not only fixed the small acceleration bug but also the original ratio problem with the not exactly matching accelerations.

-> Here a new attempt to your interesting speed profile problem:

winder3.PNG

Green and red are 'measured' speeds of the motors, yellow is the 'measured' ratio between those speeds. Target speed of the green motor is 36kHz, we start with a ratio of 0.6 (red/green).
  • t= 1s: Motors start accelerating, target green= 36kHz, target red = 36kHz*0.6 * 18kHz, both targets reached at the same time
  • t= 5s: Speed ratio changed to 0.8 -> the red motor accelerates to 24kHz
  • t= 10s: Decelerate the green motor to 10kHz, the red motor follows, keeping the speed ratio to 0.8.
  • t= 15s: Stop both motors, still keeping a ratio of 0.8.

Here the code.

Code:
#include "Arduino.h"
#include "TeensyStep.h"

Stepper greenMotor(22, 41);
Stepper redMotor(43, 42);

RotateControl greenCtrl, redCtrl;

constexpr int maxSpeed = 30000;
constexpr int acceleration = 10000;

elapsedMillis stopwatch;

void setup()
{
  while (!Serial && millis() < 500);

  // setup motors
  greenMotor
      .setMaxSpeed(maxSpeed)
      .setAcceleration(acceleration);

  redMotor // we use the same settings as for the green motor to make the math easier
      .setMaxSpeed(maxSpeed)
      .setAcceleration(acceleration);

  // start the motor controlles but keep motors at v=0
  redCtrl.rotateAsync(redMotor);
  redCtrl.overrideSpeed(0);

  greenCtrl.rotateAsync(greenMotor);
  greenCtrl.overrideSpeed(0);
  
  stopwatch = 0;
}

void loop()
{
  // the movements are initiated with a small state machine for this demo. In the real app you would use your buttons, switches etc
  // to change speed, trim the red motor etc.
  tick();

  if (stopwatch > 50)
  {
    stopwatch = 0;   

    if (redCtrl.isRunning() && greenCtrl.isRunning()) // avoid division by zero in ratio calculation
    {
      int greenSpeed = greenCtrl.getCurrentSpeed();
      int redSpeed = redCtrl.getCurrentSpeed();
      float ratio = (float)redSpeed / greenSpeed;
      Serial.printf("%d\t%i\t%i\t%.3f\n", millis(), greenSpeed, redSpeed, ratio);
    }
  }
}

//-------------------------------------------------------
// Helpers
//
// The updateSpeed functions and variables would be better encapsulated
// in a class but I didn't want to overcomplicate this demo code.

float greenSpeedFac = 1.0f;
float currentSpeedRatio = 1.0f;

// Sets the speed of the green motor to newGreenSpeed and adjusts the speed of the
// red motor to ensure that the speed ratio of both motors stays constant.
void updateSpeed(int newGreenSpeed)
{
  greenSpeedFac = newGreenSpeed / ((float)maxSpeed);

  greenCtrl.overrideSpeed(greenSpeedFac);
  redCtrl.overrideSpeed(greenSpeedFac * currentSpeedRatio);
}

// Sets the speed ratio between the red and green motors
// The function accelerates/decellerates the red motor to match that ratio. 
void updateSpeedRatio(float speedRatio)
{
  if (speedRatio > 0)
  {
    currentSpeedRatio = speedRatio;
    redCtrl.overrideSpeed(greenSpeedFac * currentSpeedRatio);
    redCtrl.overrideAcceleration(currentSpeedRatio); // adjust acceleration of red motor
  }
}


//-------------------------------------------------------
// Small state machine to simulate your user controls
//

enum states {start, fullspeed, trimedRightMotor, lowspeed, ended} state = states::start;

void tick()
{
  switch (state)
  {
  case start:                 // start sequence
    if (millis() > 1000)
    {
      updateSpeed(maxSpeed);  // green Motor -> 36kHz
      updateSpeedRatio(0.6);  // ratio red / green = 0.6 (18kHz)

      state = fullspeed;
    }
    break;

  case fullspeed:
    if (millis() > 5000)      // start red acceleration 5s after start
    {
      updateSpeedRatio(0.8);  // change the speed ratio to 0.8 -> red motor will accelerate to 0.8 * greenSpeed  (24kHz)
      state = trimedRightMotor;
    }
    break;

  case trimedRightMotor:
    if (millis() > 10000)     // declerate both motors to lowspeed after 10s
    {
      updateSpeed(10000);     // decelerate green motor to 10 kHz, red motor will follow with constant speed ratio
      state = lowspeed;
    }
    break;

  case lowspeed:              // stopping both motors after 15s
    if (millis() > 15000)
    {
      updateSpeed(0);
      state = ended;
    }
    break;

  case ended:
    break;
  }
}
 
... and here an example showing how to move your winder a little bit to the right without changing the overall pitch. I.e., the red motor increases speed only for a small period and then goes back to the original ratio of 0.6.

winder4.PNG

and the corresponding changes to the state machine:
Code:
// Small state machine to simulate your user controls

enum states {start, fullspeed, trimedRightMotor1, trimedRightMotor2, lowspeed, ended} state = states::start;

void tick()
{
  switch (state)
  {
  case start:                 // start sequence
    if (millis() > 1000)
    {
      updateSpeed(maxSpeed);  // green Motor -> 36kHz
      updateSpeedRatio(0.6);  // ratio red / green = 0.6

      state = fullspeed;
    }
    break;

  case fullspeed:
    if (millis() > 5000)          // start red acceleration 5s after start
    {
      updateSpeedRatio(0.65);  // change the speed ratio to 0.65 -> red motor will accelerate to 0.65 * greenSpeed
      state = trimedRightMotor1;
    }
    break;

  case trimedRightMotor1:
    if (millis() > 7000)     // change speed ratio back to 0.60 to keep the old pitch
    {
      updateSpeedRatio(0.6);     
      state = trimedRightMotor2;
    }
    break;


  case trimedRightMotor2:
    if (millis() > 10000)     // declerate both motors to lowspeed after 10s
    {
      updateSpeed(10000);     // decelerate green motor to 10kHz, red motor will follow with constant speed ratio
      state = lowspeed;
    }
    break;

  case lowspeed:              // stopping both motors after 15s
    if (millis() > 15000)
    {
      updateSpeed(0);
      state = ended;
    }
    break;

  case ended:
    break;
  }
}
 
....

But I think it's not about the button, see this example:

Code:
#include "TeensyStep.h"

Stepper motor(22, 41);
RotateControl controller;

void setup() {
  motor
  .setMaxSpeed(10000)
  .setAcceleration(1000);
  controller.rotateAsync(motor);

  delay(2000);
  controller.emergencyStop();
}

void loop() {}

Fixed a bug in the eStop routine on the development branch. Can you give it a try?
 
Fixed a bug in the eStop routine on the development branch. Can you give it a try?

I tested the new version: estop during acceleration is fixed! But the following code does not work any more (in the older version this was not a problem):

Code:
#include "TeensyStep.h"

Stepper motor(48, 47);
RotateControl RotCtrl(5, 1000);
StepControl StpCtrl(5, 1000);

void setup() {
  motor
  .setMaxSpeed(2000)
  .setAcceleration(2000);
  RotCtrl.rotateAsync(motor);

  delay(2000);

  RotCtrl.emergencyStop();
  motor.setTargetRel(-3000);    // after estop immediately move away from the switch...
  StpCtrl.move(motor);

  delay(2000);

  motor
  .setMaxSpeed(2000)
  .setAcceleration(2000);
  RotCtrl.rotateAsync(motor);

  delay(2000);

  RotCtrl.stop();
}

void loop() {}
 
Last edited:
I tested the new version: acceleration is fixed, but deceleration still does not seem to work:

Tested your code. Here is the output:

winder5.jpg

Looks like it does exactly what it is supposed to?

I can't create more than 4 controllers any more (I recall the limitation of up to 4 controllers was removed in earlier versions...?):
No, this was never possible, it is limited by the 4 PIT timers of the Teensies. However, you do not need to define the controllers in global space. You can as well generate them locally in a function which gives you more flexibility. E.g. you can write a function using 3 rotation controllers and 1 step controller. In another function you then use 4 step controllers...


I'll have a look at your second code block later...
 
Looks like you deleted stuff in #32? Anyway, I had a look at the issue with directly starting a move after an estop (the remaining code block in #32) This seems to be some timing issue. For a workaround you can place a delay(2) after the call to emergencyStop() I need to spend some more time to dig into it and see what is going wrong here. Time to clean up the code a little bit...

At least I know now that it was a good idea to leave the new code in the development branch until somebody really tested it :)

Did you try the speed ratio adjustment examples yet?
 
Looks like it does exactly what it is supposed to?

Sorry, I forgot to use an async stop command, wanted to stop them together...

Did you try the speed ratio adjustment examples yet?

Yes, I tried to implement them in my existing code and stumbled over the problem during acceleration. I will test again and report asap...
 
OK, I hope I got it right... Since I use 2 teensies (one to control the motors, the other one for the rest, and the values are transferred over serial), here my 2 pseudo-code blocks, first for the sending teensy:

Code:
sending_teensy() {
  ratio = max1 / max2; // calc ratio of initial speeds
  max1_old = max1;     // save initial value
  max2_old = max2;     // save initial value
  send_over_serial(max1, max2, acc1, acc2);

  // calc new speeds:
  max1 = new speed for leading motor
  max2 = something like desiredRPM / 60 * pitchRatio / leadscrewPitch / gearPulley1 * gearPulley2 * stepsPerRev * microstepping
  factor_motor1 = max1 / max1_old;
  factor_motor2 = max2 / max1_old * ratio;
  acc_motor2 = max2 / max1 * ratio;
  send_over_serial(factor_motor1, factor_motor2, acc_motor2);
}

...and that's what the receiving teensy does:

Code:
void receiving_teensy() {
  process_serial();

  if (startMotors && !Ctrl1.isRunning() && !Ctrl2.isRunning()) {
    motor1
    .setMaxSpeed(max1)
    .setAcceleration(acc1);
    motor2
    .setMaxSpeed(max2)
    .setAcceleration(acc2);

    Ctrl1.rotateAsync(motor1);
    Ctrl2.rotateAsync(motor2);
  }

  else if (overrideSpeed && Ctrl1.isRunning() && Ctrl2.isRunning()) {
    Ctrl1.overrideSpeed(factor_motor1);
    Ctrl2.overrideSpeed(factor_motor1);
  }

  else if (overrideRatio && Ctrl1.isRunning() && Ctrl2.isRunning()) {
    Ctrl2.overrideSpeed(factor_motor2);
    Ctrl2.overrideAcceleration(acc_motor2);
  }
}

There is still a problem with very low numbers, please check this example:

Code:
#include "TeensyStep.h"

Stepper motor1(22, 41);
Stepper motor2(43, 42);

RotateControl ctrl1(5, 1000);
RotateControl ctrl2(5, 1000);

constexpr int maxS = 6;
constexpr int accS = 28;

void setup() {
  motor1
  .setMaxSpeed(maxS)
  .setAcceleration(accS);

  motor2
  .setMaxSpeed(maxS)
  .setAcceleration(accS);

  ctrl1.rotateAsync(motor1);
  ctrl1.overrideSpeed(0);
  ctrl2.rotateAsync(motor2);
  ctrl2.overrideSpeed(0);

  updateSpeed(maxS);
  updateRatio(50);
  delay(2000);
  updateRatio(1);
  delay(50);
  updateSpeed(0);
}

float SpeedFac = 1.0f;
float currentRatio = 1.0f;

void updateSpeed(int newSpeed) {
  SpeedFac = newSpeed / ((float)maxS);
  ctrl1.overrideSpeed(SpeedFac);
  ctrl2.overrideSpeed(SpeedFac * currentRatio);
}

void updateRatio(float newRatio) {
  if (newRatio > 0) {
    currentRatio = newRatio;
    ctrl2.overrideSpeed(SpeedFac * currentRatio);
    ctrl2.overrideAcceleration(currentRatio);
  }
}

void loop() {}
 
Last edited:
There is still a problem with very low numbers, please check this example:

I don't quite understand what you want to achieve but I'd say it does what you requested. I added printout of motor speeds to your code. The result is shown below.

winder5.PNG

You request motor1 (blue curve) to run at 6Hz and set a ratio of 50 -> motor2 should run at 300Hz which they do. After 2s you request a ratio of 1, i.e. motor2 should decelerate from 300Hz to 6Hz, which it tries to do. But only 50ms later (before it has had a chance to reach its new target speed) you request that both motors should stop which they do. Since you requested a ratio of 1, motor2 (orange) needs to use the same acceleration as motor1. But, since motor2 didn't reach the target speed (6Hz) when you requested the stop, it will take quite long to reach zero.

If you give motor2 enough time (e.g. 500ms) to reach its target before you stop the motors, everything works nicely and the speed ratio / winding pitch is exactly what you requested over the complete movement.

Can you explain what you wanted to achieve with your code?

winder6.PNG
Code:
#include "TeensyStep.h"

Stepper motor1(0, 2);
Stepper motor2(1, 3);

RotateControl ctrl1(5, 1000);
RotateControl ctrl2(5, 1000);

constexpr int maxS = 6;
constexpr int accS = 28;

void setup()
{
  motor1
      .setMaxSpeed(maxS)
      .setAcceleration(accS);

  motor2
      .setMaxSpeed(maxS)
      .setAcceleration(accS);

  ctrl1.rotateAsync(motor1);
  ctrl1.overrideSpeed(0);
  ctrl2.rotateAsync(motor2);
  ctrl2.overrideSpeed(0);

  updateSpeed(maxS);
  updateRatio(50);
  
  delay_print(2000);

  updateRatio(1);
  delay_print(500); // <-------
  updateSpeed(0);
}

float SpeedFac = 1.0f;
float currentRatio = 1.0f;

void updateSpeed(int newSpeed)
{
  SpeedFac = newSpeed / ((float)maxS);
  ctrl1.overrideSpeed(SpeedFac);
  ctrl2.overrideSpeed(SpeedFac * currentRatio);
}

void updateRatio(float newRatio)
{
  if (newRatio > 0)
  {
    currentRatio = newRatio;
    ctrl2.overrideSpeed(SpeedFac * currentRatio);
    ctrl2.overrideAcceleration(currentRatio);
  }
}


void delay_print(unsigned d)
{
  elapsedMillis stopwatch = 0;
  while (stopwatch < d)
  {
    int p1 = ctrl1.getCurrentSpeed();
    int p2 = ctrl2.getCurrentSpeed();

    Serial.printf("%d\t%i\t%i\n", millis(), p1, p2);
    delay(20);
  }
}

void loop()
{
  while (ctrl1.isRunning() || ctrl2.isRunning())
  {
    Serial.printf("%d\t%i\t%i\n", millis(), ctrl1.getCurrentSpeed(), ctrl2.getCurrentSpeed());
    delay(20);
  }
}
 
Last edited:
OK, I hope I got it right... Since I use 2 teensies (one to control the motors, the other one for the rest, and the values are transferred over serial), here my 2 pseudo-code blocks, first for the sending teensy:

Your code is a bit difficult to understand. When I did the example for you a few posts above I also started similar to your approach (calculating with the speed of both motors). After looking at the mess I decided that it might be better to write everything in terms of speed of the "spindle" and pitch (ratio) of your winding. The pitch/ratio would normally stay constant (apart from trimming) during the movement. The only really variable thing is the speed of the spindle. This led me to the design of the updateSpeed and updateRatio functions.

So, if I would code this, I would pass spindle speed and winding pitch from teensy1 to teensy2. On teensy2 I'd do the low level stuff like translating spindle speed and winding ratio to actual speed and acceleration settings using the functions updateSpeed / updateRatio or something similar. But of course this is my personal opinion only. A lot of roads lead to Rome...
 
I would pass spindle speed and winding pitch from teensy1 to teensy2. On teensy2 I'd do the low level stuff like translating spindle speed and winding ratio to actual speed and acceleration settings using the functions updateSpeed / updateRatio or something similar.

I see! I was trying to follow earlier advice which I understood as to do all calculations in the cockpit and let the lower teensy execute only commands coming in over serial. So at the moment all calculations are done on teensy1, using the results also for the display, or to prevent settings out of range etc...

I don't quite understand what you want to achieve

My code does not represent what I wanted to achieve, rather I tried to demonstrate what happens on my machine during implementing / testing. Sorry, it's often not easy for me to find out and show clearly... So I made 2 videos showing the situation on my winding machine: the first video shows how the motors move when I totally rely on using speed override (an idea I took from your code example: starting the controllers but with a speed factor of zero). The second video shows the cockpit (I could not record both things simultaneously holding the camera and operating the machine). Rotary encoders are used to set/change speeds. If I change speeds from a slower to a faster number it always works (regardless how brutally fast I turn the knobs), but if I change from faster to slower sometimes it did an extremely long deceleration ramp, crashing into my limit switches - which now protect well. The videos do not exactly show the mentioned trimming process, rather they demonstrate that even a fast update rate is possible. So I think in real world usage I will never change as rapid as in the videos, but I will change in the very slow speed range in small steps of 1/100mm winding pitch, and if I update 2 or 3 clicks too fast (my enconders have 64 clicks per rev) I end up with the feeder travelling for ever. That is what I wanted to demonstrate with my code example. Because this can lead to some serious damage: the feeder has movable arms for the wires which would crash against the string rotating motor unit where I don't have a limit switch. (These movable arms are only out during winding, so when I normally move the feeder it will move freely between the ends of the rail.)

since motor2 didn't reach the target speed (6Hz) when you requested the stop, it will take quite long to reach zero.

This behaviour was unexpected because if I use rotateAsync I normally can stop any time even during acceleration, or I can interrupt a stopAsync and start immediately again in the opposite direction...
 
Last edited:
Your winder looks great, you definitely made huge progress compared to your first wooden model. Looks very professional now.


Reading your explanation I think you have contradictory requirements, let me try to explain: The requirements we were talking about so far were:

  1. The spindle speed shall be adjustable on the fly
  2. The winder pitch shall stay constant during speed changes of the spindle
  3. The winder pitch shall be adjustable on the fly. During a pitch adjustment #2 does not need to be fulfilled (obviously)
This is what the current code does and which leads to the observed observation: In your last step you set the ratio to 1, therefore the code needs to set the acceleration of the feeder equal to that of the spindle to fulfill #2. Now you stop the spindle so both motors will decelerate to 0 with the same acceleration to keep#2. Since the feeder speed was not at its target yet it will take longer than the spindle to reach 0.

I think (please correct me if I'm wrong) you want a 4th requirement

4) On a speed change of the spindle the feeder should reach its target speed (given by the pitch & spindle speed) always at the same time as the spindle.

Please be aware that #4 is in contradiction with #2 in the general case: As log as the speed ratio of spindle and feeder was at the target when you changed the spindle speed, #4 does the same as #2. If the speed ratio was wrong (e.g. you changed the spindle speed while the feeder is still accelerating) #4 takes care that you don't have an overshoot. Please be aware that in that case #4 necessarily generates a wrong pitch during the speed change of the spindle (you simply can not accelerate two motors from arbitrary start speeds to arbitrary end speeds with a given acceleration ratio at the same time)

Let me know if I understood this correctly, I then can try to adjust the updateSpeed / updateRatio functions accordingly.
 
Last edited:
Let me know if I understood this correctly, I then can try to adjust the updateSpeed / updateRatio functions accordingly.

I think you understood much better than me :cool:

(BTW in the library src the overrides are listed under "Blocking movements"...?)

Requirement #4 sounds good, but I am not sure because in any case I want be able to presume that the pitch is as intended. This would mean I just have to make sure not to call the next override before the previous speed ramp is completed. Do you think in that case polling something like a ctrl.isOverriden() would make sense?
 
Last edited:
Sorry to bother again with a code example, but I read so many times your explanations and still don't understand everything, so I put together another code example. What puzzles me is the difference of the ramp durations between the first and the second run:

Code:
#include "TeensyStep.h"

Stepper motor(39, 45); RotateControl ctrl(5, 1000);
float maxS = 57.0f, acc = 285.0f;

void setup() {
  firstrun();
  delay(2000);
  secondrun();
}

void firstrun() {
  motor
  .setMaxSpeed(maxS)
  .setAcceleration(acc);

  ctrl.rotateAsync(motor); delay(2000);
  overrideRatio(6.0f);     delay(2000);
  overrideRatio(1.0f);     delay(2000);

  ctrl.stopAsync();
}

void secondrun() {
  maxS *= 6;
  acc  *= 6;

  motor
  .setMaxSpeed(maxS)
  .setAcceleration(acc);

  ctrl.rotateAsync(motor);   delay(2000);
  overrideRatio(0.1666666f); delay(2000);
  overrideRatio(1.0f);       delay(2000);

  ctrl.stopAsync();
}

void overrideRatio(float factor) {
  ctrl.overrideSpeed(factor);
  ctrl.overrideAcceleration(factor);
}

void loop() {}
 
Last edited:
I tried to reproduce your problem with changing the pitch quickly on the fly. I first cleaned up everything and wrote a winder class which encapsulates all the code required to move your steppers.
Here the public interface:

Code:
class Winder
{
public:
    Winder(Stepper &spindle, Stepper &feeder);

    void begin();

    Winder &setSpindleParams(unsigned stpPerRev, unsigned acceleration); // steps per spindle revolution & maximum spindle speed in rpm
    Winder &setFeederParams(unsigned stpPerMM);                                   // steps to move the feeder 1mm

    void setSpindleSpeed(float rpm);  // changes the spindle speed to the given rpm
    void setPitch(float pitch_in_mm); // changes the winder pitch to the given value
    void updateSpeeds();

    inline int getCurSpindleSpeed() { return spindleCtrl.isRunning() ? spindleCtrl.getCurrentSpeed() : 0; }
    inline int getCurFeederSpeed() { return feederCtrl.isRunning() ? feederCtrl.getCurrentSpeed() : 0; }
    inline float getCurPitch(){return (float)getCurFeederSpeed()/getCurSpindleSpeed()/pitchFactor; }

The constructor takes references to the spindle stepper and the feeder stepper. To setup the winder you call
  • setSpindleParams which takes the number of steps the spindle needs for one revolution and the spindle acceleration in the usual units
  • setFeederParams which takes the number of feeder steps required to move the speeder 1mm
  • after that you call begin()

After setup you can call
  • setSpindleSpeed which takes the spindle speed in rpm and
  • setPitch which takes the winder pitch in mm. I.e, a pitch of 0.5 would move the feeder by 0.5mm per spindle rotation.

The set functions only preset the new values. The new speeds are changed by calling updateSpeeds(). There are also functions for reporting current speeds and current pitch.

Here an example showing how to use the winder class. It uses an interval timer to print out current values in the background so that you can do experiments without connecting the steppers, just watch the values in tyCommander.
It uses an encoder to trim the pitch.

Code:
#include "Arduino.h"
#include "Encoder.h"
#include "TeensyStep.h"
#include "Winder.h"

IntervalTimer printTimer; // to print out current speeds in the background
void printCurrent();

constexpr unsigned feederStpPerMM = 200 * 8 * 5.0f * 0.8f; // e.g. fullstep/rev * microstepping * leadscrew pitch * gearRatio
constexpr unsigned spindleStpPerRev = 200 * 16;            // e.g. fullstep/rev * microstepping
constexpr unsigned spindleAcceleration = 15000;

Stepper spindle(22, 41);
Stepper feeder(39, 45);
Winder winder(spindle, feeder);

float pitch = 0.2; //mm

Encoder trimmer(5, 6);
int oldEncVal = 0;

void setup()
{
  while (!Serial && millis() < 500);

  // setup background printing
  printTimer.begin(printCurrent, 25000);
  printTimer.priority(255); // lowest priority, we don't want to disturb stepping

  // setup the winder
  winder
      .setSpindleParams(spindleStpPerRev, spindleAcceleration)
      .setFeederParams(feederStpPerMM)
      .begin();

  // startup the winder
  winder.setSpindleSpeed(600); // spindle speed in rpm
  winder.setPitch(0.2f);       // pitch in mm
  winder.updateSpeeds();       // apply new settings
  delay(3000);
}

void loop()
{
  // read in encoder and trim the pitch (+/- 0.01 per step)

  int newEncVal = trimmer.read(); 
  if (newEncVal != oldEncVal)
  {
    oldEncVal = newEncVal;
    pitch = 0.2f + newEncVal / 400.0f;
    winder.setPitch(pitch);
    winder.updateSpeeds();
  }

  if (millis() > 15000)  // stop the spindle after 15s
  {
    winder.setSpindleSpeed(0);
    winder.updateSpeeds();
    while (1)
      yield(); // stop
  }

  delay(50);  // dont overrun the winder by sending new values to often. 
}

// helpers ----------------------------------------------

void printCurrent()
{
  unsigned t = millis();
  unsigned feederSpeed = winder.getCurFeederSpeed();
  unsigned spindleSpeed = winder.getCurSpindleSpeed();
  float curPitch = winder.getCurPitch();

  if (spindleSpeed != 0 || feederSpeed != 0)
  {
    Serial.printf("%d\t%i\t%i\t%.3f\t%.3f\n", t, spindleSpeed, feederSpeed, curPitch, pitch);
    // Serial.printf("{XYPLOT|DATA|Feeder|%d|%i}\n",t,feederSpeed);  // output for meguno (live data viewer)
    // Serial.printf("{XYPLOT|DATA|Pitch|%d|%.3f}\n", t, pitch);
  }
}

Below a speed profile showing the spindle and feeder speed (Hz) and the target and current pitch (mm) while playing with the pitch trimmer. I could not generate any unusual condition, regardless how often/large I changed the pitch, so that seems to work quite stable. I didn't implement requirement #4 since you were not yet sure if you would like it. So, stopping the spindle while the feeder is not at its target speed will still generate the behavior we discussed above.

winder7.PNG

Here a quick zip of the code, I'll upload it as an example to gitHub later.

EDIT: You need to upload the latest TeensyStep from GitHub to compile this (there was and old test function which needed to be removed)
 

Attachments

  • winderEncoder.zip
    6.4 KB · Views: 81
Last edited:
Wow, thank you, very convenient! I will play with this class and study it. One thing I noticed is that you call feeder.overrideAcceleration() now before feeder.overrideSpeed(): I am wondering if it would suffice to update feeder acceleration only when changing spindle speed?

(BTW again the Windows bug: it compiled on my linux only after changing the first character of "winder.h" in winder.cpp to a capital :cool:)
 
Wow, thank you, very convenient! I will play with this class and study it. One thing I noticed is that you call feeder.overrideAcceleration() now before feeder.overrideSpeed(): I am wondering if it would suffice to update feeder acceleration only when changing spindle speed?

Here the relevant code:

Code:
void Winder::setSpindleSpeed(float rpm)
{
    targetSpindleSpeed = rpm / 60.0f * spindleStpPerRev;
}

void Winder::setPitch(float pitch)
{
    targetPitch = pitch;
}

void Winder::updateSpeeds()
{
    float accFactor = pitchFactor * targetPitch;
    float targetFeederSpeed = accFactor * targetSpindleSpeed;

    feederCtrl.overrideAcceleration(accFactor);
    feederCtrl.overrideSpeed(targetFeederSpeed);
    spindleCtrl.overrideSpeed(targetSpindleSpeed);

    //Serial.printf("spSpeed:%f fdrSpeed:%f fdrAcc:%f\n", targetSpindleSpeed, targetFeederSpeed, accFactor);
}

The accFactor will only change if you change the pitch. So the feeder acceleration only needs to be changed when you change the pitch. You can certainly check if the target pitch changed before calling it but I wonder if it is worth it. Changing the pitch is a manual process (encoder etc) and it only costs a few microseconds...

(BTW again the Windows bug: it compiled on my linux only after changing the first character of "winder.h" in winder.cpp to a capital )
OMG, I will never get used to case sensitive file names... I'll fix that on GitHub later.

BTW: I just published a TeensyStep docu page: https://luni64.github.io/TeensyStep/index.html. (It already has a Winder stub in the applications section, waiting for nice videos :) )
 
The accFactor will only change if you change the pitch. So the feeder acceleration only needs to be changed when you change the pitch.

At least it needs to be changed for the next spindle speed change, so we could theoretically update it only before the spindle speed factor changes and keep all pitch changes with the old acceleration factor...?

I just published a TeensyStep docu page: https://luni64.github.io/TeensyStep/index.html. (It already has a Winder stub in the applications section, waiting for nice videos :) )

Nice!!!! I certainly will provide videos as soon as I can do some serious test windings. Many people are searching the net for "coil winder", so maybe it would bring more visitors if you call it like that and explain it by the example of the string winder...? Also the second rotary encoder for the winding speed would be nice :)
 
At least it needs to be changed for the next spindle speed change, so we could theoretically update it only before the spindle speed factor changes and keep all pitch changes with the old acceleration factor...?

Yes, you are absolutely right. This eliminates the ugly acceleration asymmetry in the feeder speed profile. I implemented a first try and pushed it to GitHub (https://github.com/luni64/TeensyStep/tree/develop/examples/Applications/Winder)

I certainly will provide videos as soon as I can do some serious test windings.
Great, videos, texts, descriptions fotos etc. whatever you want to share.
 
At fast winding speeds with thin wires (for example 0.12mm wires @ 3000~7000rpm) I get very slow acceleration rates for pitch changes. So what about this:

Code:
if (targetPitch > oldPitch) {
  feederCtrl.overrideAcceleration(accFactor);
  feederCtrl.overrideSpeed(targetFeederSpeed);
}

else if (targetPitch < oldPitch) {
  feederCtrl.overrideSpeed(targetFeederSpeed);
  feederCtrl.overrideAcceleration(accFactor);
}
 
The sequence of overriding acceleration and speed should be irrelevant (at least this was the intention) I tried your change but I didn't observe any change.

Anyway, I see what you want. The current code uses the same feeder acceleration which is needed to keep the pitch constant during a spindle speed change. But, since the pitch obviously can not stay constant when you require a pitch change we can as well use any acceleration for pure pitch changes.

I changed the code on GitHub respectively. setFeederParams now takes an acceleration parameter which is used for pitch trimming only.

Code:
Winder &setSpindleParams(unsigned stpPerRev, unsigned acceleration);  // steps per spindle revolution & acceleration in stp/s^2
Winder &setFeederParams(unsigned stpPerMM, unsigned acceleration);    // steps to move the feeder 1mm, acceleration for pitch trimming

And here the new updateSpeeds()

Code:
void Winder::updateSpeeds()
{   
    float targetFeederSpeed =  targetPitch * pitchFactor * targetSpindleSpeed;

    if(targetSpindleSpeed != oldSpindleSpeed)  // if target speed changed -> update all
    {
        oldSpindleSpeed = targetSpindleSpeed;

        float feederAccFactor = targetPitch * pitchFactor;
        feederCtrl.overrideAcceleration(feederAccFactor);
        feederCtrl.overrideSpeed(targetFeederSpeed);
        spindleCtrl.overrideSpeed(targetSpindleSpeed);
    }
    else if(targetPitch != oldPitch)           // if only target pitch changed, update feeder speed and use pitch acceleration
    {        
        oldPitch = targetPitch;
        feederCtrl.overrideAcceleration(feederAcc / spindleAcc);
        feederCtrl.overrideSpeed(targetFeederSpeed);
    }
}


Here the usage:

Code:
#include "Arduino.h"
#include "Encoder.h"
#include "TeensyStep.h"
#include "Winder.h"

IntervalTimer printTimer; // to print out current speeds in the background
void printCurrent();

constexpr unsigned feederStpPerMM = 200 * 8 * 5.0f * 0.8f; // e.g. fullstep/rev * microstepping * leadscrew pitch * gearRatio
constexpr unsigned spindleStpPerRev = 200 * 16;            // e.g. fullstep/rev * microstepping

Stepper spindle(22, 41);
Stepper feeder(39, 45); 
Winder winder(spindle, feeder); 
 
float pitch = 0.2; //mm

Encoder trimmer(5, 6);
int oldEncVal = 0;

void setup()
{
  while (!Serial && millis() < 500);

  // setup background printing
  printTimer.begin(printCurrent, 25000);
  printTimer.priority(255); // lowest priority, we don't want to disturb stepping

  // setup the winder
  winder
      .setSpindleParams(spindleStpPerRev, 30000)  
      .setFeederParams(feederStpPerMM, 100000 )  
      .begin();

  // startup the winder
  winder.setSpindleSpeed(800); // spindle speed in rpm
  winder.setPitch(0.12f);      // pitch in mm
  winder.updateSpeeds();       // apply new settings
  
  delay(3000);
  winder.setPitch(0.18);
  winder.updateSpeeds();
  
  delay(1000);
  winder.setPitch(0.06);
  winder.updateSpeeds();

  delay(1000);
  winder.setPitch(0.12);
  winder.updateSpeeds();
  
  delay(2000);  
  winder.setSpindleSpeed(300);
  winder.updateSpeeds();

  delay(2500);  
  winder.setSpindleSpeed(0);
  winder.updateSpeeds();
}

void loop()
{  
}

// helpers ----------------------------------------------

void printCurrent()
{
  unsigned t = millis();
  unsigned feederSpeed = winder.getCurFeederSpeed();
  unsigned spindleSpeed = winder.getCurSpindleSpeed();
  float curPitch = winder.getCurPitch();

  if (spindleSpeed != 0 || feederSpeed != 0)
  {
    Serial.printf("%d\t%i\t%i\t%.3f\t%.3f\n", t, spindleSpeed, feederSpeed, curPitch, pitch);    
  }
}

And the result with increased acceleration during pitch trimming
winder7.PNG
 
Last edited:
The sequence of overriding acceleration and speed should be irrelevant (at least this was the intention) I tried your change but I didn't observe any change.

I saw a difference with extremely slow speeds / acceleration rates, and you also mentioned the "ugly asymmetry". I thought that was gone with my suggestion...?

Anyway, I see what you want. The current code uses the same feeder acceleration which is needed to keep the pitch constant during a spindle speed change. But, since the pitch obviously can not stay constant when you require a pitch change we can as well use any acceleration for pure pitch changes.

I changed the code on GitHub respectively. setFeederParams now takes an acceleration parameter which is used for pitch trimming only.

Thanks a lot, this is a very good solution!!! Happy Easter :)
 
Status
Not open for further replies.
Back
Top