PulseTheStepPin revisited - Questions, answers, problems, solutions

Hello Everyone
As promised, I have finally managed to focus my attention to rewriting the PulseTheStepPin function. For those of you who are unfamiliar with this function, it is counter based PWM stepper driver software, that pulses the step pin on stepper driver hardware. More particularly, it pulses the step pin of stepper drivers that already have a step and direction translator in place.
Based upon a timed test using FullDuplexSerial, the original software driver made my motors run at approximately 13.34 revolutions per second with the aid of Gecko G251 stepper drivers. For your viewing pleasure, I have attached the original code below.
Out of necessity to make my code easier to read, more compact, and faster, I rewrote the function with a few more parameters. The new function is as follows:
Since each of my motors run under different circumstances and a different load, I decided to provide constant values for each motor that could easily be changed to affect only that individual motor. To give you an example of some of the different settings, here is another portion of code for you that supplies many of the parameters used by this function:
Please notice that the output of the direction pin which is sent to the stepper driver is set prior to the use of the function. Some of you may want to alter this and add it as a parameter of the function, I did not.
So where do we go from here. To be perfectly honest, I still have the need for more speed, which is one of the reasons that this rewrite is still a work in progress. In my attempts to gain more speed this is what I have learned. By altering a portion of this code, I have found that the motors start gaining speed much quicker, which of course results in faster excution. Here is the portion showing the altered code:
With this alteration, 7 out of 8 of my motors ran flawlessly with more speed, the 8TH motor failed miserably. I would attribute this to something that Dave Hein tried to point out to me, and he said:
Bruce
As promised, I have finally managed to focus my attention to rewriting the PulseTheStepPin function. For those of you who are unfamiliar with this function, it is counter based PWM stepper driver software, that pulses the step pin on stepper driver hardware. More particularly, it pulses the step pin of stepper drivers that already have a step and direction translator in place.
Based upon a timed test using FullDuplexSerial, the original software driver made my motors run at approximately 13.34 revolutions per second with the aid of Gecko G251 stepper drivers. For your viewing pleasure, I have attached the original code below.
Out of necessity to make my code easier to read, more compact, and faster, I rewrote the function with a few more parameters. The new function is as follows:
PUB PulseTheStepPin(TotalSteps, StepPin, RampingRange, SpeedRange, CycleOffset) | RampingSteps, RunningSteps, Counter
IF TotalSteps > RampingRange
RampingSteps := SpeedRange
RunningSteps := TotalSteps - RampingRange
ELSE
RampingSteps := TotalSteps / 2
RunningSteps := TotalSteps // 2
CTRA[30..26] := %00100
CTRA[5..0] := StepPin
FRQA := 1
DIRA[StepPin]~~
Counter := CNT
REPEAT RampingSteps
PHSA := -HIGH_PULSE_WIDTH
WAITCNT(Counter += CycleOffset--)
REPEAT RunningSteps
PHSA := -HIGH_PULSE_WIDTH
WAITCNT(Counter += CycleOffset)
REPEAT RampingSteps
PHSA := -HIGH_PULSE_WIDTH
WAITCNT(Counter += CycleOffset++)
Since each of my motors run under different circumstances and a different load, I decided to provide constant values for each motor that could easily be changed to affect only that individual motor. To give you an example of some of the different settings, here is another portion of code for you that supplies many of the parameters used by this function:
'Clock Frequency Equation
CLK_FREQ = ((_CLKMODE - XTAL1) >> 6) * _XINFREQ
'This is the setting for the minimal step pulse width.
HIGH_PULSE_WIDTH = CLK_FREQ / 1_000_000 'CLK_FREQ / 500_000
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Wire Dispenser Stepper Motor
DISPENSER_STEP = 2 'I/O Pin 'Step Pin
DISPENSER_CYCLE_OFFSET = 8_000
DISPENSER_MIN_SPEED = 8_000
DISPENSER_MAX_SPEED = 11_000
DISPENSER_SPEED_RANGE = DISPENSER_MAX_SPEED - DISPENSER_MIN_SPEED
DISPENSER_RAMPING_RANGE = DISPENSER_SPEED_RANGE * 2
DISPENSER_FEED_AMOUNT = 250
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Wire Feeder Stepper Motor
FEEDER_STEP = 3 'I/O Pin 'Step Pin
FEEDER_DISABLE = 4 'I/O Pin 'Disable Pin
FEEDER_CYCLE_OFFSET = 15_000
FEEDER_MIN_SPEED = 8_000 '15_000
FEEDER_MAX_SPEED = 11_000 '20_000
FEEDER_SPEED_RANGE = FEEDER_MAX_SPEED - FEEDER_MIN_SPEED
FEEDER_RAMPING_RANGE = FEEDER_SPEED_RANGE * 2
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Wire Bender Stepper Motor
BENDER_STEP = 8 'I/O Pin 'Step Pin
BENDER_DIRECTION = 9 'I/O Pin 'Direction Pin
BENDER_CYCLE_OFFSET = 8_000
BENDER_MIN_SPEED = 8_000
BENDER_MAX_SPEED = 11_000
BENDER_SPEED_RANGE = BENDER_MAX_SPEED - BENDER_MIN_SPEED
BENDER_RAMPING_RANGE = BENDER_SPEED_RANGE * 2
FIRST_BENDING_AMOUNT = 1_180
SECOND_BENDING_AMOUNT = 1_025
NINETY_DEGREE_BEND = 820
BENDER_DIRECTION_CHANGE = 1_000
'CCW_PHOTO_SENSOR_ADJ = 3
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Gantry Stepper Motor
GANTRY_STEP = 10 'I/O Pin 'Step Pin
GANTRY_DIRECTION = 11 'I/O Pin 'Direction Pin
GANTRY_CYCLE_OFFSET = 8_000
GANTRY_MIN_SPEED = 8_000
GANTRY_MAX_SPEED = 11_000
GANTRY_SPEED_RANGE = GANTRY_MAX_SPEED - GANTRY_MIN_SPEED
GANTRY_RAMPING_RANGE = GANTRY_SPEED_RANGE * 2
GANTRY_TRAVEL_AMOUNT = 1371
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Wire Cutter Stepper Motor
CUTTER_STEP = 12 'I/O Pin 'Step Pin
CUTTER_DIRECTION = 13 'I/O Pin 'Direction Pin
CUTTER_CYCLE_OFFSET = 8_000
CUTTER_MIN_SPEED = 8_000
CUTTER_MAX_SPEED = 11_000
CUTTER_SPEED_RANGE = CUTTER_MAX_SPEED - CUTTER_MIN_SPEED
CUTTER_RAMPING_RANGE = CUTTER_SPEED_RANGE * 2
CUTTER_TRAVEL_AMOUNT = 3_500
CUTTER_ADJ_TRAVEL_AMOUNT = 100
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Elevator Stepper Motor
ELEVATOR_STEP = 14 'I/O Pin 'Step Pin
ELEVATOR_DIRECTION = 15 'I/O Pin 'Direction Pin
ELEVATOR_CYCLE_OFFSET = 8_000
ELEVATOR_MIN_SPEED = 8_000
ELEVATOR_MAX_SPEED = 11_000
ELEVATOR_SPEED_RANGE = ELEVATOR_MAX_SPEED - ELEVATOR_MIN_SPEED
ELEVATOR_RAMPING_RANGE = ELEVATOR_SPEED_RANGE * 2
ELEVATOR_TRAVEL_AMOUNT = 2_000
ELEVATOR_ADJ_TRAVEL_AMOUNT = 50
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Spring Remover Stepper Motor
REMOVER_STEP = 16 'I/O Pin 'Step Pin
REMOVER_DIRECTION = 17 'I/O Pin 'Direction Pin
REMOVER_CYCLE_OFFSET = 16_000
REMOVER_MIN_SPEED = 8_000
REMOVER_MAX_SPEED = 11_000
REMOVER_SPEED_RANGE = REMOVER_MAX_SPEED - REMOVER_MIN_SPEED
REMOVER_RAMPING_RANGE = REMOVER_SPEED_RANGE * 2
REMOVER_TRAVEL_EXTENT = 10_400
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Packaging Table Stepper Motor
TABLE_STEP = 18 'I/O Pin 'Step Pin
TABLE_DIRECTION = 19 'I/O Pin 'Direction Pin
TABLE_CYCLE_OFFSET = 200_000
TABLE_MIN_SPEED = 8_000
TABLE_MAX_SPEED = 11_000
TABLE_SPEED_RANGE = TABLE_MAX_SPEED - TABLE_MIN_SPEED
TABLE_RAMPING_RANGE = TABLE_SPEED_RANGE * 2
TABLE_TRAVEL_AMOUNT = 1_000
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
And here is a portion of code showing its use:
PUB MakeSprings
OUTA[REMOVER_DIRECTION]~~
PulseTheStepPin(SolenoidPlacementPosition, REMOVER_STEP, REMOVER_RAMPING_RANGE, REMOVER_SPEED_RANGE, REMOVER_CYCLE_OFFSET)
OUTA[REMOVER_DIRECTION]~
'Reposition Bender Head For CCW Bend
OUTA[BENDER_DIRECTION]~~
AlignBender(CCW_BEND_PHOTO_SENSOR)
REPEAT ProductionQuantity
'Feed Wire
PulseTheStepPin(FirstWireFeedAmount, FEEDER_STEP, FEEDER_RAMPING_RANGE, FEEDER_SPEED_RANGE, FEEDER_CYCLE_OFFSET)
'Raise Elevator
OUTA[ELEVATOR_DIRECTION]~~
PulseTheStepPin(ELEVATOR_TRAVEL_AMOUNT, ELEVATOR_STEP, ELEVATOR_RAMPING_RANGE, ELEVATOR_SPEED_RANGE, ELEVATOR_CYCLE_OFFSET)
'Bend Wire CCW - First Bend
OUTA[BENDER_DIRECTION]~
PulseTheStepPin(FIRST_BENDING_AMOUNT, BENDER_STEP, BENDER_RAMPING_RANGE, BENDER_SPEED_RANGE, BENDER_CYCLE_OFFSET)
WAITCNT(100_000 + cnt)
'Reposition Bender Head For CCW Bend
OUTA[BENDER_DIRECTION]~~
AlignBender(CCW_BEND_PHOTO_SENSOR)
'Feed Wire
PulseTheStepPin(SecondWireFeedAmount, FEEDER_STEP, FEEDER_RAMPING_RANGE, FEEDER_SPEED_RANGE, FEEDER_CYCLE_OFFSET)
WAITCNT(100_000 + cnt)
'Bend Wire CCW - Second Bend
OUTA[BENDER_DIRECTION]~
PulseTheStepPin(SECOND_BENDING_AMOUNT, BENDER_STEP, BENDER_RAMPING_RANGE, BENDER_SPEED_RANGE, BENDER_CYCLE_OFFSET)
WAITCNT(100_000 + cnt)
'Reposition Bender Head For CCW Bend
OUTA[BENDER_DIRECTION]~~
AlignBender(CCW_BEND_PHOTO_SENSOR)
'Feed Wire
PulseTheStepPin(ThirdWireFeedAmount, FEEDER_STEP, FEEDER_RAMPING_RANGE, FEEDER_SPEED_RANGE, FEEDER_CYCLE_OFFSET)
'LowerElevator
OUTA[ELEVATOR_DIRECTION]~
PulseTheStepPin(ELEVATOR_TRAVEL_AMOUNT, ELEVATOR_STEP, ELEVATOR_RAMPING_RANGE, ELEVATOR_SPEED_RANGE, ELEVATOR_CYCLE_OFFSET)
WAITCNT(100_000 + cnt)
'Raise Elevator
OUTA[ELEVATOR_DIRECTION]~~
PulseTheStepPin(ELEVATOR_TRAVEL_AMOUNT, ELEVATOR_STEP, ELEVATOR_RAMPING_RANGE, ELEVATOR_SPEED_RANGE, ELEVATOR_CYCLE_OFFSET)
'Bend Wire CCW - Third Bend
OUTA[BENDER_DIRECTION]~
PulseTheStepPin(NINETY_DEGREE_BEND, BENDER_STEP, BENDER_RAMPING_RANGE, BENDER_SPEED_RANGE, BENDER_CYCLE_OFFSET)
WAITCNT(100_000 + cnt)
'Reposition Bender Head For CCW Bend
OUTA[BENDER_DIRECTION]~~
AlignBender(CCW_BEND_PHOTO_SENSOR)
Please notice that the output of the direction pin which is sent to the stepper driver is set prior to the use of the function. Some of you may want to alter this and add it as a parameter of the function, I did not.
So where do we go from here. To be perfectly honest, I still have the need for more speed, which is one of the reasons that this rewrite is still a work in progress. In my attempts to gain more speed this is what I have learned. By altering a portion of this code, I have found that the motors start gaining speed much quicker, which of course results in faster excution. Here is the portion showing the altered code:
REPEAT RampingSteps
PHSA := -HIGH_PULSE_WIDTH
WAITCNT(Counter += CycleOffset -= 4)
'WAITCNT(Counter += CycleOffset--)
REPEAT RunningSteps
PHSA := -HIGH_PULSE_WIDTH
WAITCNT(Counter += CycleOffset)
REPEAT RampingSteps
PHSA := -HIGH_PULSE_WIDTH
WAITCNT(Counter += CycleOffset += 4)
'WAITCNT(Counter += CycleOffset++)
With this alteration, 7 out of 8 of my motors ran flawlessly with more speed, the 8TH motor failed miserably. I would attribute this to something that Dave Hein tried to point out to me, and he said:
And he also said:Bruce, what is the value of MIN_SPEED? You initialize CounterOffset to MIN_SPEED, but you decrement CounterOffset every time it loops. If it gets too small the target cycle count for waitcnt will be less than the value of CNT, which means that waitcnt will wait for 4 billion cycles instead of just a few hundred or thousand cycles. You should limit CounterOffset to a minimum value that will prevent this.
EDIT: I ran your loop under SpinSim, and it takes about 2500 cycles per loop. So CounterOffset should not be allowed to get below this. A minimum value of 3000 should be safe to use.
Okay so now there is some food for thought. I want to gain more speed by adding another parameter to this function, but I also want to limit the possibility of failure. So I am looking for guidance and some rules to follow when setting the value of this parameter. Here is what the proposed new function will look like:Bruce, you are initializing CycleOffset to MIN_SPEED, and then decrementing CycleOffset, which is fine. If you start at 8000 you will reach 2,500 after 5,500 loops. That should take about one-third second at 80MHz. If the input bit changes in less than one-third second it will work fine. If it takes longer than one-third second there will be a problem. As I said, you can resolve that issue if you limit the minimum value of CycleOffset to 3000, or maybe 3,500 to account for the additional instructions in the loop.
PUB PulseTheStepPin(TotalSteps, StepPin, RampingRange, SpeedRange, CycleOffset, IncrementorDecrementor) | RampingSteps, RunningSteps, Counter
IF TotalSteps > RampingRange
RampingSteps := SpeedRange
RunningSteps := TotalSteps - RampingRange
ELSE
RampingSteps := TotalSteps / 2
RunningSteps := TotalSteps // 2
CTRA[30..26] := %00100
CTRA[5..0] := StepPin
FRQA := 1
DIRA[StepPin]~~
Counter := CNT
REPEAT RampingSteps
PHSA := -HIGH_PULSE_WIDTH
WAITCNT(Counter += CycleOffset -= IncrementorDecrementor)
REPEAT RunningSteps
PHSA := -HIGH_PULSE_WIDTH
WAITCNT(Counter += CycleOffset)
REPEAT RampingSteps
PHSA := -HIGH_PULSE_WIDTH
WAITCNT(Counter += CycleOffset += IncrementorDecrementor)
Alright there you have it, new function, example of usage, and some proposed changes.Bruce
Comments
Bruce
First off, add this as one of your constants
MIN_EXECUTION_TIME = CLK_FREQ / 16_000 'Look at previous code for obtaining CLK_FREQ
And then try this function as previously described
PUB PulseTheStepPin(TotalSteps, StepPin, RampingRange, SpeedRange, CycleOffset) | RampingSteps, RunningSteps, CompensationSteps, Counter IF TotalSteps > RampingRange RampingSteps := SpeedRange RunningSteps := TotalSteps - RampingRange ELSE RampingSteps := TotalSteps / 2 RunningSteps := TotalSteps // 2 CTRA[30..26] := %00100 CTRA[5..0] := StepPin FRQA := 1 DIRA[StepPin]~~ Counter := CNT CompensationSteps := 0 REPEAT RampingSteps PHSA := -HIGH_PULSE_WIDTH IF CycleOffset => MIN_EXECUTION_TIME WAITCNT(Counter += CycleOffset -= 4) ELSE WAITCNT(Counter += CycleOffset) CompensationSteps++ RunningSteps += CompensationSteps REPEAT RunningSteps PHSA := -HIGH_PULSE_WIDTH WAITCNT(Counter += CycleOffset) RampingSteps -= CompensationSteps REPEAT RampingSteps PHSA := -HIGH_PULSE_WIDTH WAITCNT(Counter += CycleOffset += 4)
Two more changes
First
MIN_EXECUTION_TIME = CLK_FREQ / 20_000 'CON definition
Second
WAITCNT(Counter += CycleOffset -= 20) 'alter in PulseTheStepPin
MIN_EXECUTION_TIME = CLK_FREQ / 21_500
EDIT: On second thought, hold off with this setting, for now.
Two things worth mentioning:
- The sweet spot for the minimum execution time is MIN_EXECUTION_TIME = CLK_FREQ / 21_500
- The RampSpeedAdjuster parameter should not be a setting of higher than 25, at least for my drivers. So anything between 0 and 25 should be fine.
I have not performed a speed test on the new version, but I can state unequivocally that it is much faster than the original function. With the information provided in this post, you should be able to run your stepper drivers at a very decent speed. Also with all the different possible settings, you should be able to fine tune each stepper motor. I hope you enjoy it.Bruce
PUB PulseTheStepPin(TotalSteps, StepPin, RampingRange, SpeedRange, CycleOffset, RampSpeedAdjuster) | RampingSteps, RunningSteps, CompensationSteps, Counter IF TotalSteps > RampingRange RampingSteps := SpeedRange RunningSteps := TotalSteps - RampingRange ELSE RampingSteps := TotalSteps / 2 RunningSteps := TotalSteps // 2 CTRA[30..26] := %00100 CTRA[5..0] := StepPin FRQA := 1 DIRA[StepPin]~~ Counter := CNT CompensationSteps := 0 REPEAT RampingSteps PHSA := -HIGH_PULSE_WIDTH IF CycleOffset => MIN_EXECUTION_TIME WAITCNT(Counter += CycleOffset -= RampSpeedAdjuster) ELSE WAITCNT(Counter += CycleOffset) CompensationSteps++ REPEAT RunningSteps += CompensationSteps PHSA := -HIGH_PULSE_WIDTH WAITCNT(Counter += CycleOffset) REPEAT RampingSteps -= CompensationSteps PHSA := -HIGH_PULSE_WIDTH WAITCNT(Counter += CycleOffset += RampSpeedAdjuster)
I wish someone would rewrite this so that it made more sense.
Bruce