Forum Rule: Always post complete source code & details to reproduce any issue!
Page 2 of 4 FirstFirst 1 2 3 4 LastLast
Results 26 to 50 of 85

Thread: TeensyStep - How to switch from 1 speed to another

  1. #26
    Quote Originally Posted by luni View Post
    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.
    Click image for larger version. 

Name:	limit.jpg 
Views:	9 
Size:	16.6 KB 
ID:	16414

    Quote Originally Posted by luni View Post
    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.

    Quote Originally Posted by luni View Post
    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);
    }

  2. #27
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    423
    Quote Originally Posted by jpk View Post
    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.


    Quote Originally Posted by jpk View Post
    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)


    Quote Originally Posted by jpk View Post
    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.

    Quote Originally Posted by jpk View Post
    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.

  3. #28
    Quote Originally Posted by luni View Post
    Sorry, my question was unclear. I was interested in some code showing this:
    Quote Originally Posted by jpk View Post
    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() {}

  4. #29
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    423

    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:

    Click image for larger version. 

Name:	winder3.PNG 
Views:	12 
Size:	36.8 KB 
ID:	16423

    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;
      }
    }

  5. #30
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    423
    ... 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.

    Click image for larger version. 

Name:	winder4.PNG 
Views:	9 
Size:	36.5 KB 
ID:	16424

    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;
      }
    }

  6. #31
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    423
    Quote Originally Posted by jpk View Post
    ....

    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?

  7. #32
    Quote Originally Posted by luni View Post
    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 by jpk; 04-15-2019 at 04:24 PM.

  8. #33
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    423
    I tested the new version: acceleration is fixed, but deceleration still does not seem to work:
    Tested your code. Here is the output:

    Click image for larger version. 

Name:	winder5.jpg 
Views:	9 
Size:	27.2 KB 
ID:	16425

    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...

  9. #34
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    423
    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?

  10. #35
    Quote Originally Posted by luni View Post
    Looks like it does exactly what it is supposed to?
    Sorry, I forgot to use an async stop command, wanted to stop them together...

    Quote Originally Posted by luni View Post
    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...

  11. #36
    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 by jpk; 04-16-2019 at 02:25 PM. Reason: added 3rd code block

  12. #37
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    423
    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.

    Click image for larger version. 

Name:	winder5.PNG 
Views:	12 
Size:	17.3 KB 
ID:	16434

    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?

    Click image for larger version. 

Name:	winder6.PNG 
Views:	8 
Size:	20.0 KB 
ID:	16435
    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 by luni; 04-16-2019 at 07:45 PM.

  13. #38
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    423
    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...

  14. #39
    Quote Originally Posted by luni View Post
    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...

    Quote Originally Posted by luni View Post
    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.)

    Quote Originally Posted by luni View Post
    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 by jpk; 04-16-2019 at 11:05 PM.

  15. #40
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    423
    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 by luni; 04-17-2019 at 06:41 AM.

  16. #41
    Quote Originally Posted by luni View Post
    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

    (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 by jpk; 04-17-2019 at 11:55 AM.

  17. #42
    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 by jpk; 04-17-2019 at 04:21 PM. Reason: code

  18. #43
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    423
    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.

    Click image for larger version. 

Name:	winder7.PNG 
Views:	21 
Size:	28.5 KB 
ID:	16440

    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)
    Attached Files Attached Files
    Last edited by luni; 04-18-2019 at 06:07 AM.

  19. #44
    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 )

  20. #45
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    423
    Quote Originally Posted by jpk View Post
    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 :-) )

  21. #46
    Quote Originally Posted by luni View Post
    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...?

    Quote Originally Posted by luni View Post
    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

  22. #47
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    423
    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...cations/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.

  23. #48
    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);
    }

  24. #49
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    423
    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
    Click image for larger version. 

Name:	winder7.PNG 
Views:	11 
Size:	26.9 KB 
ID:	16454
    Last edited by luni; 04-20-2019 at 04:42 PM.

  25. #50
    Quote Originally Posted by luni View Post
    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...?

    Quote Originally Posted by luni View Post
    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

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •