Setting the servo angle with a Raspberry Pi and Adafruit 16-channel servo controller

Servo hooked up to a Raspberry Pi

Controlling a servo from the Raspberry Pi isn’t hard, except for one minor point of confusion, which I’ll get to in a bit. 

But, first some background. A servo is basically a geared motor specifically designed for fine control. If you imagine a typical remote control car, a servo is what makes the front wheels turn left and right, while a motor provides propulsion (e.g. forward and backwards movement). Standard servos generally also only rotate around a 170 degree arc (although you can also get continuous rotation servos that can keep spinning like a regular motor). Servos are designed to accept Pulse Width Modulation signals, and based on this signal, a servo will either change position or change its speed. 

The Raspberry Pi has a pin that can be used to send PWM signals (here’s a nice tutorial) but if you want to control more than one device using PWM, you’ll need to use something more. I decided to use Adafruit’s 16-channel driver, which as the name implies, allows you to control up to 16 servos from one board. The Raspberry Pi talks to the controller board over a serial protocol called I2C, and the cool thing about I2C is that you can chain multiple devices, so it won’t prevent me from also using some I2C sensors or motor controllers in the future.

The Adafruit website has a great tutorial (with code!) for using the servo driver, but there’s one thing that isn’t entirely obvious: how to set the servo to a specific angle of rotation. Although it’s not clear from the tutorial and sample code, the servoMin and servoMax values actually represent pulse widths that will set standard (i.e. non-continuous) servos to the extremes of their range of motion (i.e. “far left” and “far right”). If you set the pulse to half way between servoMin and servoMax, the servo will be more or less centered. From there, you can calculate the pulse width required to rotate the servo to either direction in any angle you desire.

Long story short, I wrote a quick ‘n dirty function like this:

def setAngle(channel, angle, delta=170):
        Sets angle of servo (approximate).
        :param channel: Channel servo is attached to (0-15)
        :param angle:   off of center, ( -80 through 80)
        :param delta:   Angle changed from past position. Used to calculate
                        delay, since rotating thru a larger arc takes longer
                        than a shorter arc.
    delay = max(delta * 0.003, 0.03)        # calculate delay
    zero_pulse = (servoMin + servoMax) / 2  # half-way == 0 degrees
    pulse_width = zero_pulse - servoMin     # maximum pulse to either side 
    pulse = zero_pulse + (pulse_width * angle / 80)
    print "angle=%s pulse=%s" % (angle, pulse)
    pwm.setPWM(channel, 0, int(pulse))
    time.sleep(delay)  # sleep to give the servo time to do its thing

It works pretty well for the servo I have, but YMMV. One thing to note, also, is the delay. Servos rotate at different speeds, and is usually given in “seconds to rotate 60 degrees”. If you look at the technical specs of servos, this should be represented in a number that looks like “0.07 sec/60°”. Based on that, you can calculate how long it should take the servo to change its position. The function shown above is hard-coded to work for a servo that takes 0.15 seconds for 60 degrees of motion. I’ll write up some better code someday, but for now, this’ll get me to the next step.