From 701627674336de0b2e68a510064ec6517e1e522e Mon Sep 17 00:00:00 2001 From: Carter Nelson Date: Tue, 5 May 2020 14:59:39 +0100 Subject: [PATCH 1/6] add digitalio for steppers --- adafruit_motor/stepper.py | 210 +++++++++++++++++++++--------- examples/motor_servo_digitalio.py | 49 +++++++ 2 files changed, 196 insertions(+), 63 deletions(-) mode change 100644 => 100755 adafruit_motor/stepper.py create mode 100755 examples/motor_servo_digitalio.py diff --git a/adafruit_motor/stepper.py b/adafruit_motor/stepper.py old mode 100644 new mode 100755 index 5873cec..016a8cf --- a/adafruit_motor/stepper.py +++ b/adafruit_motor/stepper.py @@ -57,9 +57,37 @@ """Step a fraction of a step by partially activating two neighboring coils. Step size is determined by ``microsteps`` constructor argument.""" +_SINGLE_STEPS = ( + (0, 1, 0, 0), + (0, 0, 1, 0), + (1, 0, 0, 0), + (0, 0, 0, 1), +) + +_DOUBLE_STEPS = ( + (0, 1, 0, 1), + (0, 1, 1, 0), + (1, 0, 1, 0), + (1, 0, 0, 1), +) + +_INTERLEAVE_STEPS = ( + (0, 1, 0, 1), + (0, 1, 0, 0), + (0, 1, 1, 0), + (0, 0, 1, 0), + (1, 0, 1, 0), + (1, 0, 0, 0), + (1, 0, 0, 1), + (0, 0, 0, 1), +) class StepperMotor: - """A bipolar stepper motor or four coil unipolar motor. + """A bipolar stepper motor or four coil unipolar motor. The use of microstepping requires + pins that can output PWM. For non-microstepping, set `microsteps` to `None` and use either + PWM or digital out pins. + + **PWM** :param ~pulseio.PWMOut ain1: `pulseio.PWMOut`-compatible output connected to the driver for the first coil (unipolar) or first input to first coil (bipolar). @@ -70,54 +98,93 @@ class StepperMotor: :param ~pulseio.PWMOut bin2: `pulseio.PWMOut`-compatible output connected to the driver for the fourth coil (unipolar) or second input to second coil (bipolar). :param int microsteps: Number of microsteps between full steps. Must be at least 2 and even. + + **Digital Out** + + :param ~digitalio.DigitalInOut ain1: `digitalio.DigitalInOut`-compatible output connected to + the driver for the first coil (unipolar) or first input to first coil (bipolar). + :param ~digitalio.DigitalInOut ain2: `digitalio.DigitalInOut`-compatible output connected to + the driver for the third coil (unipolar) or second input to first coil (bipolar). + :param ~digitalio.DigitalInOut bin1: `digitalio.DigitalInOut`-compatible output connected to + the driver for the second coil (unipolar) or first input to second coil (bipolar). + :param ~digitalio.DigitalInOut bin2: `digitalio.DigitalInOut`-compatible output connected to + the driver for the fourth coil (unipolar) or second input to second coil (bipolar). + :param microsteps: set to `None` """ def __init__(self, ain1, ain2, bin1, bin2, *, microsteps=16): - self._coil = (ain2, bin1, ain1, bin2) - - # set a safe pwm freq for each output - for i in range(4): - if self._coil[i].frequency < 1500: - self._coil[i].frequency = 2000 - + if microsteps is None: + # + # Digital IO Pins + # + self._steps = None + self._coil = (ain1, ain2, bin1, bin2) + else: + # + # PWM Pins + # + #set a safe pwm freq for each output + self._coil = (ain2, bin1, ain1, bin2) + for i in range(4): + if self._coil[i].frequency < 1500: + self._coil[i].frequency = 2000 + if microsteps < 2: + raise ValueError("Microsteps must be at least 2") + if microsteps % 2 == 1: + raise ValueError("Microsteps must be even") + self._curve = [ + int(round(0xFFFF * math.sin(math.pi / (2 * microsteps) * i))) + for i in range(microsteps + 1) + ] self._current_microstep = 0 - if microsteps < 2: - raise ValueError("Microsteps must be at least 2") - if microsteps % 2 == 1: - raise ValueError("Microsteps must be even") self._microsteps = microsteps - self._curve = [ - int(round(0xFFFF * math.sin(math.pi / (2 * microsteps) * i))) - for i in range(microsteps + 1) - ] self._update_coils() def _update_coils(self, *, microstepping=False): - duty_cycles = [0, 0, 0, 0] - trailing_coil = (self._current_microstep // self._microsteps) % 4 - leading_coil = (trailing_coil + 1) % 4 - microstep = self._current_microstep % self._microsteps - duty_cycles[leading_coil] = self._curve[microstep] - duty_cycles[trailing_coil] = self._curve[self._microsteps - microstep] - - # This ensures DOUBLE steps use full torque. Without it, we'd use partial torque from the - # microstepping curve (0xb504). - if not microstepping and ( - duty_cycles[leading_coil] == duty_cycles[trailing_coil] - and duty_cycles[leading_coil] > 0 - ): - duty_cycles[leading_coil] = 0xFFFF - duty_cycles[trailing_coil] = 0xFFFF - - # Energize coils as appropriate: - for i in range(4): - self._coil[i].duty_cycle = duty_cycles[i] + if self._microsteps is None: + # + # Digital IO Pins + # + # Get coil activation sequence + if self._steps is None: + steps = (0, 0, 0, 0) + else: + steps = self._steps[self._current_microstep % len(self._steps)] + # Energize coils as appropriate: + for i, coil in enumerate(self._coil): + coil.value = steps[i] + else: + # + # PWM Pins + # + duty_cycles = [0, 0, 0, 0] + trailing_coil = (self._current_microstep // self._microsteps) % 4 + leading_coil = (trailing_coil + 1) % 4 + microstep = self._current_microstep % self._microsteps + duty_cycles[leading_coil] = self._curve[microstep] + duty_cycles[trailing_coil] = self._curve[self._microsteps - microstep] + + # This ensures DOUBLE steps use full torque. Without it, we'd use partial torque from the + # microstepping curve (0xb504). + if not microstepping and ( + duty_cycles[leading_coil] == duty_cycles[trailing_coil] + and duty_cycles[leading_coil] > 0 + ): + duty_cycles[leading_coil] = 0xFFFF + duty_cycles[trailing_coil] = 0xFFFF + + # Energize coils as appropriate: + for i in range(4): + self._coil[i].duty_cycle = duty_cycles[i] def release(self): """Releases all the coils so the motor can free spin, also won't use any power""" # De-energize coils: - for i in range(4): - self._coil[i].duty_cycle = 0 + for coil in self._coil: + if self._microsteps is None: + coil.value = 0 + else: + coil.duty_cycle = 0 def onestep(self, *, direction=FORWARD, style=SINGLE): """Performs one step of a particular style. The actual rotation amount will vary by style. @@ -129,34 +196,51 @@ def onestep(self, *, direction=FORWARD, style=SINGLE): :param int direction: Either `FORWARD` or `BACKWARD` :param int style: `SINGLE`, `DOUBLE`, `INTERLEAVE`""" - # Adjust current steps based on the direction and type of step. - step_size = 0 - if style == MICROSTEP: + if self._microsteps is None: + # + # Digital IO Pins + # step_size = 1 - else: - half_step = self._microsteps // 2 - full_step = self._microsteps - # Its possible the previous steps were MICROSTEPS so first align with the interleave - # pattern. - additional_microsteps = self._current_microstep % half_step - if additional_microsteps != 0: - # We set _current_microstep directly because our step size varies depending on the - # direction. - if direction == FORWARD: - self._current_microstep += half_step - additional_microsteps - else: - self._current_microstep -= additional_microsteps - step_size = 0 + if style == SINGLE: + self._steps = _SINGLE_STEPS + elif style == DOUBLE: + self._steps = _DOUBLE_STEPS elif style == INTERLEAVE: - step_size = half_step - - current_interleave = self._current_microstep // half_step - if (style == SINGLE and current_interleave % 2 == 1) or ( - style == DOUBLE and current_interleave % 2 == 0 - ): - step_size = half_step - elif style in (SINGLE, DOUBLE): - step_size = full_step + self._steps = _INTERLEAVE_STEPS + else: + raise ValueError("Unsupported step style.") + else: + # + # PWM Pins + # + # Adjust current steps based on the direction and type of step. + step_size = 0 + if style == MICROSTEP: + step_size = 1 + else: + half_step = self._microsteps // 2 + full_step = self._microsteps + # Its possible the previous steps were MICROSTEPS so first align with the interleave + # pattern. + additional_microsteps = self._current_microstep % half_step + if additional_microsteps != 0: + # We set _current_microstep directly because our step size varies depending on the + # direction. + if direction == FORWARD: + self._current_microstep += half_step - additional_microsteps + else: + self._current_microstep -= additional_microsteps + step_size = 0 + elif style == INTERLEAVE: + step_size = half_step + + current_interleave = self._current_microstep // half_step + if (style == SINGLE and current_interleave % 2 == 1) or ( + style == DOUBLE and current_interleave % 2 == 0 + ): + step_size = half_step + elif style in (SINGLE, DOUBLE): + step_size = full_step if direction == FORWARD: self._current_microstep += step_size diff --git a/examples/motor_servo_digitalio.py b/examples/motor_servo_digitalio.py new file mode 100755 index 0000000..3539600 --- /dev/null +++ b/examples/motor_servo_digitalio.py @@ -0,0 +1,49 @@ +# Use this example for digital pin control of an H-bridge driver +# like a TB6612 or L298N. + +import time +import board +import digitalio +from adafruit_motor import stepper + +DELAY = 0.01 +STEPS = 200 + +coils = ( + digitalio.DigitalInOut(board.D19), # A1 + digitalio.DigitalInOut(board.D26), # A2 + digitalio.DigitalInOut(board.D20), # B1 + digitalio.DigitalInOut(board.D21), # B2 +) + +for coil in coils: + coil.direction = digitalio.Direction.OUTPUT + +motor = stepper.StepperMotor(coils[0], coils[1], coils[2], coils[3], + microsteps=None) + +for step in range(STEPS): + motor.onestep() + time.sleep(DELAY) + +for step in range(STEPS): + motor.onestep(direction=stepper.BACKWARD) + time.sleep(DELAY) + +for step in range(STEPS): + motor.onestep(style=stepper.DOUBLE) + time.sleep(DELAY) + +for step in range(STEPS): + motor.onestep(direction=stepper.BACKWARD, style=stepper.DOUBLE) + time.sleep(DELAY) + +for step in range(STEPS): + motor.onestep(style=stepper.INTERLEAVE) + time.sleep(DELAY) + +for step in range(STEPS): + motor.onestep(direction=stepper.BACKWARD, style=stepper.INTERLEAVE) + time.sleep(DELAY) + +motor.release() From 6474a74c7567677ffb1a298545960f4ef20012e8 Mon Sep 17 00:00:00 2001 From: Carter Nelson Date: Tue, 5 May 2020 15:12:19 +0100 Subject: [PATCH 2/6] CI formatting --- adafruit_motor/stepper.py | 3 ++- examples/motor_servo_digitalio.py | 11 +++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/adafruit_motor/stepper.py b/adafruit_motor/stepper.py index 016a8cf..ca2189b 100755 --- a/adafruit_motor/stepper.py +++ b/adafruit_motor/stepper.py @@ -82,6 +82,7 @@ (0, 0, 0, 1), ) + class StepperMotor: """A bipolar stepper motor or four coil unipolar motor. The use of microstepping requires pins that can output PWM. For non-microstepping, set `microsteps` to `None` and use either @@ -123,7 +124,7 @@ def __init__(self, ain1, ain2, bin1, bin2, *, microsteps=16): # # PWM Pins # - #set a safe pwm freq for each output + # set a safe pwm freq for each output self._coil = (ain2, bin1, ain1, bin2) for i in range(4): if self._coil[i].frequency < 1500: diff --git a/examples/motor_servo_digitalio.py b/examples/motor_servo_digitalio.py index 3539600..7668767 100755 --- a/examples/motor_servo_digitalio.py +++ b/examples/motor_servo_digitalio.py @@ -10,17 +10,16 @@ STEPS = 200 coils = ( - digitalio.DigitalInOut(board.D19), # A1 - digitalio.DigitalInOut(board.D26), # A2 - digitalio.DigitalInOut(board.D20), # B1 - digitalio.DigitalInOut(board.D21), # B2 + digitalio.DigitalInOut(board.D19), # A1 + digitalio.DigitalInOut(board.D26), # A2 + digitalio.DigitalInOut(board.D20), # B1 + digitalio.DigitalInOut(board.D21), # B2 ) for coil in coils: coil.direction = digitalio.Direction.OUTPUT -motor = stepper.StepperMotor(coils[0], coils[1], coils[2], coils[3], - microsteps=None) +motor = stepper.StepperMotor(coils[0], coils[1], coils[2], coils[3], microsteps=None) for step in range(STEPS): motor.onestep() From 2b3a8250b3d97abf7de14b2e6a9e560099ab2b03 Mon Sep 17 00:00:00 2001 From: Carter Nelson Date: Tue, 5 May 2020 15:19:36 +0100 Subject: [PATCH 3/6] lint --- adafruit_motor/stepper.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/adafruit_motor/stepper.py b/adafruit_motor/stepper.py index ca2189b..d0c48aa 100755 --- a/adafruit_motor/stepper.py +++ b/adafruit_motor/stepper.py @@ -165,8 +165,8 @@ def _update_coils(self, *, microstepping=False): duty_cycles[leading_coil] = self._curve[microstep] duty_cycles[trailing_coil] = self._curve[self._microsteps - microstep] - # This ensures DOUBLE steps use full torque. Without it, we'd use partial torque from the - # microstepping curve (0xb504). + # This ensures DOUBLE steps use full torque. Without it, we'd use + # partial torque from the microstepping curve (0xb504). if not microstepping and ( duty_cycles[leading_coil] == duty_cycles[trailing_coil] and duty_cycles[leading_coil] > 0 @@ -187,7 +187,7 @@ def release(self): else: coil.duty_cycle = 0 - def onestep(self, *, direction=FORWARD, style=SINGLE): + def onestep(self, *, direction=FORWARD, style=SINGLE): #pylint: disable=too-many-branches """Performs one step of a particular style. The actual rotation amount will vary by style. `SINGLE` and `DOUBLE` will normal cause a full step rotation. `INTERLEAVE` will normally do a half step rotation. `MICROSTEP` will perform the smallest configured step. @@ -221,12 +221,12 @@ def onestep(self, *, direction=FORWARD, style=SINGLE): else: half_step = self._microsteps // 2 full_step = self._microsteps - # Its possible the previous steps were MICROSTEPS so first align with the interleave - # pattern. + # Its possible the previous steps were MICROSTEPS so first align + # with the interleave pattern. additional_microsteps = self._current_microstep % half_step if additional_microsteps != 0: - # We set _current_microstep directly because our step size varies depending on the - # direction. + # We set _current_microstep directly because our step size varies + # depending on the direction. if direction == FORWARD: self._current_microstep += half_step - additional_microsteps else: From 91c0effca59be1feb63cab0483b888e98f164ceb Mon Sep 17 00:00:00 2001 From: Carter Nelson Date: Tue, 5 May 2020 15:24:52 +0100 Subject: [PATCH 4/6] stuck in CI infinite loop --- adafruit_motor/stepper.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_motor/stepper.py b/adafruit_motor/stepper.py index d0c48aa..82deb2a 100755 --- a/adafruit_motor/stepper.py +++ b/adafruit_motor/stepper.py @@ -187,7 +187,9 @@ def release(self): else: coil.duty_cycle = 0 - def onestep(self, *, direction=FORWARD, style=SINGLE): #pylint: disable=too-many-branches + def onestep( + self, *, direction=FORWARD, style=SINGLE + ): # pylint: disable=too-many-branches """Performs one step of a particular style. The actual rotation amount will vary by style. `SINGLE` and `DOUBLE` will normal cause a full step rotation. `INTERLEAVE` will normally do a half step rotation. `MICROSTEP` will perform the smallest configured step. From 936b4e6b4182e2f5fe82e337ad222694e1200868 Mon Sep 17 00:00:00 2001 From: Carter Nelson Date: Tue, 5 May 2020 15:33:03 +0100 Subject: [PATCH 5/6] docstring --- adafruit_motor/stepper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_motor/stepper.py b/adafruit_motor/stepper.py index 82deb2a..637c8d6 100755 --- a/adafruit_motor/stepper.py +++ b/adafruit_motor/stepper.py @@ -85,8 +85,8 @@ class StepperMotor: """A bipolar stepper motor or four coil unipolar motor. The use of microstepping requires - pins that can output PWM. For non-microstepping, set `microsteps` to `None` and use either - PWM or digital out pins. + pins that can output PWM. For non-microstepping, can set microsteps to None and use + digital out pins. **PWM** From 82d28d1a249a75abd8f175d65e3b73234a235159 Mon Sep 17 00:00:00 2001 From: Carter Nelson Date: Tue, 5 May 2020 16:09:30 +0100 Subject: [PATCH 6/6] tuples to bytes --- adafruit_motor/stepper.py | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/adafruit_motor/stepper.py b/adafruit_motor/stepper.py index 637c8d6..5bcd35f 100755 --- a/adafruit_motor/stepper.py +++ b/adafruit_motor/stepper.py @@ -57,29 +57,12 @@ """Step a fraction of a step by partially activating two neighboring coils. Step size is determined by ``microsteps`` constructor argument.""" -_SINGLE_STEPS = ( - (0, 1, 0, 0), - (0, 0, 1, 0), - (1, 0, 0, 0), - (0, 0, 0, 1), -) +_SINGLE_STEPS = bytes([0b0010, 0b0100, 0b0001, 0b1000]) -_DOUBLE_STEPS = ( - (0, 1, 0, 1), - (0, 1, 1, 0), - (1, 0, 1, 0), - (1, 0, 0, 1), -) +_DOUBLE_STEPS = bytes([0b1010, 0b0110, 0b0101, 0b1001]) -_INTERLEAVE_STEPS = ( - (0, 1, 0, 1), - (0, 1, 0, 0), - (0, 1, 1, 0), - (0, 0, 1, 0), - (1, 0, 1, 0), - (1, 0, 0, 0), - (1, 0, 0, 1), - (0, 0, 0, 1), +_INTERLEAVE_STEPS = bytes( + [0b1010, 0b0010, 0b0110, 0b0100, 0b0101, 0b0001, 0b1001, 0b1000] ) @@ -148,12 +131,12 @@ def _update_coils(self, *, microstepping=False): # # Get coil activation sequence if self._steps is None: - steps = (0, 0, 0, 0) + steps = 0b0000 else: steps = self._steps[self._current_microstep % len(self._steps)] # Energize coils as appropriate: for i, coil in enumerate(self._coil): - coil.value = steps[i] + coil.value = (steps >> i) & 0x01 else: # # PWM Pins