Skip to content

Commit 15220eb

Browse files
committed
PWM-Counter: Add Stepper Motor PWM-Counter driver.
Signed-off-by: Ihor Nehrutsa <IhorNehrutsa@gmail.com>
1 parent 381ac74 commit 15220eb

4 files changed

Lines changed: 510 additions & 0 deletions

File tree

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
## Stepper motor PWM-Counter driver
2+
3+
This MicroPython software driver is designed to control stepper motor using STEP/DIR hardware driver.
4+
5+
![stepper_motor_driver](https://github.com/IhorNehrutsa/micropython-lib/assets/70886343/27933a08-7225-4931-a1ee-8e0042d0b822)
6+
7+
* The driver signal "STEP" (aka "PULSE") is intended for clock pulses. In one pulse, the motor rotor turns one step. The higher the frequency of pulses, the higher the speed of rotation of the rotor.
8+
9+
* The driver signal "DIR" is intended to select the direction of rotation of the engine ("1" - in one direction, "0" - in the other direction).
10+
11+
![forward_reverse](https://github.com/IhorNehrutsa/micropython-lib/assets/70886343/f1986469-6fca-4d10-a6f2-262020a19946)
12+
13+
### Hardware
14+
15+
As an example of a STEP / DIR hardware driver:
16+
17+
* [TMC2209](https://wiki.fysetc.com/Silent2209) module, TB6612,
18+
19+
* [TB6560-V2](https://mypractic.com/stepper-motor-driver-tb6560-v2-description-characteristics-recommendations-for-use) module,
20+
21+
* [TB6600](https://mytectutor.com/tb6600-stepper-motor-driver-with-arduino) based driver,
22+
23+
* DM860H, DM556 etc.
24+
25+
### Software
26+
27+
The main feature of this driver is that the generation and counting of pulses are performed by hardware, which frees up time in the main loop. PWM will start pulses and Counter will stop pulses in irq handler.
28+
29+
The PWM unit creates STEP pulses and sets the motor speed.
30+
31+
The GPIO unit controls the DIR pin, the direction of rotation of the motor.
32+
33+
The Counter unit counts pulses, that is, the actual position of the stepper motor.
34+
35+
![stepper_motor_pwm_counter](https://github.com/IhorNehrutsa/micropython-lib/assets/70886343/4e6cf4b9-b198-4fa6-8bcc-51d873bf74ce)
36+
37+
In general case MicroPython ports need 4 pins: PWM STEP output, GPIO DIR output, Counter STEP input, Counter DIR input (red wires in the image).
38+
39+
The ESP32 port allows to connect Counter inputs to the same outputs inside the MCU(green wires in the picture), so 2 pins are needed.
40+
41+
This driver requires PR:
42+
43+
[ESP32: Add Quadrature Encoder and Pulse Counter classes. #8766](https://github.com/micropython/micropython/pull/8766)
44+
45+
Constructor
46+
-----------
47+
48+
class:: StepperMotorPwmCounter(pin_step, pin_dir, freq, reverse)
49+
50+
Construct and return a new StepperMotorPwmCounter object using the following parameters:
51+
52+
- *pin_step* is the entity on which the PWM is output, which is usually a
53+
:ref:`machine.Pin <machine.Pin>` object, but a port may allow other values, like integers.
54+
- *freq* should be an integer which sets the frequency in Hz for the
55+
PWM cycle i.e. motor speed.
56+
- *reverse* reverse the motor direction if the value is True
57+
58+
Properties
59+
----------
60+
61+
property:: StepperMotorPwmCounter.freq
62+
63+
Get/set the current frequency of the STEP/PWM output.
64+
65+
property:: StepperMotorPwmCounter.steps_counter
66+
67+
Get current steps position form the Counter.
68+
69+
property:: StepperMotorPwmCounter.steps_target
70+
71+
Get/set the steps target.
72+
73+
Methods
74+
-------
75+
76+
method:: StepperMotorPwmCounter.deinit()
77+
78+
Disable the PWM output.
79+
80+
method:: StepperMotorPwmCounter.go()
81+
82+
Call it in the main loop to move the motor to the steps_target position.
83+
84+
method:: StepperMotorPwmCounter.is_ready()
85+
86+
Return True if steps_target is achieved.
87+
88+
Tested on ESP32.
89+
90+
**Simple example is:**
91+
```
92+
# stepper_motor_pwm_counter_test1.py
93+
94+
from time import sleep
95+
96+
from stepper_motor_pwm_counter import StepperMotorPwmCounter
97+
98+
try:
99+
motor = StepperMotorPwmCounter(26, 23, freq=10_000)
100+
print(motor)
101+
102+
motor.steps_target = 8192
103+
while True:
104+
if not motor.is_ready():
105+
motor.go()
106+
print(f'motor.steps_target={motor.steps_target}, motor.steps_counter={motor.steps_counter}, motor.is_ready()={motor.is_ready()}')
107+
else:
108+
print()
109+
print(f'motor.steps_target={motor.steps_target}, motor.steps_counter={motor.steps_counter}, motor.is_ready()={motor.is_ready()}')
110+
print('SET steps_target', -motor.steps_target)
111+
print('sleep(1)')
112+
print()
113+
sleep(1)
114+
motor.steps_target = -motor.steps_target
115+
motor.go()
116+
117+
sleep(0.1)
118+
119+
except Exception as e:
120+
print(e)
121+
raise e
122+
finally:
123+
try:
124+
motor.deinit()
125+
except:
126+
pass
127+
```
128+
129+
**Output is:**
130+
```
131+
StepMotorPWMCounter(pin_step=Pin(26), pin_dir=Pin(23), freq=10000, reverse=0,
132+
pwm=PWM(Pin(26), freq=10000, duty_u16=0),
133+
counter=Counter(0, src=Pin(26), direction=Pin(23), edge=Counter.RISING, filter_ns=0))
134+
motor.steps_target=8192, motor.steps_counter=2, motor.is_ready()=False
135+
motor.steps_target=8192, motor.steps_counter=1025, motor.is_ready()=False
136+
motor.steps_target=8192, motor.steps_counter=2048, motor.is_ready()=False
137+
motor.steps_target=8192, motor.steps_counter=3071, motor.is_ready()=False
138+
motor.steps_target=8192, motor.steps_counter=4094, motor.is_ready()=False
139+
motor.steps_target=8192, motor.steps_counter=5117, motor.is_ready()=False
140+
motor.steps_target=8192, motor.steps_counter=6139, motor.is_ready()=False
141+
motor.steps_target=8192, motor.steps_counter=7162, motor.is_ready()=False
142+
motor.steps_target=8192, motor.steps_counter=8185, motor.is_ready()=False
143+
irq_handler: steps_over_run=6, counter.get_value()=8204
144+
145+
motor.steps_target=8192, motor.steps_counter=8204, motor.is_ready()=True
146+
SET steps_target -8192
147+
sleep(1)
148+
149+
motor.steps_target=-8192, motor.steps_counter=7200, motor.is_ready()=False
150+
motor.steps_target=-8192, motor.steps_counter=6176, motor.is_ready()=False
151+
motor.steps_target=-8192, motor.steps_counter=5153, motor.is_ready()=False
152+
motor.steps_target=-8192, motor.steps_counter=4130, motor.is_ready()=False
153+
motor.steps_target=-8192, motor.steps_counter=3107, motor.is_ready()=False
154+
motor.steps_target=-8192, motor.steps_counter=2084, motor.is_ready()=False
155+
motor.steps_target=-8192, motor.steps_counter=1061, motor.is_ready()=False
156+
motor.steps_target=-8192, motor.steps_counter=37, motor.is_ready()=False
157+
motor.steps_target=-8192, motor.steps_counter=-986, motor.is_ready()=False
158+
motor.steps_target=-8192, motor.steps_counter=-2009, motor.is_ready()=False
159+
motor.steps_target=-8192, motor.steps_counter=-3032, motor.is_ready()=False
160+
motor.steps_target=-8192, motor.steps_counter=-4054, motor.is_ready()=False
161+
motor.steps_target=-8192, motor.steps_counter=-5077, motor.is_ready()=False
162+
motor.steps_target=-8192, motor.steps_counter=-6100, motor.is_ready()=False
163+
motor.steps_target=-8192, motor.steps_counter=-7123, motor.is_ready()=False
164+
motor.steps_target=-8192, motor.steps_counter=-8146, motor.is_ready()=False
165+
irq_handler: steps_over_run=4, counter.get_value()=-8188
166+
motor.steps_target=-8192, motor.steps_counter=-8189, motor.is_ready()=False
167+
168+
motor.steps_target=-8192, motor.steps_counter=-9209, motor.is_ready()=True
169+
SET steps_target 8192
170+
sleep(1)
171+
172+
motor.steps_target=8192, motor.steps_counter=-8205, motor.is_ready()=False
173+
Traceback (most recent call last):
174+
File "<stdin>", line 25, in <module>
175+
KeyboardInterrupt:
176+
```
177+
178+
**Example with motor speed acceleration/deceleration:**
179+
```
180+
# stepper_motor_pwm_counter_test2.py
181+
182+
from time import sleep
183+
184+
from stepper_motor_pwm_counter import StepperMotorPwmCounter
185+
186+
187+
try:
188+
motor = StepperMotorPwmCounter(26, 23)
189+
print(motor)
190+
191+
f_min = 3_000
192+
f_max = 50_000
193+
df = 1_000
194+
motor.freq = f_min
195+
motor_steps_start = motor.steps_counter
196+
motor.steps_target = 8192 * 10
197+
while True:
198+
if not motor.is_ready():
199+
motor.go()
200+
else:
201+
print()
202+
print(f'motor.steps_target={motor.steps_target}, motor.steps_counter={motor.steps_counter}, motor.is_ready()={motor.is_ready()}')
203+
print('SET steps_target', -motor.steps_target)
204+
print('sleep(1)')
205+
print()
206+
sleep(1)
207+
motor_steps_start = motor.steps_target
208+
motor.steps_target = -motor.steps_target
209+
motor.go()
210+
211+
212+
m = min(abs(motor.steps_counter - motor_steps_start), abs(motor.steps_target - motor.steps_counter))
213+
motor.freq = min(f_min + df * m // 1000, f_max)
214+
215+
sleep(0.1)
216+
217+
except Exception as e:
218+
print(e)
219+
raise e
220+
finally:
221+
try:
222+
motor.deinit()
223+
except:
224+
pass
225+
226+
```
227+
[Motor speed acceleration/deceleration video](https://drive.google.com/file/d/1HOkmqnaepOOmt4XUEJzPtQJNCVQrRUXs/view?usp=drive_link)

0 commit comments

Comments
 (0)