57
57
"""Step a fraction of a step by partially activating two neighboring coils. Step size is determined
58
58
by ``microsteps`` constructor argument."""
59
59
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
+
60
68
61
69
class 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**
63
75
64
76
:param ~pulseio.PWMOut ain1: `pulseio.PWMOut`-compatible output connected to the driver for
65
77
the first coil (unipolar) or first input to first coil (bipolar).
@@ -70,56 +82,97 @@ class StepperMotor:
70
82
:param ~pulseio.PWMOut bin2: `pulseio.PWMOut`-compatible output connected to the driver for
71
83
the fourth coil (unipolar) or second input to second coil (bipolar).
72
84
: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`
73
97
"""
74
98
75
99
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
+ ]
83
123
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" )
88
124
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
- ]
93
125
self ._update_coils ()
94
126
95
127
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 ]
115
163
116
164
def release (self ):
117
165
"""Releases all the coils so the motor can free spin, also won't use any power"""
118
166
# 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
123
176
"""Performs one step of a particular style. The actual rotation amount will vary by style.
124
177
`SINGLE` and `DOUBLE` will normal cause a full step rotation. `INTERLEAVE` will normally
125
178
do a half step rotation. `MICROSTEP` will perform the smallest configured step.
@@ -129,34 +182,51 @@ def onestep(self, *, direction=FORWARD, style=SINGLE):
129
182
130
183
:param int direction: Either `FORWARD` or `BACKWARD`
131
184
: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
+ #
135
189
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
150
194
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
160
230
161
231
if direction == FORWARD :
162
232
self ._current_microstep += step_size
0 commit comments