Introducing "SyncroStepper" - Syncronization For Multi-Axis Machines
idbruce
Posts: 6,197
Hello Everyone
Here is some code that will allow you to get some good speed out of your stepper motors and keep the start of their movement syncronized. Interpolation is my next project, but for now, this may wet your appetite.
This really isn't what I wanted to do, because there is a lot of redundant code, but it works. I really wanted to create an object for it, but I was having trouble updating and checking my variables. If you want the existing code for the original plan for syncronized steppers, go to this post: http://forums.parallax.com/showthread.php/159900-Does-This-Method-Provide-The-Exact-Same-Return-As-SPINs-Square-Root-Operator?p=1313527&viewfull=1#post1313527
If you want more information pertaining to how the code works and settings for the constants and variables, go to this thread: http://forums.parallax.com/showthread.php/159853-New-Stepper-Driver-Object-A-Variant-Of-PulseTheStepPin
All I can say is.... "Oh yeah baby"
EDIT: The syncronization has not been checked with a scope. If the start of the axes are not truly syncronized, then they are pretty darn close. If more accuracy is needed, then perhaps a "WAITPEQ" would do the trick.
Here is some code that will allow you to get some good speed out of your stepper motors and keep the start of their movement syncronized. Interpolation is my next project, but for now, this may wet your appetite.
This really isn't what I wanted to do, because there is a lot of redundant code, but it works. I really wanted to create an object for it, but I was having trouble updating and checking my variables. If you want the existing code for the original plan for syncronized steppers, go to this post: http://forums.parallax.com/showthread.php/159900-Does-This-Method-Provide-The-Exact-Same-Return-As-SPINs-Square-Root-Operator?p=1313527&viewfull=1#post1313527
If you want more information pertaining to how the code works and settings for the constants and variables, go to this thread: http://forums.parallax.com/showthread.php/159853-New-Stepper-Driver-Object-A-Variant-Of-PulseTheStepPin
All I can say is.... "Oh yeah baby"
EDIT: The syncronization has not been checked with a scope. If the start of the axes are not truly syncronized, then they are pretty darn close. If more accuracy is needed, then perhaps a "WAITPEQ" would do the trick.

Comments
-Phil
No need to apologize for corrections and please feel free to correct me at anytime, because if I am ignorant, I certainly do not want to remain that way
As far as being an interesting project, I would have to ask which part, the project as a whole or just this section of code? However I will say this, there is something very satisfying about seeing two homemade linear actuators operate as smooth as silk and in unison. I am impatiently waiting for the day when I can finally test my extruder, but I am quite a ways from that point.
The test represents X and Y both moving 12 inches in one direction and then 12 inches in the other, under a repeat loop of 10 times, so it represents 20 linear feet of movement for each axis. Additionally, there are several waitcnt(s) in the test code, so these movements would actually be a lot faster.
CON _CLKMODE = XTAL1 + PLL16X _XINFREQ = 5_000_000 'Clock Frequency Equation CLK_FREQ = ((_CLKMODE - XTAL1) >> 6) * _XINFREQ 'X Axis X_DIRECTION = 22 X_STEP = 23 X_DISABLE = 24 X_PULSE_WIDTH = CLK_FREQ / 1_000_000 '= 80 X_MIN_SPEED = CLK_FREQ / 5_714 '= 14_000 Rounded X_MAX_SPEED = CLK_FREQ / 16_000 '= 5_000 X_RAMP_INC_DEC = CLK_FREQ / 26_666_666 '= 3 Rounded X_INC_DIR = 0 '= Counter-clockwise rotation X_DEC_DIR = 1 '= Clockwise rotation X_STEPS_PER_REV = 2_000 '= 200 Full steps * 10 microsteps X_STEPS_PER_METER = 55_555 X_RAMPING_STEPS = (X_MIN_SPEED - X_MAX_SPEED) / X_RAMP_INC_DEC X_TOTAL_RAMPING_STEPS = X_RAMPING_STEPS * 2 X_MAX_STEPS = 16_933 '12 inches of linear travel 'Y Axis Y_DISABLE = 25 Y_STEP = 26 Y_DIRECTION = 27 Y_PULSE_WIDTH = CLK_FREQ / 1_000_000 '= 80 Y_MIN_SPEED = CLK_FREQ / 5_714 '= 14_000 Rounded Y_MAX_SPEED = CLK_FREQ / 16_000 '= 5_000 Y_RAMP_INC_DEC = CLK_FREQ / 26_666_666 '= 3 Rounded Y_INC_DIR = 1 '= Counter-clockwise rotation Y_DEC_DIR = 0 '= Clockwise rotation Y_STEPS_PER_REV = 2_000 '= 200 Full steps * 10 microsteps Y_STEPS_PER_METER = 55_555 Y_RAMPING_STEPS = (Y_MIN_SPEED - Y_MAX_SPEED) / Y_RAMP_INC_DEC Y_TOTAL_RAMPING_STEPS = Y_RAMPING_STEPS * 2 Y_MAX_STEPS = 16_933 '12 inches of linear travel VAR LONG lMoveNow BYTE bX_Direction LONG lX_MoveComplete LONG lX_RampingSteps LONG lX_RunningSteps LONG lX_Stack[100] BYTE bY_Direction LONG lY_MoveComplete LONG lY_RampingSteps LONG lY_RunningSteps LONG lY_Stack[100] LONG xStartCount LONG xEndCount LONG yStartCount LONG yEndCount LONG StartDifference LONG EndDifference OBJ Debug : "FullDuplexSerial" PUB Main Debug.start(31, 30, 0, 115200) WAITCNT(CLKFREQ + CNT) COGNEW(X_Stage, @lX_Stack) COGNEW(Y_Stage, @lY_Stack) WAITCNT(CNT + (3 * CLKFREQ)) REPEAT 10 TestStageSyncronization Debug.str(STRING("xStartCount = ")) Debug.dec(xStartCount) DEBUG.tx(13) Debug.str(STRING("xEndCount = ")) Debug.dec(xEndCount) DEBUG.tx(13) Debug.str(STRING("yStartCount = ")) Debug.dec(yStartCount) DEBUG.tx(13) Debug.str(STRING("yEndCount = ")) Debug.dec(yEndCount) DEBUG.tx(13) IF xStartCount > yStartCount StartDifference := xStartCount - yStartCount ELSE StartDifference := yStartCount - xStartCount IF xEndCount > yEndCount EndDifference := xEndCount - yEndCount ELSE EndDifference := yEndCount - xEndCount Debug.str(STRING("StartDifference = ")) Debug.dec(StartDifference) Debug.str(STRING(" Clock Cycles")) DEBUG.tx(13) Debug.str(STRING("EndDifference = ")) Debug.dec(EndDifference) Debug.str(STRING(" Clock Cycles")) DEBUG.tx(13) DEBUG.tx(13) PUB TestStageSyncronization lX_MoveComplete := TRUE lY_MoveComplete := TRUE 'Prepare X stage for positive movement UpdateX_RampingAndRunningSteps(X_MAX_STEPS) bX_Direction := X_INC_DIR 'Prepare Y stage for positive movement UpdateY_RampingAndRunningSteps(Y_MAX_STEPS) bY_Direction := Y_INC_DIR 'Now make the moves lMoveNow := TRUE 'Loop until the moves are complete and then reinitlize the variables REPEAT WHILE lX_MoveComplete == FALSE AND lY_MoveComplete == FALSE lX_MoveComplete := FALSE lY_MoveComplete := FALSE 'Wait a bit for test purposes WAITCNT(CNT + (5 * CLKFREQ)) 'Now get ready to go in the opposite direction 'Prepare X stage for negative movement UpdateX_RampingAndRunningSteps(X_MAX_STEPS) bX_Direction := X_DEC_DIR 'Prepare Y stage for negative movement UpdateY_RampingAndRunningSteps(Y_MAX_STEPS) bY_Direction := Y_DEC_DIR 'Now make the moves lMoveNow := TRUE PUB UpdateX_RampingAndRunningSteps(TotalSteps) 'If maximum speed can be obtained, determine the actual number of 'ramping and running steps. IF TotalSteps > X_TOTAL_RAMPING_STEPS 'Copy the global amount of ramping steps lX_RampingSteps := X_RAMPING_STEPS 'Determine the number of full speed steps. lX_RunningSteps := TotalSteps - X_TOTAL_RAMPING_STEPS 'Else ramp upto the half-way point and then ramp down. ELSE lX_RampingSteps := TotalSteps / 2 lX_RunningSteps := TotalSteps // 2 PUB UpdateY_RampingAndRunningSteps(TotalSteps) 'If maximum speed can be obtained, determine the actual number of 'ramping and running steps. IF TotalSteps > Y_TOTAL_RAMPING_STEPS 'Copy the global amount of ramping steps lY_RampingSteps := Y_RAMPING_STEPS 'Determine the number of full speed steps. lY_RunningSteps := TotalSteps - Y_TOTAL_RAMPING_STEPS 'Else ramp upto the half-way point and then ramp down. ELSE lY_RampingSteps := TotalSteps / 2 lY_RunningSteps := TotalSteps // 2 PUB X_Stage | Counter, StartStopSpeed xStartCount := CNT DIRA[X_DIRECTION] := 1 REPEAT IF lMoveNow == TRUE lMoveNow := FALSE StartStopSpeed := X_MIN_SPEED 'Set up the CTRMODE of Counter A for NCO/PWM single-ended. CTRA[30..26] := %00100 'Set the output pin for Counter A. CTRA[5..0] := X_STEP 'Set the value to be added to PHSA with every clock cycle. FRQA := 1 'Set APIN as an output. DIRA[X_STEP] := 1 'Set the OUTA register to match the desired direction of rotation. OUTA[X_DIRECTION] := bX_Direction 'Get the current System Counter value. Counter := CNT 'Ramp up the stepper motor to maximum speed. REPEAT lX_RampingSteps 'Send out a high pulse on the step pin for the desired duration. PHSA := -X_PULSE_WIDTH 'Wait for a specified period of time before sending another 'high pulse to the step pin. WAITCNT(Counter += StartStopSpeed -= X_RAMP_INC_DEC) 'Maintain maximum speed REPEAT lX_RunningSteps 'Send out a high pulse on the step pin for the desired duration. PHSA := -X_PULSE_WIDTH 'Wait for a specified period of time before sending another 'high pulse to the step pin. WAITCNT(Counter += StartStopSpeed) 'Ramp down the stepper motor and come to a stop. REPEAT lX_RampingSteps 'Send out a high pulse on the step pin for the desired duration. PHSA := -X_PULSE_WIDTH 'Wait for a specified period of time before sending another 'high pulse to the step pin. WAITCNT(Counter += StartStopSpeed += X_RAMP_INC_DEC) xEndCount := CNT lX_MoveComplete := TRUE PUB Y_Stage | Counter, StartStopSpeed yStartCount := CNT DIRA[Y_DIRECTION] := 1 REPEAT IF lMoveNow == TRUE lMoveNow := FALSE StartStopSpeed := Y_MIN_SPEED 'Set up the CTRMODE of Counter A for NCO/PWM single-ended. CTRA[30..26] := %00100 'Set the output pin for Counter A. CTRA[5..0] := Y_STEP 'Set the value to be added to PHSA with every clock cycle. FRQA := 1 'Set APIN as an output. DIRA[Y_STEP] := 1 'Set the OUTA register to match the desired direction of rotation. OUTA[Y_DIRECTION] := bY_Direction 'Get the current System Counter value. Counter := CNT 'Ramp up the stepper motor to maximum speed. REPEAT lY_RampingSteps 'Send out a high pulse on the step pin for the desired duration. PHSA := -Y_PULSE_WIDTH 'Wait for a specified period of time before sending another 'high pulse to the step pin. WAITCNT(Counter += StartStopSpeed -= Y_RAMP_INC_DEC) 'Maintain maximum speed REPEAT lY_RunningSteps 'Send out a high pulse on the step pin for the desired duration. PHSA := -Y_PULSE_WIDTH 'Wait for a specified period of time before sending another 'high pulse to the step pin. WAITCNT(Counter += StartStopSpeed) 'Ramp down the stepper motor and come to a stop. REPEAT lY_RampingSteps 'Send out a high pulse on the step pin for the desired duration. PHSA := -Y_PULSE_WIDTH 'Wait for a specified period of time before sending another 'high pulse to the step pin. WAITCNT(Counter += StartStopSpeed += Y_RAMP_INC_DEC) yEndCount := CNT lY_MoveComplete := TRUEIt needs a cog per steppermotor but it eliminates timedifferences between steppulses. The bresenham algorithm creates this jitter if the "slave"-axle runs at certain speedratios that are not a fraction represented by small even numbers
Best regards
Stefan
The time differences are an absolute necessity to achieve motor ramping. Without ramping, a stepper motor will never be able to achieve it's full potential. So in actuality, time differences are a good thing.
-Phil
Oh, I see... Thanks for clafying.
@Stefan
My apologies for the misinterpretation
Thank you.
-Phil
By the way I posit jitter is neither here-nor-there if you are using a reasonable amount of micro-stepping,
partly because the effect is so small (slight distortions of the sine-waves) and partly because you are generating
steps at a rate where the chopper circuit period is significant, so synchronising with that (not normally possible)
would be needed to avoid jitter.
Or more simply the jitter in microseconds is a much smaller value. You do have to generate steps faster of course.
http://forums.parallax.com/showthread.php/142705-Step-Dir-signal-generator-for-CNC?highlight=manatwork
brs