micropython_trasmit_recieve/servo.py

75 lines
2.5 KiB
Python
Raw Permalink Normal View History

2026-05-27 04:39:50 +00:00
# servo.py — MG995 (and other analog hobby servos), 50 Hz PWM
from machine import Pin, PWM
# MG995: treat as standard 1.02.0 ms for ~0180°; many units reach full travel closer to 0.52.5 ms.
_DEFAULT_MIN_US = 1000
_DEFAULT_MAX_US = 2000
_WIDE_MIN_US = 500
_WIDE_MAX_US = 2500
class MG995:
def __init__(
self,
pin,
freq=50,
min_us=_DEFAULT_MIN_US,
max_us=_DEFAULT_MAX_US,
angle_max=180,
wide_range=False,
):
"""
Drive one MG995 on `pin` (GPIO number or Pin).
`wide_range=True` uses ~5002500 µs pulse limits (often needed for full
mechanical sweep); default 10002000 µs is gentler on the gearbox.
`angle_max` is the upper limit passed to write_angle (default 180).
"""
if wide_range:
min_us, max_us = _WIDE_MIN_US, _WIDE_MAX_US
self._pin_id = pin
self._freq = int(freq)
self._min_us = int(min_us)
self._max_us = int(max_us)
self._angle_max = float(angle_max)
self._pwm = PWM(Pin(pin), freq=self._freq, duty_u16=0)
self._use_ns = hasattr(self._pwm, 'duty_ns')
self._period_us = 1_000_000 // self._freq if self._freq > 0 else 20_000
def write_microseconds(self, us):
"""Set pulse width in microseconds (clamped to configured min/max)."""
us = max(self._min_us, min(self._max_us, int(us)))
if self._use_ns:
self._pwm.duty_ns(us * 1000)
else:
# Fraction of period → duty_u16 (ESP32-style full-scale mapping)
self._pwm.duty_u16(max(0, min(65535, int(us * 65535 / self._period_us)))))
def write_angle(self, degrees):
"""Map `degrees` (0 … angle_max) to pulse between min_us and max_us."""
a = max(0.0, min(self._angle_max, float(degrees)))
span = self._max_us - self._min_us
us = self._min_us + int(round(span * (a / self._angle_max)))
self.write_microseconds(us)
def center(self):
"""Mid pulse (~center position for default symmetric limits)."""
self.write_microseconds((self._min_us + self._max_us) // 2)
def off(self):
"""Stop PWM output (no holding torque from the signal)."""
try:
self._pwm.duty_u16(0)
except Exception:
pass
try:
if self._use_ns:
self._pwm.duty_ns(0)
except Exception:
pass
def deinit(self):
self.off()
self._pwm.deinit()