Friday, February 17, 2017

Python Servo Module/Library for Raspberry Pi

Python Servo Library


We will now create a servo wrapper class which will add some convenience methods and attributes using software PWM. It will be designed so that it can be imported into other code or used by itself to calibrate or test a servo.

From here we will start putting together a Python Robot class similar to what we did for AVA in C++.

This library is based on the code we used to calibrate the sensor and still has some jitter. In due course we will look at improving the code using hardware DMA (perhaps using the pigpio library this time).

The code is well commented, so with no further ado, here it is.

# RS_Servo.py - Wrapper Servo Class for Raspberry Pi
#
# 15 February 2017 - 1.0 Original Issue
# 18 February 2017 - 1.1 Modified with @property
#
# Reefwing Software

import RPi.GPIO as GPIO
from time import sleep

# Private Attributes
__CALIBRATE      = "1"
__SET_DUTY_CYCLE = "2"
__SCAN           = "3"
__QUIT           = "q"

class Servo:
    # Servo class wrapper using RPi.GPIO PWM

    # Servo private class attribute - to count servo instances
    __number = 0

    def __init__(self, pin, min_dc=2, max_dc=10, freq=50):
        # Create a new servo instance with default pulse width limits if not provided

        # Configure the Pi to use pin names (i.e. BCM) and allocate I/O
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(pin, GPIO.OUT)

        # Create PWM channel on the servo pin with a frequency of freq - default 50Hz
        self.PWM = GPIO.PWM(pin, freq)

        # Increment Servo instances
        type(self).__number += 1     
        
        # Instance attributes
        self.pin = pin
        self.min_duty_cycle = min_dc
        self.max_duty_cycle = max_dc
        self.duty_cycle = self.get_centre(min_dc, max_dc)
        self.angle = 0

    def __str__(self):
        # Return string representation of servo
        return "Servo: pin - {0}, MIN_DC - {1}, MAX_DC - {2}, DC - {3}".format(self.pin, self.min_duty_cycle, self.max_duty_cycle, self.duty_cycle)

    def start(self):
        # Start PWM
        self.PWM.start(self.duty_cycle)

    def stop(self):
        # Stop PWM
        self.PWM.stop()

    def centre(self):
        # Move servo to the centre position
        centre = self.get_centre(self.min_duty_cycle, self.max_duty_cycle)
        self.duty_cycle = centre

    def min_dc(self):
        # Move servo to minimum duty cycle position
        self.duty_cycle = self.min_duty_cycle

    def max_dc(self):
        # Move servo to maximum duty cycle position
        self.duty_cycle = self.max_duty_cycle

    def scan(self, min_dc=None, max_dc=None):
        # Scans from min_dc to max_dc - defaults to max and min duty cycle
        min_dc = (min_dc or self.min_duty_cycle)
        max_dc = (max_dc or self.max_duty_cycle)
        centre = self.get_centre(min_dc, max_dc)
        self.duty_cycle = min_dc
        sleep(1)
        self.duty_cycle = centre
        sleep(1)
        self.duty_cycle = max_dc
        sleep(1)
        self.duty_cycle = centre
        sleep(1)

    def cal_duty_cycle(self, dc):
        # Set duty cycle for servo - not clamped
        self.PWM.ChangeDutyCycle(dc)

    def cleanup(self):
        # Stop PWM channel for servo and centre
        self.centre()
        sleep(1)
        self.stop()

    @property
    def duty_cycle(self):
        return self.__duty_cycle

    @duty_cycle.setter
    def duty_cycle(self, dc):
        # Set duty cycle for servo - clamped to max and min duty cycle
        dc = self.clamp(dc, self.min_duty_cycle, self.max_duty_cycle)
        self.PWM.ChangeDutyCycle(dc)
        self.__duty_cycle = dc

    @staticmethod
    def get_centre(min_dc, max_dc):
         return min_dc + (max_dc - min_dc)/2

    @staticmethod
    def clamp(dc, min_dc, max_dc):
        return max(min(dc, max_dc), min_dc)
    
    @classmethod
    def count(cls):
        # Returns the number of Servo instances
        return cls.__number

def main():
    try:
        servo = Servo(6)    # create a new servo to be controlled from GPIO pin 5
        servo.start()       # start PWM for servo

        print(servo)
        print("Servo instances: ", Servo.count())

        while True:
            test = input("\nSelect Action - (1) Calibrate, (2) Set max/min duty cycle, or (3) Scan: ")

            if test == __CALIBRATE:
                while True:
                    response = input("Enter Duty Cycle (q = quit): ")
                    if response == __QUIT:
                        break;
                    servo.cal_duty_cycle(float(response))
            elif test == __SET_DUTY_CYCLE:
                min_dc = float(input("Enter minimum duty cycle: "))
                max_dc = float(input("Enter maximum duty cycle: "))
                servo.min_duty_cycle = min_dc
                servo.max_duty_cycle = max_dc
                print(servo)
            else:
                while True:
                    # Scan Servo from min duty cycle to max duty cycle
                    print("\nScanning (CTRL c to exit)...")
                    servo.scan()
            
    except KeyboardInterrupt:
        print("\n-- CTRL-C: Terminating program --")
    finally:
        print("Cleaning up PWM and GPIO...")
        servo.cleanup()
        GPIO.cleanup()
        print("Done.")

if __name__ == "__main__":
    # execute only if run as a script
    main()
    

No comments:

Post a Comment