|
| 1 | +# Servo Class |
| 2 | + |
| 3 | +The `Servo` class provides an interface for controlling standard hobby servo motors using PWM signals on the Raspberry Pi Pico. It allows you to set the servo angle, control the duty cycle directly, and safely stop or deinitialize the servo. |
| 4 | + |
| 5 | +## Constructor |
| 6 | + |
| 7 | +```python |
| 8 | +Servo(pwm, min_us=500, max_us=2500, dead_zone_us=1500, freq=50) |
| 9 | +``` |
| 10 | +- `pwm` (`PWM`): A `machine.PWM` object for the servo control pin. |
| 11 | +- `min_us` (`int`, optional): Minimum pulse width in microseconds (default: 500). |
| 12 | +- `max_us` (`int`, optional): Maximum pulse width in microseconds (default: 2500). |
| 13 | +- `dead_zone_us` (`int`, optional): Pulse width for the servo's neutral position (default: 1500). |
| 14 | +- `freq` (`int`, optional): PWM frequency in Hz (default: 50). |
| 15 | + |
| 16 | +## Example Usage |
| 17 | + |
| 18 | +```python |
| 19 | +from machine import Pin, PWM |
| 20 | +from servo import Servo |
| 21 | +from time import sleep |
| 22 | + |
| 23 | +# Create a PWM object on GPIO 16 |
| 24 | +pwm = PWM(Pin(16)) |
| 25 | +servo = Servo(pwm, freq=50) |
| 26 | + |
| 27 | +# Move servo to 0, 90, and 180 degrees |
| 28 | +servo.set_angle(0) |
| 29 | +sleep(1) |
| 30 | +servo.set_angle(90) |
| 31 | +sleep(1) |
| 32 | +servo.set_angle(180) |
| 33 | +sleep(1) |
| 34 | + |
| 35 | +# Stop the servo (move to neutral position) |
| 36 | +servo.stop() |
| 37 | + |
| 38 | +# Deinitialize when done |
| 39 | +servo.deinit() |
| 40 | +``` |
| 41 | + |
| 42 | +## Methods |
| 43 | + |
| 44 | +- **set_angle(angle)** |
| 45 | + Set the servo to a specific angle (0–180 degrees). |
| 46 | + |
| 47 | +- **set_duty(duty_us)** |
| 48 | + Set the PWM pulse width in microseconds directly. |
| 49 | + |
| 50 | +- **get_duty()** |
| 51 | + Get the current PWM pulse width in microseconds. |
| 52 | + |
| 53 | +- **stop()** |
| 54 | + Move the servo to the neutral (dead zone) position. |
| 55 | + |
| 56 | +- **deinit()** |
| 57 | + Deinitialize the PWM object to free hardware resources. |
| 58 | + |
| 59 | +--- |
| 60 | + |
| 61 | +**Notes:** |
| 62 | +- The servo must be powered appropriately; do not power directly from the Pico's 3.3V pin if your servo draws significant current. |
| 63 | +- The control pin must support PWM output. |
| 64 | +- Typical servos expect a 50Hz PWM signal with pulse widths between 500μs (0°) and 2500μs (180°). |
| 65 | + |
| 66 | +## Class Unit Test |
| 67 | + |
| 68 | +```python |
| 69 | +from machine import Pin, PWM |
| 70 | +from servo import Servo |
| 71 | +from time import sleep |
| 72 | + |
| 73 | +pwm = PWM(Pin(16)) |
| 74 | +servo = Servo(pwm) |
| 75 | + |
| 76 | +print("Testing set_angle()") |
| 77 | +for angle in [0, 90, 180, 90, 0]: |
| 78 | + servo.set_angle(angle) |
| 79 | + print(f"Set angle to {angle}°; duty = {servo.get_duty()}us") |
| 80 | + sleep(1) |
| 81 | + |
| 82 | +print("Testing stop()") |
| 83 | +servo.stop() |
| 84 | +print(f"Servo stopped at duty = {servo.get_duty()}us") |
| 85 | + |
| 86 | +servo.deinit() |
| 87 | +print("Servo deinitialized.") |
| 88 | +``` |
| 89 | + |
| 90 | +## Class Implementation |
| 91 | + |
| 92 | +```python |
| 93 | +from machine import PWM |
| 94 | + |
| 95 | +class Servo: |
| 96 | + """Servo Class for controlling pulse density modulation servos. |
| 97 | +
|
| 98 | + This class provides an interface for controlling servo motors using PWM signals. |
| 99 | + It handles the conversion between angles (0-180 degrees) and pulse widths. |
| 100 | +
|
| 101 | + Args: |
| 102 | + pwm (PWM): A PWM object to control the servo. |
| 103 | + min_us (int, optional): Minimum pulse width in microseconds. Defaults to 500. |
| 104 | + max_us (int, optional): Maximum pulse width in microseconds. Defaults to 2500. |
| 105 | + dead_zone_us (int, optional): Pulse width for the servo's neutral position. Defaults to 1500. |
| 106 | + freq (int, optional): PWM frequency in Hz. Defaults to 50. |
| 107 | + """ |
| 108 | + |
| 109 | + def __init__( |
| 110 | + self, |
| 111 | + pwm: PWM, |
| 112 | + min_us=500, |
| 113 | + max_us=2500, |
| 114 | + dead_zone_us=1500, |
| 115 | + freq=50, |
| 116 | + ): |
| 117 | + self.pwm = pwm |
| 118 | + self.pwm.freq(freq) |
| 119 | + self._move_period_ms = 1000 // freq |
| 120 | + min_us = min_us if min_us > 0 else 0 |
| 121 | + max_us = max_us if min_us < max_us < (1000 // freq) * 1000 else 0 |
| 122 | + self._curr_duty = 0 |
| 123 | + self.dead_zone_us = dead_zone_us |
| 124 | + |
| 125 | + def set_duty(self, duty_us: int): |
| 126 | + self._curr_duty = duty_us |
| 127 | + self.pwm.duty_ns(duty_us * 1000) |
| 128 | + |
| 129 | + def set_angle(self, angle: int): |
| 130 | + angle = min(max(angle, 0), 180) |
| 131 | + duty_us = int(500 + (angle / 180) * 2000) |
| 132 | + self.set_duty(duty_us) |
| 133 | + |
| 134 | + def get_duty(self) -> int: |
| 135 | + return self._curr_duty |
| 136 | + |
| 137 | + def stop(self): |
| 138 | + self.set_duty(self.dead_zone_us) |
| 139 | + |
| 140 | + def deinit(self): |
| 141 | + self.pwm.deinit() |
| 142 | +``` |
0 commit comments