Skip to content

Commit c34abe2

Browse files
authored
Merge pull request #9 from dhalbert/better_servo_limits
narrow servo pulse range for safety; expose .fraction; improve doc
2 parents 6830041 + 667ae1c commit c34abe2

File tree

3 files changed

+47
-20
lines changed

3 files changed

+47
-20
lines changed

adafruit_motor/servo.py

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40,65 +40,82 @@ class _BaseServo: # pylint: disable-msg=too-few-public-methods
4040
:param ~pulseio.PWMOut pwm_out: PWM output object.
4141
:param int min_pulse: The minimum pulse length of the servo in microseconds.
4242
:param int max_pulse: The maximum pulse length of the servo in microseconds."""
43-
def __init__(self, pwm_out, *, min_pulse=550, max_pulse=2400):
43+
def __init__(self, pwm_out, *, min_pulse=750, max_pulse=2250):
4444
self._min_duty = int((min_pulse * pwm_out.frequency) / 1000000 * 0xffff)
4545
max_duty = (max_pulse * pwm_out.frequency) / 1000000 * 0xffff
4646
self._duty_range = int(max_duty - self._min_duty)
4747
self._pwm_out = pwm_out
4848

4949
@property
50-
def _fraction(self):
50+
def fraction(self):
51+
"""Pulse width expressed as fraction between 0.0 (`min_pulse`) and 1.0 (`max_pulse`).
52+
For conventional servos, corresponds to the servo position as a fraction
53+
of the actuation range.
54+
"""
5155
return (self._pwm_out.duty_cycle - self._min_duty) / self._duty_range
5256

53-
@_fraction.setter
54-
def _fraction(self, value):
55-
"""The fraction of pulse high."""
57+
@fraction.setter
58+
def fraction(self, value):
5659
duty_cycle = self._min_duty + int(value * self._duty_range)
5760
self._pwm_out.duty_cycle = duty_cycle
5861

5962
class Servo(_BaseServo):
6063
"""Control the position of a servo.
6164
6265
:param ~pulseio.PWMOut pwm_out: PWM output object.
63-
:param int actuation_range: The physical range of the servo corresponding to the signal's
64-
duty in degrees.
65-
:param int min_pulse: The minimum pulse length of the servo in microseconds.
66-
:param int max_pulse: The maximum pulse length of the servo in microseconds."""
67-
def __init__(self, pwm_out, *, actuation_range=180, min_pulse=550, max_pulse=2400):
66+
:param int actuation_range: The physical range of motion of the servo in degrees, \
67+
for the given ``min_pulse`` and ``max_pulse`` values.
68+
:param int min_pulse: The minimum pulse width of the servo in microseconds.
69+
:param int max_pulse: The maximum pulse width of the servo in microseconds.
70+
71+
The specified pulse width range of a servo has historically been 1000-2000us,
72+
for a 90 degree range of motion. But nearly all modern servos have a 170-180
73+
degree range, and the pulse widths can go well out of the range to achieve this
74+
extended motion. The default values here of ``750`` and ``2250`` typically give
75+
135 degrees of motion. You can set ``actuation_range`` to correspond to the
76+
actual range of motion you observe with your given ``min_pulse`` and ``max_pulse``
77+
values.
78+
79+
.. warning:: You can extend the pulse width above and below these limits to
80+
get a wider range of movement. But if you go too low or too high,
81+
the servo mechanism may hit the end stops, buzz, and draw extra current as it stalls.
82+
Test carefully to find the safe minimum and maximum.
83+
"""
84+
def __init__(self, pwm_out, *, actuation_range=180, min_pulse=750, max_pulse=2250):
6885
super().__init__(pwm_out, min_pulse=min_pulse, max_pulse=max_pulse)
6986
self._actuation_range = actuation_range
7087
self._pwm = pwm_out
7188

7289
@property
7390
def angle(self):
74-
"""The servo angle in degrees."""
75-
return self._actuation_range * self._fraction
91+
"""The servo angle in degrees. Must be in the range ``0`` to ``actuation_range``."""
92+
return self._actuation_range * self.fraction
7693

7794
@angle.setter
7895
def angle(self, new_angle):
7996
if new_angle < 0 or new_angle > self._actuation_range:
8097
raise ValueError("Angle out of range")
81-
self._fraction = new_angle / self._actuation_range
98+
self.fraction = new_angle / self._actuation_range
8299

83100
class ContinuousServo(_BaseServo):
84101
"""Control a continuous rotation servo.
85102
86-
:param int min_pulse: The minimum pulse length of the servo in microseconds.
87-
:param int max_pulse: The maximum pulse length of the servo in microseconds."""
103+
:param int min_pulse: The minimum pulse width of the servo in microseconds.
104+
:param int max_pulse: The maximum pulse width of the servo in microseconds."""
88105
@property
89106
def throttle(self):
90107
"""How much power is being delivered to the motor. Values range from ``-1.0`` (full
91108
throttle reverse) to ``1.0`` (full throttle forwards.) ``0`` will stop the motor from
92109
spinning."""
93-
return self._fraction * 2 - 1
110+
return self.fraction * 2 - 1
94111

95112
@throttle.setter
96113
def throttle(self, value):
97114
if value > 1.0 or value < -1.0:
98115
raise ValueError("Throttle must be between -1.0 and 1.0")
99116
if value is None:
100117
raise ValueError("Continuous servos cannot spin freely")
101-
self._fraction = (value + 1) / 2
118+
self.fraction = (value + 1) / 2
102119

103120
def __enter__(self):
104121
return self

examples/continuous_servo.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@
1919
# pca = PCA9685(i2c, reference_clock_speed=25630710)
2020
pca.frequency = 50
2121

22-
# The pulse range is 550 - 2400 by default.
22+
# The pulse range is 750 - 2250 by default.
2323
servo7 = servo.ContinuousServo(pca.channels[7])
2424
# If your servo doesn't stop once the script is finished you may need to tune the
2525
# reference_clock_speed above or the min_pulse and max_pulse timings below.
26-
# servo7 = servo.ContinuousServo(pca.channels[7], min_pulse=550, max_pulse=2400)
26+
# servo7 = servo.ContinuousServo(pca.channels[7], min_pulse=750, max_pulse=2250)
2727

2828
print("Forwards")
2929
servo7.throttle = 1

examples/servo_sweep.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
# This is an example for the Micro servo - TowerPro SG-92R: https://www.adafruit.com/product/169
3434
# servo7 = servo.Servo(pca.channels[7], min_pulse=500, max_pulse=2400)
3535

36-
# The pulse range is 550 - 2400 by default.
36+
# The pulse range is 750 - 2250 by default. This range typically gives 135 degrees of
37+
# range, but the default is to use 180 degrees. You can specify the expected range if you wish:
38+
# servo7 = servo.Servo(pca.channels[7], actuation_range=135)
3739
servo7 = servo.Servo(pca.channels[7])
3840

3941
# We sleep in the loops to give the servo time to move into position.
@@ -43,4 +45,12 @@
4345
for i in range(180):
4446
servo7.angle = 180 - i
4547
time.sleep(0.03)
48+
49+
# You can also specify the movement fractionally.
50+
fraction = 0.0
51+
while fraction < 1.0:
52+
servo7.fraction = fraction
53+
fraction += 0.01
54+
time.sleep(0.03)
55+
4656
pca.deinit()

0 commit comments

Comments
 (0)