Skip to content
This repository was archived by the owner on Dec 20, 2024. It is now read-only.

Commit 12346e4

Browse files
committed
adding stepper motor
1 parent 1f839dd commit 12346e4

File tree

11 files changed

+294
-10
lines changed

11 files changed

+294
-10
lines changed

README.rst

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ This library can work with other MCU (Not tested) that used the following PWM me
3232
* freq
3333
* duty_u16
3434

35-
Sadly do not have the hardware to test the step motor part of the library so, this will
36-
be an exercise for the reader.:)
37-
3835

3936
Installing with mip
4037
====================

docs/api.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,9 @@ MicroPython MOTOR Library
77

88
.. automodule:: micropython_motor.motor
99
:members:
10+
11+
.. automodule:: micropython_motor.servo
12+
:members:
13+
14+
.. automodule:: micropython_motor.stepper
15+
:members:

docs/conf.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,6 @@
182182
"override": True,
183183
},
184184
]
185-
python_type_aliases = {
186-
"DigitalInOut": "digitalio.DigitalInOut",
187-
}
188185

189186
object_description_options = [
190187
("py:.*", dict(generate_synopses="first_sentence")),

docs/examples.rst

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,26 @@
1-
Simple test
1+
Motor test
22
------------
33

4-
Ensure your device works with this simple test.
4+
DC motor simple test
55

66
.. literalinclude:: ../examples/motor_simpletest.py
77
:caption: examples/motor_simpletest.py
8-
:lines: 5-
8+
:lines: 10-
9+
10+
Servo test
11+
------------
12+
13+
Servo Simple Test
14+
15+
.. literalinclude:: ../examples/servo_simpletest.py
16+
:caption: examples/servo_simpletest.py
17+
:lines: 6-
18+
19+
Step Motor test
20+
--------------------
21+
22+
Stepper Motor Test
23+
24+
.. literalinclude:: ../examples/stepper_simpletest.py
25+
:caption: examples/stepper_simpletest.py
26+
:lines: 13-

examples.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
[
88
"micropython_motor/examples/motor_simpletest.py",
99
"github:jposada202020/MicroPython_MOTOR/examples/motor_simpletest.py"
10+
],
11+
[
12+
"micropython_motor/examples/stepper_simpletest.py",
13+
"github:jposada202020/MicroPython_MOTOR/examples/stepper_simpletest.py"
1014
]
1115
],
1216
"version": "1"

examples/stepper_simpletest.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# SPDX-FileCopyrightText: 2021 jedgarpark for Adafruit Industries
2+
# SPDX-FileCopyrightText: Copyright (c) 2023 Jose D. Montoya
3+
#
4+
# SPDX-License-Identifier: MIT
5+
6+
"""Example adapted from https://learn.adafruit.com/use-dc-stepper-servo-motor-solenoid-rp2040-pico/stepper-motor"""
7+
8+
# Hardware setup:
9+
# Stepper motor via DRV8833 driver breakout on GP21, GP20, GP19, GP18
10+
# external power supply
11+
# DRV8833 Enabled on GP16
12+
13+
import time
14+
from machine import PWM, Pin
15+
from micropython_motor import stepper
16+
17+
print("Stepper test")
18+
19+
20+
mode = -1
21+
22+
# Mode button setup
23+
drv_switch = Pin(16, Pin.OUT)
24+
drv_switch.on()
25+
26+
# Stepper motor setup
27+
DELAY = 0.006 # fastest is ~ 0.004, 0.01 is still very smooth, gets steppy after that
28+
STEPS = 513 # this is a full 360º
29+
coils = (Pin(21, Pin.OUT), Pin(20, Pin.OUT), Pin(19, Pin.OUT), Pin(18, Pin.OUT))
30+
31+
32+
stepper_motor = stepper.StepperMotor(
33+
coils[0], coils[1], coils[2], coils[3], microsteps=None
34+
)
35+
36+
37+
def stepper_fwd():
38+
print("stepper forward")
39+
for _ in range(STEPS):
40+
stepper_motor.onestep(direction=stepper.FORWARD)
41+
time.sleep(DELAY)
42+
stepper_motor.release()
43+
44+
45+
def stepper_back():
46+
print("stepper backward")
47+
for _ in range(STEPS):
48+
stepper_motor.onestep(direction=stepper.BACKWARD)
49+
time.sleep(DELAY)
50+
stepper_motor.release()
51+
52+
53+
def run_test(testnum):
54+
if testnum is 0:
55+
stepper_fwd()
56+
elif testnum is 1:
57+
stepper_back()
58+
59+
60+
while True:
61+
mode = (mode + 1) % 2
62+
print("switch to mode %d" % (mode))
63+
print()
64+
time.sleep(0.8) # big debounce
65+
run_test(mode)

micropython_motor/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44

55
from .servo import Servo
66
from .motor import MOTOR
7+
from .stepper import StepperMotor

micropython_motor/servo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44
# SPDX-License-Identifier: MIT
55
"""
6-
`motor`
6+
`servo`
77
================================================================================
88
99
MicroPython Helper for controlling PWM based servos

micropython_motor/stepper.py

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries
2+
# SPDX-FileCopyrightText: Copyright (c) 2023 Jose D. Montoya
3+
#
4+
# SPDX-License-Identifier: MIT
5+
6+
"""
7+
`stepper`
8+
====================================================
9+
10+
MicroPython Helper for controlling stepper motors with microstepping support.
11+
12+
13+
* Author(s): Tony DiCola, Scott Shawcroft, Jose D. Montoya
14+
"""
15+
16+
import math
17+
from micropython import const
18+
19+
20+
# Constants that specify the direction and style of steps.
21+
FORWARD = const(1)
22+
"""Step forward"""
23+
BACKWARD = const(2)
24+
""""Step backward"""
25+
SINGLE = const(1)
26+
"""Step so that each step only activates a single coil"""
27+
DOUBLE = const(2)
28+
"""Step so that each step only activates two coils to produce more torque."""
29+
INTERLEAVE = const(3)
30+
"""Step half a step to alternate between single coil and double coil steps."""
31+
MICROSTEP = const(4)
32+
"""Step a fraction of a step by partially activating two neighboring coils. Step size is determined
33+
by ``microsteps`` constructor argument."""
34+
35+
_SINGLE_STEPS = bytes([0b0010, 0b0100, 0b0001, 0b1000])
36+
37+
_DOUBLE_STEPS = bytes([0b1010, 0b0110, 0b0101, 0b1001])
38+
39+
_INTERLEAVE_STEPS = bytes(
40+
[0b1010, 0b0010, 0b0110, 0b0100, 0b0101, 0b0001, 0b1001, 0b1000]
41+
)
42+
43+
44+
class StepperMotor:
45+
"""A bipolar stepper motor or four coil unipolar motor. The use of microstepping requires
46+
pins that can output PWM. For non-microstepping, can set microsteps to None and use
47+
digital out pins.
48+
49+
50+
:param ain1: `machine.PWM` or `machine.Pin`-compatible output connected to the driver for
51+
the first coil (unipolar) or first input to first coil (bipolar).
52+
:param ain2: `machine.PWM` or `machine.Pin`-compatible output connected to the driver for
53+
the third coil (unipolar) or second input to first coil (bipolar).
54+
:param bin1: `machine.PWM` or `machine.Pin`-compatible output connected to the driver for
55+
the second coil (unipolar) or second input to second coil (bipolar).
56+
:param bin2: `machine.PWM` or `machine.Pin`-compatible output connected to the driver for
57+
the fourth coil (unipolar) or second input to second coil (bipolar).
58+
:param int microsteps: Number of microsteps between full steps. Must be at least 2 and even.
59+
60+
"""
61+
62+
def __init__(self, ain1, ain2, bin1, bin2, *, microsteps=16) -> None:
63+
if microsteps is None:
64+
# Digital IO Pins
65+
self._steps = None
66+
self._coil = (ain1, ain2, bin1, bin2)
67+
else:
68+
# PWM Pins set a safe pwm freq for each output
69+
self._coil = (ain2, bin1, ain1, bin2)
70+
for i in range(4):
71+
if self._coil[i].freq() < 1500:
72+
try:
73+
self._coil[i].frequency = 2000
74+
except AttributeError as err:
75+
raise ValueError(
76+
"PWMOut outputs must either be set to at least "
77+
"1500 Hz or allow variable frequency."
78+
) from err
79+
if microsteps < 2:
80+
raise ValueError("Microsteps must be at least 2")
81+
if microsteps % 2 == 1:
82+
raise ValueError("Microsteps must be even")
83+
self._curve = [
84+
int(round(0xFFFF * math.sin(math.pi / (2 * microsteps) * i)))
85+
for i in range(microsteps + 1)
86+
]
87+
self._current_microstep = 0
88+
self._microsteps = microsteps
89+
self._update_coils()
90+
91+
def _update_coils(self, *, microstepping: bool = False) -> None:
92+
if self._microsteps is None:
93+
# Digital IO Pins Get coil activation sequence
94+
if self._steps is None:
95+
steps = 0b0000
96+
else:
97+
steps = self._steps[self._current_microstep % len(self._steps)]
98+
# Energize coils as appropriate:
99+
for i, coil in enumerate(self._coil):
100+
coil.value((steps >> i) & 0x01)
101+
else:
102+
# PWM Pins
103+
duty_cycles = [0, 0, 0, 0]
104+
trailing_coil = (self._current_microstep // self._microsteps) % 4
105+
leading_coil = (trailing_coil + 1) % 4
106+
microstep = self._current_microstep % self._microsteps
107+
duty_cycles[leading_coil] = self._curve[microstep]
108+
duty_cycles[trailing_coil] = self._curve[self._microsteps - microstep]
109+
110+
# This ensures DOUBLE steps use full torque. Without it, we'd use
111+
# partial torque from the microstepping curve (0xb504).
112+
if not microstepping and (
113+
duty_cycles[leading_coil] == duty_cycles[trailing_coil]
114+
and duty_cycles[leading_coil] > 0
115+
):
116+
duty_cycles[leading_coil] = 0xFFFF
117+
duty_cycles[trailing_coil] = 0xFFFF
118+
119+
# Energize coils as appropriate:
120+
for i in range(4):
121+
self._coil[i].duty_u16(duty_cycles[i])
122+
123+
def release(self) -> None:
124+
"""Releases all the coils so the motor can free spin, also won't use any power"""
125+
# De-energize coils:
126+
for coil in self._coil:
127+
if self._microsteps is None:
128+
coil.value(0)
129+
else:
130+
coil.duty_u16(0)
131+
132+
def onestep(self, *, direction: int = FORWARD, style: int = SINGLE) -> None:
133+
"""Performs one step of a particular style. The actual rotation amount will vary by style.
134+
`SINGLE` and `DOUBLE` will normal cause a full step rotation. `INTERLEAVE` will normally
135+
do a half step rotation. `MICROSTEP` will perform the smallest configured step.
136+
137+
When step styles are mixed, subsequent `SINGLE`, `DOUBLE` or `INTERLEAVE` steps may be
138+
less than normal in order to align to the desired style's pattern.
139+
140+
:param int direction: Either `FORWARD` or `BACKWARD`
141+
:param int style: `SINGLE`, `DOUBLE`, `INTERLEAVE`"""
142+
if self._microsteps is None:
143+
# Digital IO Pins
144+
step_size = 1
145+
if style == SINGLE:
146+
self._steps = _SINGLE_STEPS
147+
elif style == DOUBLE:
148+
self._steps = _DOUBLE_STEPS
149+
elif style == INTERLEAVE:
150+
self._steps = _INTERLEAVE_STEPS
151+
else:
152+
raise ValueError("Unsupported step style.")
153+
else:
154+
# PWM Pins Adjust current steps based on the direction and type of step.
155+
step_size = 0
156+
if style == MICROSTEP:
157+
step_size = 1
158+
else:
159+
half_step = self._microsteps // 2
160+
full_step = self._microsteps
161+
# Its possible the previous steps were MICROSTEPS so first align
162+
# with the interleave pattern.
163+
additional_microsteps = self._current_microstep % half_step
164+
if additional_microsteps != 0:
165+
# We set _current_microstep directly because our step size varies
166+
# depending on the direction.
167+
if direction == FORWARD:
168+
self._current_microstep += half_step - additional_microsteps
169+
else:
170+
self._current_microstep -= additional_microsteps
171+
step_size = 0
172+
elif style == INTERLEAVE:
173+
step_size = half_step
174+
175+
current_interleave = self._current_microstep // half_step
176+
if (style == SINGLE and current_interleave % 2 == 1) or (
177+
style == DOUBLE and current_interleave % 2 == 0
178+
):
179+
step_size = half_step
180+
elif style in (SINGLE, DOUBLE):
181+
step_size = full_step
182+
183+
if direction == FORWARD:
184+
self._current_microstep += step_size
185+
else:
186+
self._current_microstep -= step_size
187+
188+
# Now that we know our target microstep we can determine how to energize the four coils.
189+
self._update_coils(microstepping=style == MICROSTEP)
190+
191+
return self._current_microstep

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
[
1212
"micropython_motor/servo.py",
1313
"github:jposada202020/MicroPython_MOTOR/micropython_motor/servo.py"
14+
],
15+
[
16+
"micropython_motor/stepper.py",
17+
"github:jposada202020/MicroPython_MOTOR/micropython_motor/stepper.py"
1418
]
1519
],
1620
"version": "1"

0 commit comments

Comments
 (0)