Forum Rule: Always post complete source code & details to reproduce any issue!
Page 2 of 2 FirstFirst 1 2
Results 26 to 44 of 44

Thread: New Stepper Motor Library

  1. #26
    Junior Member
    Join Date
    Jul 2015
    Posts
    8
    I did measure it with and without anything connected, with the same result.

    if I do a digital write to the same pins they output 3.3 volts as expected. I'm sorta clueless.

  2. #27
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    307
    Just to make sure that Teensy and scope are working, I'd test if this gives 10Ás pulses on your scope:

    Code:
    constexpr int pin = 0;
    
    void setup() {
      pinMode(pin, OUTPUT);
    }
    
    void loop() {
      digitalWriteFast(pin, HIGH);
      delayMicroseconds(10);
      digitalWriteFast(pin, LOW);
    
      delayMicroseconds(200);
    }

  3. #28
    Junior Member
    Join Date
    Jul 2015
    Posts
    8
    strange, I think you got the point, though I'm not sure why the servo motors don't run.
    I tested it with an arduino uno before and they had no problem.

    here's my scope output generated by your test code:Click image for larger version. 

Name:	Scope.jpg 
Views:	7 
Size:	123.5 KB 
ID:	15204

    I'm again clueless now.

  4. #29
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    307
    Which drivers are you using? Datasheet? It could be that the 3.3V of a Teensy are too small to drive optocoupled inputs. Here a thread which might help: https://forum.pjrc.com/threads/50148...-which-uses-5V

  5. #30
    Junior Member
    Join Date
    Jul 2015
    Posts
    8
    I measured directly at the teensy pin and for now I use a ULN2803A darlington transistor array as a driver.
    of course the teensy output doesn't switch the transistors properly.
    for any reason it seems to be a timer issue. I suspected platformio to be the problem, but I got the same result using arduino IDE

  6. #31
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    307
    If you can post a minimal example sketch showing the effect I can try to reproduce and dig further into it.

  7. #32
    Junior Member
    Join Date
    Jul 2015
    Posts
    8
    Thanks luni for all your help.

    Finally I figured there where a few issues, that in combination made no sense and caused me to not figure the actual problem.

    First problem: my oscilloscope obviosly suddenly died, or shows wrong values on a wrong timebase, on both channels wich was misleading
    second problem (the actual problem why it didn't work): I made a mistake in the driver circuit -> forgot to pull up the outputs

  8. #33
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    307
    Glad to hear that it works now. Have fun with TeensyStep!

  9. #34
    Junior Member
    Join Date
    Nov 2015
    Posts
    9
    Thanks for the great library! Have you given any thought to adding support for s-curves?

    I wrote a small python script that can be used to test/simulate different "motion profiles" using TeensyStep.

    Code:
    import time
    import math
    import signal
    import numpy as np
    import matplotlib.pyplot as plt
    
    # Teensy 3.2 at 96Mhz -> Bus at 48Mhz
    F_BUS = 48e6
    
    
    class MotionProfile(object):
        speedMax = 20000
        speedMin = 1000
        accelMax = 8000
        accelEnd = 0
        decelStart = 0
        distance = 0
        dtMin = 0
        dtMax = 0
        dtAccel = 0
        currentDelay = 0
        
        def start(self, e):
            self.distance = e
            dv = self.speedMax-self.speedMin
            dt = dv/float(self.accelMax)
            d = dv*dt/2.0
            self.accelEnd = min(self.distance/2, d)
            self.decelStart = self.distance-self.accelEnd
            self.dtMin = F_BUS/self.speedMin
            self.dtMax = F_BUS/self.speedMax
            self.dtAccel = math.sqrt(2*self.accelMax)
            return self.dtMin
        
        def update(self, e):
            pos = self.distance-e
            if pos < self.accelEnd:
                return F_BUS/(self.dtAccel*math.sqrt(pos)+self.speedMin)
            elif pos < self.decelStart:
                return self.dtMax
            else:
                return F_BUS/(self.dtAccel*math.sqrt(e)+self.speedMin)
    
    
    class Simulation(object):
        """ Simulates the TeensyStep controller
        
        """
        speedUpdateRate = 5000
        profile = None
        
        def __init__(self, **kwargs):
            for k, v in kwargs.items():
                setattr(self, k, v)
        
        def run(self, target, time_limit=10, sample_smoothing=1, 
                simulation_speed=100.0):
            """
            Run a simulation of a move from 0 to target steps
            
            Parameters
            ----------
            target: unsigned int
                Number of steps to move
            time_limit: float
                Seconds to wait before stopping the simulation if target is not hit
            sample_smoothing: unsigned int
                Smooth samples over multiple points
            simulation_speed: float
                Scales the timers to "speed up" the simulation
            
            """
            # t, p, v, a, j
            sim = []
            start_time = time.time()
            profile = self.profile
            
            position = 0
            error = target-position
            
            # Setup simulated timers
            t1_expiry = profile.start(error)  # PIT timer
            t2_expiry = F_BUS/1000000*self.speedUpdateRate  # FTM timer
            t3_expiry = F_BUS/2  # Simulation watchdog timer
            
            # Run until complete
            print("Timers: PIT={}, FTM={}".format(t1_expiry, t2_expiry))
            
            # Set initial values clk, pos, v, a, j, period
            v, a, j, = 0, 0, 0
            sim.append((-t2_expiry/3, position, v, a, j, 0))
            sim.append((0, position, v, a, j, 0))
            
            # Run simulation
            clk, i, t1, t2, t3 = 0, 0, 0, 0, 0
            while True:
                clk += 1
                t1 += 1
                t2 += 1
                t3 += 1
                if t1_expiry and t1 >= t1_expiry/simulation_speed:  # PIT timer
                    t1 = 0  # clear timer
                    if error > 0:
                        position += 1  # do step
                        error = target-position
                    else:
                        t1_expiry = 0 # Stop timer
                if t2 >= t2_expiry/simulation_speed:  # FTM timer
                    t2 = 0  # clear timer
                    
                    # Change speed
                    if t1_expiry:
                        t1_expiry = profile.update(error)
                    
                    i += 1
                    # If we log too much we get jittery data
                    if i >= sample_smoothing:
                        i = 0
                        # Log simulation data
                        last = sim[-1]
                        t = (clk-last[0])/simulation_speed
                        v = (position-last[1])/t
                        a = (v-last[2])/t
                        j = (a-last[3])/t
                        sim.append((clk, position, v, a, j, t1_expiry))
                        
                # In case nothing happens abort
                if t3 >= t3_expiry/simulation_speed:
                    t3 = 0
                    if error == 0:
                        break
                    if time.time()-start_time > time_limit:
                        print("Aborted: {} {}".format(clk, error))
                        break
            
            print("Complete: {}".format(round(time.time()-start_time), 2))
            return sim
    
    
    def main():
        # Simulate the given motion profile
        sim = Simulation(profile=MotionProfile())
        data = sim.run(100000, time_limit=2, sample_smoothing=10)
        
        # Plot the data
        t = [s[0] for s in data]
        fig, plots = plt.subplots(5, 1)
        plots[0].set_title("Simulation")
        for i, (label, color) in enumerate((
                  ('Position', 'red'),
                  ('Velocity', 'blue'),
                  ('Accel', 'green'),
                  ('Jerk', 'purple'),
                  ('Cycles', 'orange')
                )):
            p = plots[i]
            ydata = [s[i+1] for s in data]
            p.plot(t, ydata, color=color, label=label)
            p.set_ylabel(label)
        plots[-1].set_xlabel('Clock')
        plt.show()
    
    
    if __name__ == '__main__':
        # Keep ctrl c
        signal.signal(signal.SIGINT, signal.SIG_DFL)
        main()
    Click image for larger version. 

Name:	TeensyStepSim.jpg 
Views:	9 
Size:	64.6 KB 
ID:	15266
    Attached Thumbnails Attached Thumbnails Click image for larger version. 

Name:	TeensyStepSim.jpg 
Views:	10 
Size:	66.5 KB 
ID:	15263  
    Last edited by frmdstryr; 11-30-2018 at 03:02 PM. Reason: Refactor

  10. #35
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    307
    Have you given any thought to adding support for s-curves?
    Yes, thought of that but the need was never big enough to really implement it. I could offer to implement a hook function which will be called whenever a new acceleration value is needed. You could then implement any acceleration you feel is appropriate for your application. Would that make sense for you?

  11. #36
    Junior Member
    Join Date
    Nov 2015
    Posts
    9
    That would be great! In my own tweaked version here I made it delegate the initial (from doStep) and update calculations (from the accUpdateChannel) to a class that can implement the methods as needed.

    Code:
    class MotionProfile {
    public:
        /**
         * Initialize the motion profile
         * @return time in steps/s 
         */
        virtual uint32_t start(const StepControl &controller) {
            return update(controller);
        }
        
        /**
         * Calculate the timer delay for the next step which in
         * turn sets the velocity.
         * @return time in steps/s 
         */
        virtual uint32_t update(const StepControl &controller) {
            return 10000; 
        }
    };
    Then made a subclass for the trapezoidal profile and am working on another subclass for s-curves.

  12. #37
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    307
    Yep, this is exactly what I was thinking of. I would implement a base class with all the house keeping functionality and the Bresenham part.
    • I'd add two abstract members, say start and updateSpeed. You would need to override them in your derived controller classes.
    • void start() would be called right before the movement starts. This would be the place where you could pre-calculate all values which will stay constant during the movement.
    • uint32 updateSpeed(currentPos) would be called whenever a new speed value is needed. You simply would calculate the new speed based on the passed in current position and return it.


    If you are willing to do the testing I can try to implement it tomorrow.

  13. #38
    Junior Member
    Join Date
    Nov 2015
    Posts
    9
    Sounds good! No rush, I can try it out whenever it's ready. Thanks!

  14. #39
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    307

    Custom acceleration profile

    @frmdstryr: As discussed yesterday, I splitted the original StepControl class into the abstract base class StepControlBase and derived the new StepControl from this class. The code is uploaded to the development branch of the GitHub repository (https://github.com/luni64/TeensyStep/tree/develop )

    To generate any special acceleration profile you need to to the following:

    1. Derive a new controller class from StepControlBase (you can use my StepControl as a template, see below)
    2. Override the following functions in your controller:
      • uint32_t prepareMovement(uint32_t targetPos, uint32_t targetSpeed, uint32_t pullInSpeed, uint32_t acceleration)
        In that function you can setup whatever you need during the movement. The passed in parameters should be clear from the names. You need to return the starting frequency in steps/s from this function. You only need to take care of the acceleration, the Bresenham algorithm is handled by the base class.
      • uint32_t updateSpeed(uint32_t currentPosition)
        This function will be called whenever a new speed is required. You need to calculate that speed based on the passed in position and return it in steps/s
      • uint32_t StepControl::initiateStopping(uint32_t currentPosition)
        This function will be called when the user requests a stop. Do whatever needs to be done to calculate a stopping profile and return the new target position.


    Sounds more complicated than it really is. Let me know if that works for you. Here the current code for my standard acceleration profile as an example:

    Code:
    #include "StepControlBase.h"
    
    class StepControl : public StepControlBase<>
    {
      protected:
        uint32_t accelerationEnd;
        uint32_t decelerationStart;
        uint32_t sqrt_2a;
        uint32_t s_tgt, v_tgt, v_min;
    
        inline uint32_t prepareMovement(uint32_t targetPos, uint32_t targetSpeed, uint32_t pullInSpeed, uint32_t acceleration);
        inline uint32_t updateSpeed(uint32_t currentPosition);
        inline uint32_t initiateStopping(uint32_t currentPosition);
    };
    
    // IMPLEMENTATION --------------------------------------------------------------------------------------------------
    
    uint32_t StepControl::prepareMovement(uint32_t targetPos, uint32_t targetSpeed, uint32_t pullInSpeed, uint32_t a)
    {
        s_tgt = targetPos;
        v_tgt = targetSpeed;
        v_min = pullInSpeed;
    
        if (v_tgt > v_min)                   // target speed larger than pull in speed
        {                                    // -> acceleration required
            float dv = v_tgt - v_min;
            float ae = dv * dv / (2.0f * a); // length of acceleration phase (steps)
            sqrt_2a = sqrtf(2.0f * a);       // precalculated factor, needed during acceleration
    
            accelerationEnd = std::min((uint32_t)ae, s_tgt / 2); // limit acceleration phase to half of total steps
            decelerationStart = s_tgt - accelerationEnd;
            return v_min;                    // start movment with the pull in speed
        }
        else                                 // target speed smaller that pull in speed
        {                                    // -> no acceleration necessary
            accelerationEnd = 0;
            decelerationStart = s_tgt;
            return v_tgt;
        }
    }
    
    uint32_t StepControl::updateSpeed(uint32_t currentPosition)
    {
        // acceleration phase -------------------------------------
        if (currentPosition < accelerationEnd)
            return sqrt_2a * sqrtf(currentPosition) + v_min;
    
        // constant speed phase ------------------------------------
        if (currentPosition < decelerationStart)
            return v_tgt;
    
        //deceleration phase --------------------------------------
        return sqrt_2a * sqrtf(s_tgt - currentPosition) + v_min;
    }
    
    uint32_t StepControl::initiateStopping(uint32_t s_cur)
    {
        if (s_cur <= decelerationStart)  // we are already decelerating, nothing to change...
        {
            return s_tgt;
        }
        else                             // accelerating or constant speed phase
        {
            uint32_t newTarget;
            if (s_cur < accelerationEnd) // still accelerating
                newTarget = s_cur * 2;   // will take the same distance to stop as we already traveled
            else                         // constant speed phase
                newTarget = s_cur + accelerationEnd;
    
            accelerationEnd = 0;
            decelerationStart = s_cur; // start decelerating now
            return newTarget;
        }
    }
    Last edited by luni; 12-01-2018 at 11:20 AM.

  15. #40
    Junior Member
    Join Date
    Nov 2015
    Posts
    9
    Tried it out. Works well! Thank you!

    One more thing. I've been using TeensyStep to have a engraver follow a path. I know there's already a callback that can be fired when a move completes but at that point the timer is stopped already and must be started again for the next move which may cause jerk (assuming the velocity profile is changed so that it doesn't stop at each point).

    What are your thoughts on providing a way to keep the timer running if more immediate moves are needed?

    Something like a `virtual void onComplete()` that can be overridden to only stop the timer if there's no more moves waiting and modified doMove to only restart the timer if it's not already running.

  16. #41
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    307
    What are your thoughts on providing a way to keep the timer running if more immediate moves are needed? Something like a `virtual void onComplete()` that can be overridden to only stop the timer if there's no more moves waiting and modified doMove to only restart the timer if it's not already running.
    What you are looking for is probably a motion planner. I.e. you give it a list of 2d (3d) data and it then generates movements from point to point without stopping in between. Unfortunately this is something quite difficult to implement. It is not about just working through a list of points, you also need to do a proper acceleration / deceleration in between. E.g. if you move from say (x,y) = (0,0) to (1000, 0) to (1000,1000) you need to plan what do to with the y motor which needs to accelerate from zero. To stay in sync, the x-motor needs to be decelerated first. The situation is completely different when both motors where moving before. What I want to say is: "no, I probably won't open that can of worms..."

    But, there are perfect solutions available for that kind of problems. grbl is kind of standard in the open source DIY CNC scene. It was made for exactly this kind of applications, comes with a g-code interpreter, can control up to 3 motors with up to 30000 steps/s. I don't know if someone ported it to ARM yet. (It was developed for UNOs, i.e. it runs on a simple ATmega328P). Anyway, a couple of years ago I did a project using a 328p as "grbl coprocessor" for a Teensy which worked nicely. The encircled part is the grbl coprocessor.

    Click image for larger version. 

Name:	grbl2.png 
Views:	13 
Size:	664.7 KB 
ID:	15272

    Here you can see it in action (one motor only, the video was meant to show the performance of the encoder code and the websocket server running on the Teensy) https://youtu.be/ZLVXQfjfS6Q.

    If you are interested I can post schematics, I should also have a couple of empty boards lying around somewhere...

  17. #42
    Junior Member
    Join Date
    Nov 2015
    Posts
    9
    Sure I'd be interested in checking out the schematics if you can pm them to me. Yes, I've heard of grbl but haven't tried it yet as i'd prefer to do everything on the teensy.

    S-curve's are working in the simulation but there's some issues that seem to be related to floating point math / overflows or something on the actual teensy (3.2). It works fine for 25k steps/s on my desk but at 60k steps/s on the engraver I'm getting strange behavior on larger distance moves (works well for movements up to around 120k steps).


    Click image for larger version. 

Name:	TeensyStepSCurve.jpg 
Views:	7 
Size:	63.2 KB 
ID:	15289


    Also regarding ,

    you also need to do a proper acceleration / deceleration in between. E.g. if you move from say (x,y) = (0,0) to (1000, 0) to (1000,1000) you need to plan what do to with the y motor which needs to accelerate from zero
    Correct, I plan to keep track of the current state of each motor and scale the velocities based on the dot products of the path vectors using a look ahead algorithm on a queue of points. Perhaps I'm over simplifying it but I believe it should just work like how the current line following algorithm adjusts the speed of each motor.

  18. #43
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    307
    Sure I'd be interested in checking out the schematics
    Here you are: drive-download-20181205T110944Z-001.zip

    S-curve's are working in the simulation but there's some issues that seem to be related to floating point math / overflows or something on the actual teensy (3.2). It works fine for 25k steps/s on my desk but at 60k steps/s on the engraver I'm getting strange behavior on larger distance moves (works well for movements up to around 120k steps).
    Sounds great! When you fixed the issues, would it be possible to get a copy for the examples folder (Pull request would be even better)?

    Correct, I plan to keep track of the current state of each motor and scale the velocities based on the dot products of the path vectors using a look ahead algorithm on a queue of points. Perhaps I'm over simplifying it but I believe it should just work like how the current line following algorithm adjusts the speed of each motor.
    Looking forward to see your results, would be a really useful feature indeed.

  19. #44
    Junior Member
    Join Date
    Dec 2018
    Posts
    1
    Just wanted to say thanks to luni for some great code, and thanks to frmdstryr for helping to improve it. I was moving my linear test jig in a matter of minutes using luni's library & a Teensy 3.6. It was only a few minutes after that that I realized S-curve ramping was needed. Now, it appears it will be realized soon. Thank you both again for your efforts!

Posting Permissions

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