Skip to content

Commit 4e030ea

Browse files
authored
Merge pull request #44 from caternuson/digi_step
Add digitalio for steppers
2 parents 434e5b5 + 82d28d1 commit 4e030ea

File tree

2 files changed

+183
-65
lines changed

2 files changed

+183
-65
lines changed

adafruit_motor/stepper.py

100644100755
+135-65
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,21 @@
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

6169
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**
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

examples/motor_servo_digitalio.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Use this example for digital pin control of an H-bridge driver
2+
# like a TB6612 or L298N.
3+
4+
import time
5+
import board
6+
import digitalio
7+
from adafruit_motor import stepper
8+
9+
DELAY = 0.01
10+
STEPS = 200
11+
12+
coils = (
13+
digitalio.DigitalInOut(board.D19), # A1
14+
digitalio.DigitalInOut(board.D26), # A2
15+
digitalio.DigitalInOut(board.D20), # B1
16+
digitalio.DigitalInOut(board.D21), # B2
17+
)
18+
19+
for coil in coils:
20+
coil.direction = digitalio.Direction.OUTPUT
21+
22+
motor = stepper.StepperMotor(coils[0], coils[1], coils[2], coils[3], microsteps=None)
23+
24+
for step in range(STEPS):
25+
motor.onestep()
26+
time.sleep(DELAY)
27+
28+
for step in range(STEPS):
29+
motor.onestep(direction=stepper.BACKWARD)
30+
time.sleep(DELAY)
31+
32+
for step in range(STEPS):
33+
motor.onestep(style=stepper.DOUBLE)
34+
time.sleep(DELAY)
35+
36+
for step in range(STEPS):
37+
motor.onestep(direction=stepper.BACKWARD, style=stepper.DOUBLE)
38+
time.sleep(DELAY)
39+
40+
for step in range(STEPS):
41+
motor.onestep(style=stepper.INTERLEAVE)
42+
time.sleep(DELAY)
43+
44+
for step in range(STEPS):
45+
motor.onestep(direction=stepper.BACKWARD, style=stepper.INTERLEAVE)
46+
time.sleep(DELAY)
47+
48+
motor.release()

0 commit comments

Comments
 (0)