/ adafruit_motor / servo.py
servo.py
1 # The MIT License (MIT) 2 # 3 # Copyright (c) 2017 Scott Shawcroft for Adafruit Industries LLC 4 # 5 # Permission is hereby granted, free of charge, to any person obtaining a copy 6 # of this software and associated documentation files (the "Software"), to deal 7 # in the Software without restriction, including without limitation the rights 8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 # copies of the Software, and to permit persons to whom the Software is 10 # furnished to do so, subject to the following conditions: 11 # 12 # The above copyright notice and this permission notice shall be included in 13 # all copies or substantial portions of the Software. 14 # 15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 # THE SOFTWARE. 22 """ 23 `adafruit_motor.servo` 24 ==================================================== 25 26 Servos are motor based actuators that incorporate a feedback loop into the design. These feedback 27 loops enable pulse width modulated control to determine position or rotational speed. 28 29 * Author(s): Scott Shawcroft 30 """ 31 32 __version__ = "0.0.0-auto.0" 33 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Motor.git" 34 35 # We disable the too few public methods check because this is a private base class for the two types 36 # of servos. 37 class _BaseServo: # pylint: disable-msg=too-few-public-methods 38 """Shared base class that handles pulse output based on a value between 0 and 1.0 39 40 :param ~pulseio.PWMOut pwm_out: PWM output object. 41 :param int min_pulse: The minimum pulse length of the servo in microseconds. 42 :param int max_pulse: The maximum pulse length of the servo in microseconds.""" 43 44 def __init__(self, pwm_out, *, min_pulse=750, max_pulse=2250): 45 self._pwm_out = pwm_out 46 self.set_pulse_width_range(min_pulse, max_pulse) 47 48 def set_pulse_width_range(self, min_pulse=750, max_pulse=2250): 49 """Change min and max pulse widths.""" 50 self._min_duty = int((min_pulse * self._pwm_out.frequency) / 1000000 * 0xFFFF) 51 max_duty = (max_pulse * self._pwm_out.frequency) / 1000000 * 0xFFFF 52 self._duty_range = int(max_duty - self._min_duty) 53 54 @property 55 def fraction(self): 56 """Pulse width expressed as fraction between 0.0 (`min_pulse`) and 1.0 (`max_pulse`). 57 For conventional servos, corresponds to the servo position as a fraction 58 of the actuation range. Is None when servo is diabled (pulsewidth of 0ms). 59 """ 60 if self._pwm_out.duty_cycle == 0: # Special case for disabled servos 61 return None 62 return (self._pwm_out.duty_cycle - self._min_duty) / self._duty_range 63 64 @fraction.setter 65 def fraction(self, value): 66 if value is None: 67 self._pwm_out.duty_cycle = 0 # disable the motor 68 return 69 if not 0.0 <= value <= 1.0: 70 raise ValueError("Must be 0.0 to 1.0") 71 duty_cycle = self._min_duty + int(value * self._duty_range) 72 self._pwm_out.duty_cycle = duty_cycle 73 74 75 class Servo(_BaseServo): 76 """Control the position of a servo. 77 78 :param ~pulseio.PWMOut pwm_out: PWM output object. 79 :param int actuation_range: The physical range of motion of the servo in degrees, \ 80 for the given ``min_pulse`` and ``max_pulse`` values. 81 :param int min_pulse: The minimum pulse width of the servo in microseconds. 82 :param int max_pulse: The maximum pulse width of the servo in microseconds. 83 84 ``actuation_range`` is an exposed property and can be changed at any time: 85 86 .. code-block:: python 87 88 servo = Servo(pwm) 89 servo.actuation_range = 135 90 91 The specified pulse width range of a servo has historically been 1000-2000us, 92 for a 90 degree range of motion. But nearly all modern servos have a 170-180 93 degree range, and the pulse widths can go well out of the range to achieve this 94 extended motion. The default values here of ``750`` and ``2250`` typically give 95 135 degrees of motion. You can set ``actuation_range`` to correspond to the 96 actual range of motion you observe with your given ``min_pulse`` and ``max_pulse`` 97 values. 98 99 .. warning:: You can extend the pulse width above and below these limits to 100 get a wider range of movement. But if you go too low or too high, 101 the servo mechanism may hit the end stops, buzz, and draw extra current as it stalls. 102 Test carefully to find the safe minimum and maximum. 103 """ 104 105 def __init__(self, pwm_out, *, actuation_range=180, min_pulse=750, max_pulse=2250): 106 super().__init__(pwm_out, min_pulse=min_pulse, max_pulse=max_pulse) 107 self.actuation_range = actuation_range 108 """The physical range of motion of the servo in degrees.""" 109 self._pwm = pwm_out 110 111 @property 112 def angle(self): 113 """The servo angle in degrees. Must be in the range ``0`` to ``actuation_range``. 114 Is None when servo is disabled.""" 115 if self.fraction is None: # special case for disabled servos 116 return None 117 return self.actuation_range * self.fraction 118 119 @angle.setter 120 def angle(self, new_angle): 121 if new_angle is None: # disable the servo by sending 0 signal 122 self.fraction = None 123 return 124 if new_angle < 0 or new_angle > self.actuation_range: 125 raise ValueError("Angle out of range") 126 self.fraction = new_angle / self.actuation_range 127 128 129 class ContinuousServo(_BaseServo): 130 """Control a continuous rotation servo. 131 132 :param int min_pulse: The minimum pulse width of the servo in microseconds. 133 :param int max_pulse: The maximum pulse width of the servo in microseconds.""" 134 135 @property 136 def throttle(self): 137 """How much power is being delivered to the motor. Values range from ``-1.0`` (full 138 throttle reverse) to ``1.0`` (full throttle forwards.) ``0`` will stop the motor from 139 spinning.""" 140 return self.fraction * 2 - 1 141 142 @throttle.setter 143 def throttle(self, value): 144 if value > 1.0 or value < -1.0: 145 raise ValueError("Throttle must be between -1.0 and 1.0") 146 if value is None: 147 raise ValueError("Continuous servos cannot spin freely") 148 self.fraction = (value + 1) / 2 149 150 def __enter__(self): 151 return self 152 153 def __exit__(self, exception_type, exception_value, traceback): 154 self.throttle = 0