Skip to content

narrow servo pulse range for safety; expose .fraction; improve doc #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 34 additions & 17 deletions adafruit_motor/servo.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,65 +40,82 @@ class _BaseServo: # pylint: disable-msg=too-few-public-methods
:param ~pulseio.PWMOut pwm_out: PWM output object.
:param int min_pulse: The minimum pulse length of the servo in microseconds.
:param int max_pulse: The maximum pulse length of the servo in microseconds."""
def __init__(self, pwm_out, *, min_pulse=550, max_pulse=2400):
def __init__(self, pwm_out, *, min_pulse=750, max_pulse=2250):
self._min_duty = int((min_pulse * pwm_out.frequency) / 1000000 * 0xffff)
max_duty = (max_pulse * pwm_out.frequency) / 1000000 * 0xffff
self._duty_range = int(max_duty - self._min_duty)
self._pwm_out = pwm_out

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

@_fraction.setter
def _fraction(self, value):
"""The fraction of pulse high."""
@fraction.setter
def fraction(self, value):
duty_cycle = self._min_duty + int(value * self._duty_range)
self._pwm_out.duty_cycle = duty_cycle

class Servo(_BaseServo):
"""Control the position of a servo.

:param ~pulseio.PWMOut pwm_out: PWM output object.
:param int actuation_range: The physical range of the servo corresponding to the signal's
duty in degrees.
:param int min_pulse: The minimum pulse length of the servo in microseconds.
:param int max_pulse: The maximum pulse length of the servo in microseconds."""
def __init__(self, pwm_out, *, actuation_range=180, min_pulse=550, max_pulse=2400):
:param int actuation_range: The physical range of motion of the servo in degrees, \
for the given ``min_pulse`` and ``max_pulse`` values.
:param int min_pulse: The minimum pulse width of the servo in microseconds.
:param int max_pulse: The maximum pulse width of the servo in microseconds.

The specified pulse width range of a servo has historically been 1000-2000us,
for a 90 degree range of motion. But nearly all modern servos have a 170-180
degree range, and the pulse widths can go well out of the range to achieve this
extended motion. The default values here of ``750`` and ``2250`` typically give
135 degrees of motion. You can set ``actuation_range`` to correspond to the
actual range of motion you observe with your given ``min_pulse`` and ``max_pulse``
values.

.. warning:: You can extend the pulse width above and below these limits to
get a wider range of movement. But if you go too low or too high,
the servo mechanism may hit the end stops, buzz, and draw extra current as it stalls.
Test carefully to find the safe minimum and maximum.
"""
def __init__(self, pwm_out, *, actuation_range=180, min_pulse=750, max_pulse=2250):
super().__init__(pwm_out, min_pulse=min_pulse, max_pulse=max_pulse)
self._actuation_range = actuation_range
self._pwm = pwm_out

@property
def angle(self):
"""The servo angle in degrees."""
return self._actuation_range * self._fraction
"""The servo angle in degrees. Must be in the range ``0`` to ``actuation_range``."""
return self._actuation_range * self.fraction

@angle.setter
def angle(self, new_angle):
if new_angle < 0 or new_angle > self._actuation_range:
raise ValueError("Angle out of range")
self._fraction = new_angle / self._actuation_range
self.fraction = new_angle / self._actuation_range

class ContinuousServo(_BaseServo):
"""Control a continuous rotation servo.

:param int min_pulse: The minimum pulse length of the servo in microseconds.
:param int max_pulse: The maximum pulse length of the servo in microseconds."""
:param int min_pulse: The minimum pulse width of the servo in microseconds.
:param int max_pulse: The maximum pulse width of the servo in microseconds."""
@property
def throttle(self):
"""How much power is being delivered to the motor. Values range from ``-1.0`` (full
throttle reverse) to ``1.0`` (full throttle forwards.) ``0`` will stop the motor from
spinning."""
return self._fraction * 2 - 1
return self.fraction * 2 - 1

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

def __enter__(self):
return self
Expand Down
4 changes: 2 additions & 2 deletions examples/continuous_servo.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
# pca = PCA9685(i2c, reference_clock_speed=25630710)
pca.frequency = 50

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

print("Forwards")
servo7.throttle = 1
Expand Down
12 changes: 11 additions & 1 deletion examples/servo_sweep.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
# This is an example for the Micro servo - TowerPro SG-92R: https://www.adafruit.com/product/169
# servo7 = servo.Servo(pca.channels[7], min_pulse=500, max_pulse=2400)

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

# We sleep in the loops to give the servo time to move into position.
Expand All @@ -43,4 +45,12 @@
for i in range(180):
servo7.angle = 180 - i
time.sleep(0.03)

# You can also specify the movement fractionally.
fraction = 0.0
while fraction < 1.0:
servo7.fraction = fraction
fraction += 0.01
time.sleep(0.03)

pca.deinit()