diff --git a/adafruit_motor/motor.py b/adafruit_motor/motor.py index 503baeb..61f6253 100644 --- a/adafruit_motor/motor.py +++ b/adafruit_motor/motor.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: 2021 Scott Shawcroft for Adafruit Industries # # SPDX-License-Identifier: MIT @@ -23,11 +23,26 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Motor.git" +FAST_DECAY = 0 +"""Recirculation current fast decay mode (coasting)""" + +SLOW_DECAY = 1 +"""Recirculation current slow decay mode (braking)""" + class DCMotor: """DC motor driver. ``positive_pwm`` and ``negative_pwm`` can be swapped if the motor runs in the opposite direction from what was expected for "forwards". + Motor controller recirculation current decay mode is selectable and defaults to + ``motor.FAST_DECAY`` (coasting). ``motor.SLOW_DECAY`` is recommended to improve spin + threshold, speed-to-throttle linearity, and PWM frequency sensitivity. + + Decay mode settings only effect the operational performance of controller chips such + as the DRV8833, DRV8871, and TB6612. Either decay mode setting is compatible + with discrete h-bridge controller circuitry such as the L9110H and L293D; operational + performance is not altered. + :param ~pwmio.PWMOut positive_pwm: The motor input that causes the motor to spin forwards when high and the other is low. :param ~pwmio.PWMOut negative_pwm: The motor input that causes the motor to spin backwards @@ -37,11 +52,12 @@ def __init__(self, positive_pwm, negative_pwm): self._positive = positive_pwm self._negative = negative_pwm self._throttle = None + self._decay_mode = FAST_DECAY @property def throttle(self): """Motor speed, ranging from -1.0 (full speed reverse) to 1.0 (full speed forward), - or ``None``. + or ``None`` (controller off). If ``None``, both PWMs are turned full off. If ``0.0``, both PWMs are turned full on. """ return self._throttle @@ -49,22 +65,46 @@ def throttle(self): @throttle.setter def throttle(self, value): if value is not None and (value > 1.0 or value < -1.0): - raise ValueError("Throttle must be None or between -1.0 and 1.0") + raise ValueError("Throttle must be None or between -1.0 and +1.0") self._throttle = value - if value is None: + if value is None: # Turn off motor controller (high-Z) self._positive.duty_cycle = 0 self._negative.duty_cycle = 0 - elif value == 0: + elif value == 0: # Brake motor (low-Z) self._positive.duty_cycle = 0xFFFF self._negative.duty_cycle = 0xFFFF else: duty_cycle = int(0xFFFF * abs(value)) - if value < 0: - self._positive.duty_cycle = 0 - self._negative.duty_cycle = duty_cycle - else: - self._positive.duty_cycle = duty_cycle - self._negative.duty_cycle = 0 + if self._decay_mode == SLOW_DECAY: # Slow Decay (Braking) Mode + if value < 0: + self._positive.duty_cycle = 0xFFFF - duty_cycle + self._negative.duty_cycle = 0xFFFF + else: + self._positive.duty_cycle = 0xFFFF + self._negative.duty_cycle = 0xFFFF - duty_cycle + else: # Default Fast Decay (Coasting) Mode + if value < 0: + self._positive.duty_cycle = 0 + self._negative.duty_cycle = duty_cycle + else: + self._positive.duty_cycle = duty_cycle + self._negative.duty_cycle = 0 + + @property + def decay_mode(self): + """Motor controller recirculation current decay mode. A value of ``motor.FAST_DECAY`` + sets the motor controller to the default fast recirculation current decay mode + (coasting); ``motor.SLOW_DECAY`` sets slow decay (braking) mode.""" + return self._decay_mode + + @decay_mode.setter + def decay_mode(self, mode=FAST_DECAY): + if mode in (FAST_DECAY, SLOW_DECAY): + self._decay_mode = mode + else: + raise ValueError( + "Decay mode value must be either motor.FAST_DECAY or motor.SLOW_DECAY" + ) def __enter__(self): return self diff --git a/examples/motor_pca9685_dc_motor.py b/examples/motor_pca9685_dc_motor.py index cec9aa9..17e34bb 100644 --- a/examples/motor_pca9685_dc_motor.py +++ b/examples/motor_pca9685_dc_motor.py @@ -32,6 +32,9 @@ # See here for more info: https://learn.adafruit.com/adafruit-motor-shield-v2-for-arduino/faq#faq-13 pca.channels[7].duty_cycle = 0xFFFF motor4 = motor.DCMotor(pca.channels[5], pca.channels[6]) +motor4.decay_mode = ( + motor.SLOW_DECAY +) # Set motor to active braking mode to improve performance print("Forwards slow") motor4.throttle = 0.5 diff --git a/tests/test_stepper.py b/tests/test_stepper.py index 24673cf..73c312a 100644 --- a/tests/test_stepper.py +++ b/tests/test_stepper.py @@ -2,6 +2,18 @@ # # SPDX-License-Identifier: Unlicense +""" +`test_stepper` +==================================================== + +Tests stepper functionality. + +* Author(s): ladyada +""" + +__version__ = "1.0.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Motor.git" + import os import sys from unittest.mock import MagicMock @@ -14,17 +26,23 @@ from adafruit_motor import stepper # pylint: disable-msg=wrong-import-position +# pylint: disable=consider-using-in + class Coil: + """Class Coil""" + def __init__(self): self._duty_cycle = 0 @property def frequency(self): + """Default frequency setting""" return 1500 @property def duty_cycle(self): + """16-bit duty cycle value""" return self._duty_cycle @duty_cycle.setter @@ -34,6 +52,7 @@ def duty_cycle(self, value): def test_single_coil(): + """Tests single coil""" coil = (Coil(), Coil(), Coil(), Coil()) # We undo the coil order so our tests make more sense. motor = stepper.StepperMotor(coil[2], coil[0], coil[1], coil[3]) @@ -47,6 +66,7 @@ def test_single_coil(): def test_double_coil(): + """Tests double coil""" coil = (Coil(), Coil(), Coil(), Coil()) # We undo the coil order so our tests make more sense. motor = stepper.StepperMotor(coil[2], coil[0], coil[1], coil[3]) @@ -62,6 +82,7 @@ def test_double_coil(): def test_interleave_steps(): + """Tests interleave steps""" coil = (Coil(), Coil(), Coil(), Coil()) # We undo the coil order so our tests make more sense. motor = stepper.StepperMotor(coil[2], coil[0], coil[1], coil[3]) @@ -89,6 +110,7 @@ def test_interleave_steps(): def test_microstep_steps(): + """Tests microsteps""" coil = (Coil(), Coil(), Coil(), Coil()) # We undo the coil order so our tests make more sense. motor = stepper.StepperMotor(coil[2], coil[0], coil[1], coil[3], microsteps=2) @@ -121,6 +143,7 @@ def test_microstep_steps(): def test_double_to_single(): + """Tests double to single movement""" coil = (Coil(), Coil(), Coil(), Coil()) # We undo the coil order so our tests make more sense. motor = stepper.StepperMotor(coil[2], coil[0], coil[1], coil[3]) @@ -154,6 +177,7 @@ def test_double_to_single(): def test_microstep_to_single(): + """Tests microsteps to single movement""" coil = (Coil(), Coil(), Coil(), Coil()) # We undo the coil order so our tests make more sense. motor = stepper.StepperMotor(coil[2], coil[0], coil[1], coil[3])