Introduction to PWM and Servo Motors
Pulse Width Modulation (PWM) is a technique used to control analog devices using digital signals. With a Raspberry Pi, PWM can be implemented to precisely control servo motors, which are essential components in robotics, automation, and many DIY electronics projects.
servo motor has three wires: power (usually red), ground (black or brown), and control (yellow, orange, or white). The control wire receives PWM signals that determine the motor's position. By manipulating the timing of these signals, you can make the servo rotate to specific angles with high accuracy.
Understanding PWM for Servo Control
Most standard hobby servos require a PWM signal with the following characteristics:
- Frequency of 50Hz (a 20ms period)
- Pulse width typically between 1ms to 2ms for the standard rotation range (usually 0-180 degrees)
The relationship between pulse width and servo position is:
- 1ms pulse = 0 degrees (or maximum counter-clockwise)
- 1.5ms pulse = 90 degrees (or center position)
- 2ms pulse = 180 degrees (or maximum clockwise)
Note: Different servo models may have slightly different pulse width and range specifications. Always check your servo's datasheet for the exact values.
Hardware Setup
To connect a servo motor to your Raspberry Pi:
- Connect the red (power) wire to a 5V GPIO pin (pins 2 or 4)
- Connect the black or brown (ground) wire to any ground pin (pins 6, 9, 14, 20, etc.)
- Connect the yellow, orange, or white (control) wire to a GPIO pin that supports PWM (GPIO 12, 13, 18, or 19)
Important: Servo motors can draw significant current. For small servos in simple projects, you can power them directly from the Pi. For more powerful servos or multiple servos, use an external power supply to avoid damaging your Raspberry Pi.
Software Requirements
Before coding, ensure your Raspberry Pi has the necessary software:
- Update your system:
sudo apt update && sudo apt upgrade - Install the RPi.GPIO library:
sudo apt install python3-rpi.gpio - For more precise PWM control, install the pigpio library:
sudo apt install pigpio - Enable and start the pigpio daemon:
sudo systemctl enable pigpiodsudo systemctl start pigpiod
Basic Servo Control with RPi.GPIO
Here's a simple Python script to control a servo using the RPi.GPIO library:
import RPi.GPIO as GPIOimport time# Set the GPIO modeGPIO.setmode(GPIO.BCM)GPIO.setwarnings(False)# Define the servo GPIO pinservo_pin = 18# Setup the servo pin as an outputGPIO.setup(servo_pin, GPIO.OUT)# Create PWM instance with 50Hz frequencyservo = GPIO.PWM(servo_pin, 50)# Initialize servo at neutral position (90 degrees)servo.start(7.5) # 7.5% duty cycle for neutral positiontry: while True: # Move to 0 degrees print("Moving to 0 degrees") servo.ChangeDutyCycle(2.5) # ~2.5% duty cycle for 0 degrees time.sleep(1) # Move to 90 degrees print("Moving to 90 degrees") servo.ChangeDutyCycle(7.5) # ~7.5% duty cycle for 90 degrees time.sleep(1) # Move to 180 degrees print("Moving to 180 degrees") servo.ChangeDutyCycle(12.5) # ~12.5% duty cycle for 180 degrees time.sleep(1) # Return to neutral position print("Moving back to center") servo.ChangeDutyCycle(7.5) time.sleep(1) except KeyboardInterrupt: print("Stopping...")# Clean up on exitservo.stop()GPIO.cleanup()print("Cleanup completed") Advanced Control with pigpio Library
The pigpio library offers more precise PWM control by utilizing hardware features:
import pigpioimport time# Connect to pigpio daemonpi = pigpio.pi()# Check if connection was successfulif not pi.connected: print("Error: Could not connect to pigpio daemon") exit(1)# Define the servo pinservo_pin = 18# Function to move servo to angledef move_servo(pin, angle): # Convert angle to pulse width (in microseconds) # Standard servos use 1000-2000us range for 0-180 degrees # Some servos might use different ranges, adjust as needed pulse_width = 1000 + (angle * 1000 / 180) pi.set_servo_pulsewidth(pin, pulse_width)try: while True: # Move to 0 degrees print("Moving to 0 degrees") move_servo(servo_pin, 0) time.sleep(1) # Move to 90 degrees print("Moving to 90 degrees") move_servo(servo_pin, 90) time.sleep(1) # Move to 180 degrees print("Moving to 180 degrees") move_servo(servo_pin, 180) time.sleep(1) # Sweep back and forth print("Sweeping from 0 to 180 degrees") for angle in range(0, 181): move_servo(servo_pin, angle) time.sleep(0.01) print("Sweeping from 180 to 0 degrees") for angle in range(180, -1, -1): move_servo(servo_pin, angle) time.sleep(0.01) except KeyboardInterrupt: print("\nStopping...")# Turn off servo and disconnect on exitpi.set_servo_pulsewidth(servo_pin, 0)pi.stop()print("Cleanup completed") Creating a Servo Controller Class
For more complex projects, creating a dedicated class for servo control can help organize your code:
import pigpioimport timeclass ServoController: def __init__(self, pin, min_pulse=1000, max_pulse=2000): """ Initialize servo controller Args: pin: GPIO pin number min_pulse: Minimum pulse width in microseconds (default: 1000) max_pulse: Maximum pulse width in microseconds (default: 2000) """ self.pin = pin self.pi = pigpio.pi() self.min_pulse = min_pulse self.max_pulse = max_pulse if not self.pi.connected: raise ConnectionError("Could not connect to pigpio daemon") def move_to_angle(self, angle): """ Move servo to specific angle Args: angle: Angle in degrees (0-180) """ # Clamp angle to valid range angle = max(0, min(180, angle)) # Convert angle to pulse width pulse_width = self.min_pulse + (angle * (self.max_pulse - self.min_pulse) / 180) # Set pulse width self.pi.set_servo_pulsewidth(self.pin, pulse_width) def sweep(self, start_angle=0, end_angle=180, delay=0.01): """ Sweep servo between angles Args: start_angle: Starting angle in degrees end_angle: Ending angle in degrees delay: Delay between steps in seconds """ # Determine direction if start_angle < end_angle: angles = range(start_angle, end_angle + 1) else: angles = range(start_angle, end_angle - 1, -1) # Sweep through angles for angle in angles: self.move_to_angle(angle) time.sleep(delay) def cleanup(self): """Turn off servo and disconnect from pigpio""" self.pi.set_servo_pulsewidth(self.pin, 0) self.pi.stop()# Example usageif __name__ == "__main__": try: # Initialize servo on GPIO 18 servo = ServoController(pin=18) # Move to middle position print("Moving to center position (90 degrees)") servo.move_to_angle(90) time.sleep(1) # Sweep from 0 to 180 degrees print("Sweeping from 0 to 180 degrees") servo.sweep(0, 180, 0.02) time.sleep(1) # Sweep back from 180 to 0 degrees print("Sweeping from 180 back to 0 degrees") servo.sweep(180, 0, 0.02) time.sleep(1) # Move to specific angles print("Moving to specific angles: 45, 90, 135 degrees") servo.move_to_angle(45) time.sleep(1) servo.move_to_angle(90) time.sleep(1) servo.move_to_angle(135) time.sleep(1) except KeyboardInterrupt: print("\nStopping...") servo.cleanup() print("Cleanup completed") Common Issues and Troubleshooting
Servo Jittering or Not Moving Smoothly
- Check power supply - servos might malfunction if not receiving adequate current
- Use the pigpio library for more precise PWM signals
- Add a capacitor across the servo's power and ground pins to reduce electrical noise
- Ensure the control wire is properly connected and not picking up interference
Servo Not Moving Full Range
- Adjust the min_pulse and max_pulse values to match your servo's specifications
- Some servos have limited rotation ranges (e.g., 90) rather than full 180 rotation
- Check if mechanical constraints are limiting the servo's movement
- Verify that your PWM duty cycle calculations are correct
Advanced Techniques
Smooth Motion with Interpolation
For smoother servo movements, you can use interpolation to calculate intermediate positions:
import timeimport mathdef smooth_move(pin, start_angle, end_angle, duration, move_func): """ Move servo smoothly from start to end angle over specified duration Args: pin: GPIO pin number start_angle: Starting angle in degrees end_angle: Target angle in degrees duration: Movement duration in seconds move_func: Function to move servo to a given angle """ num_steps = 20 # Number of intermediate positions step_time = duration / num_steps for step in range(num_steps + 1): # Calculate interpolation factor (0 to 1) t = step / num_steps # Apply easing function for smoother motion (ease-in-out cubic) t_smooth = t * t * (3 - 2 * t) # Calculate current angle current_angle = start_angle + (end_angle - start_angle) * t_smooth # Move to current angle move_func(pin, current_angle) time.sleep(step_time)# Example usage with pigpiodef move_servo(pin, angle): pulse_width = 1000 + (angle * 1000 / 180) pi.set_servo_pulsewidth(pin, pulse_width)# Move smoothly from 0 to 180 over 2 secondssmooth_move(18, 0, 180, 2.0, move_servo) Multiple Servo Control
When controlling multiple servos, you can extend the ServoController class or manage multiple instances:
import pigpioimport time# Connect to pigpio daemonpi = pigpio.pi()if not pi.connected: print("Error: Could not connect to pigpio daemon") exit(1)class MultiServoController: def __init__(self): self.pi = pigpio.pi() self.servos = {} if not self.pi.connected: raise ConnectionError("Could not connect to pigpio daemon") def add_servo(self, servo_id, pin, min_pulse=1000, max_pulse=2000): """ Add a servo to the controller Args: servo_id: Identifier for the servo pin: GPIO pin number min_pulse: Minimum pulse width in microseconds max_pulse: Maximum pulse width in microseconds """ self.servos[servo_id] = { 'pin': pin, 'min_pulse': min_pulse, 'max_pulse': max_pulse } def move_servo(self, servo_id, angle): """ Move a specific servo to an angle Args: servo_id: Identifier for the servo angle: Target angle in degrees """ if servo_id not in self.servos: raise ValueError(f"Servo {servo_id} not found") servo = self.servos[servo_id] # Clamp angle to valid range angle = max(0, min(180, angle)) # Convert angle to pulse width pulse_width = servo['min_pulse'] + (angle * (servo['max_pulse'] - servo['min_pulse']) / 180) # Set pulse width self.pi.set_servo_pulsewidth(servo['pin'], pulse_width) def cleanup(self): """Turn off all servos and disconnect""" for servo_id, servo in self.servos.items(): self.pi.set_servo_pulsewidth(servo['pin'], 0) self.pi.stop()# Example usagetry: multi_servo = MultiServoController() # Add two servos multi_servo.add_servo('pan', 18) # Pan servo on pin 18 multi_servo.add_servo('tilt', 19) # Tilt servo on pin 19 # Move servos independently multi_servo.move_servo('pan', 90) multi_servo.move_servo('tilt', 90) time.sleep(1) # Create a simple pattern for i in range(5): # Pan left and right multi_servo.move_servo('pan', 45) time.sleep(0.5) multi_servo.move_servo('pan', 135) time.sleep(0.5) # Tilt up and down multi_servo.move_servo('tilt', 60) time.sleep(0.5) multi_servo.move_servo('tilt', 120) time.sleep(0.5) # Return to center multi_servo.move_servo('pan', 90) multi_servo.move_servo('tilt', 90) time.sleep(1) except KeyboardInterrupt: print("\nStopping...") finally: multi_servo.cleanup() print("Cleanup completed") Applications and Project Ideas
With servo motor control on your Raspberry Pi, you can build various projects:
- Robotic Arms: Multiple servos working together to create articulated movement.
- Camera Mounts: Pan-and-tilt systems for surveillance, photography, or time-lapse videos.
- Smart Locks: Servo-controlled latch mechanisms for automated door locking.
- Automated Pet Feeders: Precise dispensing of food at scheduled times.
- Drawing Robots: X-Y plotters using two servos for creating drawings.
- Weather Stations: Servo-controlled directional sensors or moving parts.
Conclusion
Controlling servo motors with PWM on a Raspberry Pi opens up numerous possibilities for interactive and automated projects. By understanding the fundamentals of PWM and servo operation, you can create precise movement control with relatively simple Python code.
As you become more comfortable with basic servo control, consider exploring more advanced techniques like sensor integration, computer vision, and multi-servo coordination. The combination of the Raspberry Pi's computing power and the precision of servo motors provides a powerful platform for creating complex electronic systems and robotic applications.
