5757"""Step a fraction of a step by partially activating two neighboring coils. Step size is determined
5858 by ``microsteps`` constructor argument."""
5959
60+ _SINGLE_STEPS = bytes ([0b0010 , 0b0100 , 0b0001 , 0b1000 ])
61+
62+ _DOUBLE_STEPS = bytes ([0b1010 , 0b0110 , 0b0101 , 0b1001 ])
63+
64+ _INTERLEAVE_STEPS = bytes (
65+ [0b1010 , 0b0010 , 0b0110 , 0b0100 , 0b0101 , 0b0001 , 0b1001 , 0b1000 ]
66+ )
67+
6068
6169class StepperMotor :
62- """A bipolar stepper motor or four coil unipolar motor.
70+ """A bipolar stepper motor or four coil unipolar motor. The use of microstepping requires
71+ pins that can output PWM. For non-microstepping, can set microsteps to None and use
72+ digital out pins.
73+
74+ **PWM**
6375
6476 :param ~pulseio.PWMOut ain1: `pulseio.PWMOut`-compatible output connected to the driver for
6577 the first coil (unipolar) or first input to first coil (bipolar).
@@ -70,56 +82,97 @@ class StepperMotor:
7082 :param ~pulseio.PWMOut bin2: `pulseio.PWMOut`-compatible output connected to the driver for
7183 the fourth coil (unipolar) or second input to second coil (bipolar).
7284 :param int microsteps: Number of microsteps between full steps. Must be at least 2 and even.
85+
86+ **Digital Out**
87+
88+ :param ~digitalio.DigitalInOut ain1: `digitalio.DigitalInOut`-compatible output connected to
89+ the driver for the first coil (unipolar) or first input to first coil (bipolar).
90+ :param ~digitalio.DigitalInOut ain2: `digitalio.DigitalInOut`-compatible output connected to
91+ the driver for the third coil (unipolar) or second input to first coil (bipolar).
92+ :param ~digitalio.DigitalInOut bin1: `digitalio.DigitalInOut`-compatible output connected to
93+ the driver for the second coil (unipolar) or first input to second coil (bipolar).
94+ :param ~digitalio.DigitalInOut bin2: `digitalio.DigitalInOut`-compatible output connected to
95+ the driver for the fourth coil (unipolar) or second input to second coil (bipolar).
96+ :param microsteps: set to `None`
7397 """
7498
7599 def __init__ (self , ain1 , ain2 , bin1 , bin2 , * , microsteps = 16 ):
76- self ._coil = (ain2 , bin1 , ain1 , bin2 )
77-
78- # set a safe pwm freq for each output
79- for i in range (4 ):
80- if self ._coil [i ].frequency < 1500 :
81- self ._coil [i ].frequency = 2000
82-
100+ if microsteps is None :
101+ #
102+ # Digital IO Pins
103+ #
104+ self ._steps = None
105+ self ._coil = (ain1 , ain2 , bin1 , bin2 )
106+ else :
107+ #
108+ # PWM Pins
109+ #
110+ # set a safe pwm freq for each output
111+ self ._coil = (ain2 , bin1 , ain1 , bin2 )
112+ for i in range (4 ):
113+ if self ._coil [i ].frequency < 1500 :
114+ self ._coil [i ].frequency = 2000
115+ if microsteps < 2 :
116+ raise ValueError ("Microsteps must be at least 2" )
117+ if microsteps % 2 == 1 :
118+ raise ValueError ("Microsteps must be even" )
119+ self ._curve = [
120+ int (round (0xFFFF * math .sin (math .pi / (2 * microsteps ) * i )))
121+ for i in range (microsteps + 1 )
122+ ]
83123 self ._current_microstep = 0
84- if microsteps < 2 :
85- raise ValueError ("Microsteps must be at least 2" )
86- if microsteps % 2 == 1 :
87- raise ValueError ("Microsteps must be even" )
88124 self ._microsteps = microsteps
89- self ._curve = [
90- int (round (0xFFFF * math .sin (math .pi / (2 * microsteps ) * i )))
91- for i in range (microsteps + 1 )
92- ]
93125 self ._update_coils ()
94126
95127 def _update_coils (self , * , microstepping = False ):
96- duty_cycles = [0 , 0 , 0 , 0 ]
97- trailing_coil = (self ._current_microstep // self ._microsteps ) % 4
98- leading_coil = (trailing_coil + 1 ) % 4
99- microstep = self ._current_microstep % self ._microsteps
100- duty_cycles [leading_coil ] = self ._curve [microstep ]
101- duty_cycles [trailing_coil ] = self ._curve [self ._microsteps - microstep ]
102-
103- # This ensures DOUBLE steps use full torque. Without it, we'd use partial torque from the
104- # microstepping curve (0xb504).
105- if not microstepping and (
106- duty_cycles [leading_coil ] == duty_cycles [trailing_coil ]
107- and duty_cycles [leading_coil ] > 0
108- ):
109- duty_cycles [leading_coil ] = 0xFFFF
110- duty_cycles [trailing_coil ] = 0xFFFF
111-
112- # Energize coils as appropriate:
113- for i in range (4 ):
114- self ._coil [i ].duty_cycle = duty_cycles [i ]
128+ if self ._microsteps is None :
129+ #
130+ # Digital IO Pins
131+ #
132+ # Get coil activation sequence
133+ if self ._steps is None :
134+ steps = 0b0000
135+ else :
136+ steps = self ._steps [self ._current_microstep % len (self ._steps )]
137+ # Energize coils as appropriate:
138+ for i , coil in enumerate (self ._coil ):
139+ coil .value = (steps >> i ) & 0x01
140+ else :
141+ #
142+ # PWM Pins
143+ #
144+ duty_cycles = [0 , 0 , 0 , 0 ]
145+ trailing_coil = (self ._current_microstep // self ._microsteps ) % 4
146+ leading_coil = (trailing_coil + 1 ) % 4
147+ microstep = self ._current_microstep % self ._microsteps
148+ duty_cycles [leading_coil ] = self ._curve [microstep ]
149+ duty_cycles [trailing_coil ] = self ._curve [self ._microsteps - microstep ]
150+
151+ # This ensures DOUBLE steps use full torque. Without it, we'd use
152+ # partial torque from the microstepping curve (0xb504).
153+ if not microstepping and (
154+ duty_cycles [leading_coil ] == duty_cycles [trailing_coil ]
155+ and duty_cycles [leading_coil ] > 0
156+ ):
157+ duty_cycles [leading_coil ] = 0xFFFF
158+ duty_cycles [trailing_coil ] = 0xFFFF
159+
160+ # Energize coils as appropriate:
161+ for i in range (4 ):
162+ self ._coil [i ].duty_cycle = duty_cycles [i ]
115163
116164 def release (self ):
117165 """Releases all the coils so the motor can free spin, also won't use any power"""
118166 # De-energize coils:
119- for i in range (4 ):
120- self ._coil [i ].duty_cycle = 0
121-
122- def onestep (self , * , direction = FORWARD , style = SINGLE ):
167+ for coil in self ._coil :
168+ if self ._microsteps is None :
169+ coil .value = 0
170+ else :
171+ coil .duty_cycle = 0
172+
173+ def onestep (
174+ self , * , direction = FORWARD , style = SINGLE
175+ ): # pylint: disable=too-many-branches
123176 """Performs one step of a particular style. The actual rotation amount will vary by style.
124177 `SINGLE` and `DOUBLE` will normal cause a full step rotation. `INTERLEAVE` will normally
125178 do a half step rotation. `MICROSTEP` will perform the smallest configured step.
@@ -129,34 +182,51 @@ def onestep(self, *, direction=FORWARD, style=SINGLE):
129182
130183 :param int direction: Either `FORWARD` or `BACKWARD`
131184 :param int style: `SINGLE`, `DOUBLE`, `INTERLEAVE`"""
132- # Adjust current steps based on the direction and type of step.
133- step_size = 0
134- if style == MICROSTEP :
185+ if self ._microsteps is None :
186+ #
187+ # Digital IO Pins
188+ #
135189 step_size = 1
136- else :
137- half_step = self ._microsteps // 2
138- full_step = self ._microsteps
139- # Its possible the previous steps were MICROSTEPS so first align with the interleave
140- # pattern.
141- additional_microsteps = self ._current_microstep % half_step
142- if additional_microsteps != 0 :
143- # We set _current_microstep directly because our step size varies depending on the
144- # direction.
145- if direction == FORWARD :
146- self ._current_microstep += half_step - additional_microsteps
147- else :
148- self ._current_microstep -= additional_microsteps
149- step_size = 0
190+ if style == SINGLE :
191+ self ._steps = _SINGLE_STEPS
192+ elif style == DOUBLE :
193+ self ._steps = _DOUBLE_STEPS
150194 elif style == INTERLEAVE :
151- step_size = half_step
152-
153- current_interleave = self ._current_microstep // half_step
154- if (style == SINGLE and current_interleave % 2 == 1 ) or (
155- style == DOUBLE and current_interleave % 2 == 0
156- ):
157- step_size = half_step
158- elif style in (SINGLE , DOUBLE ):
159- step_size = full_step
195+ self ._steps = _INTERLEAVE_STEPS
196+ else :
197+ raise ValueError ("Unsupported step style." )
198+ else :
199+ #
200+ # PWM Pins
201+ #
202+ # Adjust current steps based on the direction and type of step.
203+ step_size = 0
204+ if style == MICROSTEP :
205+ step_size = 1
206+ else :
207+ half_step = self ._microsteps // 2
208+ full_step = self ._microsteps
209+ # Its possible the previous steps were MICROSTEPS so first align
210+ # with the interleave pattern.
211+ additional_microsteps = self ._current_microstep % half_step
212+ if additional_microsteps != 0 :
213+ # We set _current_microstep directly because our step size varies
214+ # depending on the direction.
215+ if direction == FORWARD :
216+ self ._current_microstep += half_step - additional_microsteps
217+ else :
218+ self ._current_microstep -= additional_microsteps
219+ step_size = 0
220+ elif style == INTERLEAVE :
221+ step_size = half_step
222+
223+ current_interleave = self ._current_microstep // half_step
224+ if (style == SINGLE and current_interleave % 2 == 1 ) or (
225+ style == DOUBLE and current_interleave % 2 == 0
226+ ):
227+ step_size = half_step
228+ elif style in (SINGLE , DOUBLE ):
229+ step_size = full_step
160230
161231 if direction == FORWARD :
162232 self ._current_microstep += step_size
0 commit comments