-
-
Save nickovs/23928a8591735b00b00654fa628302d5 to your computer and use it in GitHub Desktop.
| # stepper.py | |
| # A micropython driver for 4-phase, unipolar stepper motors such as | |
| # the 28BYJ-48 | |
| # Relesed to the Public Domain by Nicko van Someren, 2020 | |
| # The constructor for the Stepper class takes as arguments the four | |
| # pins for driving the motor phases, in phase order, and optionally a | |
| # timer. The pins can be passed as pin numbers or machine.Pin objects | |
| # and the timer can be a machine.Timer object or a timer index. Note | |
| # that if two stepper motors use the same timer then they will not be | |
| # able to run at the same time. | |
| # | |
| # The run() method takes a number of steps and an optional delay (in | |
| # seconds) between driving the steps (the default is 1ms). A negative | |
| # step count will drive the motor in the oposite direction to a | |
| # positive count. The count represents "half steps" since the driver | |
| # alternates driving single coils and driving pairs of adjacent coils. | |
| # Calls to run() return immediately; the motor runs on a timer in the | |
| # background. Calling run() again before the previous command has | |
| # finished adds the new count to the old count, so the destination | |
| # position is the sum of the requests; the delay is set to the new | |
| # value if stepper is not already at its final location. | |
| # | |
| # The stop() method will stop the rotation of the motor. It returns | |
| # the number of un-taken steps that would be needed to perform the | |
| # outstanding requests from previous calls to run(). | |
| # | |
| # The is_running property returns true if the motor is running, | |
| # i.e. stop() would return a non-zero value, and false otherwise. | |
| import machine | |
| import time | |
| # When the following number is sampled at four consecutive | |
| # even-numbered bits it will have two bits set, but sampling at four | |
| # consecutive odd-numbered bits will only yield one bit set. | |
| _WAVE_MAGIC = 0b0000011100000111 | |
| class Stepper: | |
| def __init__(self, A, B, C, D, T=1): | |
| if not isinstance(T, machine.Timer): | |
| T = machine.Timer(T) | |
| self._timer = T | |
| l = [] | |
| for p in (A, B, C, D): | |
| if not isinstance(p, machine.Pin): | |
| p = machine.Pin(p, machine.Pin.OUT) | |
| l.append(p) | |
| self._pins = l | |
| self._phase = 0 | |
| self._stop() | |
| self._run_remaining = 0 | |
| def _stop(self): | |
| [p.off() for p in self._pins] | |
| # Note: This is called on an interrupt on some platforms, so it must not use the heap | |
| def _callback(self, t): | |
| if self._run_remaining != 0: | |
| direction = 1 if self._run_remaining > 0 else -1 | |
| self._phase = (self._phase + direction) % 8 | |
| wave = _WAVE_MAGIC >> self._phase | |
| for i in range(4): | |
| self._pins[i].value((wave >> (i*2)) & 1) | |
| self._run_remaining -= direction | |
| else: | |
| self._timer.deinit() | |
| self._stop() | |
| def run(self, count, delay=0.001): | |
| tick_hz=1000000 | |
| period = int(delay*tick_hz) | |
| if period < 500: | |
| period = 500 | |
| self._run_remaining += count | |
| if self._run_remaining != 0: | |
| self._timer.init(period=period, tick_hz=tick_hz, | |
| mode=machine.Timer.PERIODIC, callback=self._callback) | |
| else: | |
| self._timer.deinit() | |
| self._stop() | |
| def stop(self): | |
| remaining = self._run_remaining | |
| self._run_remaining = 0 | |
| self._timer.deinit() | |
| self._stop() | |
| return remaining | |
| @property | |
| def is_running(self): | |
| return self._run_remaining != 0 |
@omirete The answer to that is dependent on the type of stepper motor that you have but it will typically be 2 times the nominal number of steps for the motor. For some sorts of winding it could be 1 times or 4 times, but it's pretty easy to tell those apart just by trying. Of course this for one rotation of the motor's shaft; if you have a gearbox then you need to multiply this by the division ratio of the gearbox. Thus if you have a 5:1 step-down gearbox then you need to multiply the count by 5, since you will need 5 revolutions of the motor shaft to get one revolution of the gearbox output.
This is a beautiful and useful piece of code! Thank you for sharing it!
Is there a quick way to make the motor operate in Full Step?
@11mbs The code as written will always carry out the motion using half-stepping, although you can always choose to pass in even values for the count.
The easiest way to change the code to only drive in full-step would be to change line 64 to read self._phase = (self._phase + direction*2) % 8 and line 53 to self._phase = 1. This works because the 'odd' phases are the ones where only one winding is powered; starting at 1 and moving by an even amount will keep you there.
One could also update this code to add a fullstep parameter to the constructor, use it to set a step size attribute in the object to either 1 or 2 and use this to multiply the direction at run time. This would be a worthwhile change but I'm travelling right now and don't have access to a stepper motor, and I'm hesitant to post changes to the published code without testing them, so I'm going to leave that task to you for the moment.
Thanks a lot for this! It works really well.
May I ask how I would go about calculating the exact value for the
countvariable so I get exactly a full revolution?