Using a controllerless servo on the Raspberry Pi with Perl

I put out a notice not long ago that I was contemplating writing a guide to using the Raspberry PI with Perl.

One person who pointed out one minor mistake of mine with follow up with some other questions, asked about how to run a servo without needing a controller board. I realized that I hadn't exposed a couple of functions in the core WiringPi::API distribution that allowed a user to configure the PWM frequency, which is required as the Pi default doesn't play nice with typical servos.

The default PWM base frequency on a Pi is 19.2MHz, which is then divided by the clock signal (default: 32) and the PWM range (0-1023). So to get the default operating frequency:

# base   range   clck     operational freq
19.2e6 / 1024  /  32   ==     586Hz

To get this down to 50Hz required for a typical servo, I bumped up the range to 2000 (nice round number), and then just bounced around with the clock signal divider until I hit 50:

19.2e6 / 2000 / 192 == 50Hz

To be honest, I found the formula online, but then read through the datasheet for the Pi, and went on my way to not just copy and paste, but figure out exactly what frequency meant, what the divisors meant and then felt comfortable knowing exactly how PWM works ;)

So, for full left, the servo requires a pulse of 50Hz for ~1ms (PWM 50), centre is ~1.5ms (PWM 150) and full right is ~2.5ms (PWM 250). My servo required me to tweak these numbers a tiny bit to get the full 180 degree motion.

Anyway, to some code. I've commented the code as to what's happening and when, but an overall is that when started, the servo will go full-left, wait a sec, then swing from left-to-right, then back right-to-left until a SIGINT (CTRL-C) is caught, at which time, it puts the servo back to left position, then puts the pin back to INPUT mode so that if a different software is run after, the pin won't still be in PWM mode.

Unfortunately, at this time, we still require sudo for PWM functionality. It's being looked at. It's the only thing left that requires root.

use warnings;
use strict;

use RPi::WiringPi;
use RPi::WiringPi::Constant qw(:all);

die "need root!\n" if $> != 0;

use constant {
    LEFT    => 60,
    RIGHT   => 255,
    CENTRE  => 150,
    PIN     => 18,
    DIVISOR => 192,
    RANGE   => 2000,
    DELAY   => 0.001,
};

# set up a signal handler for CTRL-C

my $run = 1;
$SIG{INT} = sub {
    $run = 0;
};

# create the Pi object

my $pi = RPi::WiringPi->new;

# create a signal pin, set mode to PWM output

my $s = $pi->pin(PIN);
$s->mode(PWM_OUT);

# configure PWM to 50Hz for the servo

$pi->pwm_mode(PWM_MODE_MS);
$pi->pwm_clock(DIVISOR);
$pi->pwm_range(RANGE);

# set the servo to left max

$s->pwm(LEFT);

sleep 1;

while ($run){
    for (LEFT .. RIGHT){
        # sweep all the way left to right
        $s->pwm($_);
        select(undef, undef, undef, DELAY);
    }

    sleep 1;

    for (reverse LEFT .. RIGHT){
        # sweep all the way right to left
        $s->pwm($_);
        select(undef, undef, undef, DELAY);
    }

    sleep 1;
}

# set the pin back to INPUT

$s->pwm(LEFT);
$s->mode(INPUT);

There were three distributions that required updates for this to work, the last one (RPi::WiringPi) isn't yet up to the CPAN as I'm making slight other changes to it, but it can be found on Github. One of the updates will be the ability to get a servo "object" (ie my $servo = $pi->servo($pin_num);), that'll take care of all of the frequency stuff in the background, then put it all back to default state when your program exits.

Note that the Pi may struggle to power the servo and it may cause a low-voltage situation, so it's best you power your 5v servo from an alternate source (I just happen to have a few powered-up Arduino's nearby all with 5v pins accessible). Also note that even though the +/- of the servo is 5v, you can safely connect the signal pin on it to the 3.3v GPIO on the Pi as on the servo, the pin is input only (ie. it doesn't feed back to the Pi).

2 Comments

Nice! Thanks :) Can you give some specs or link what kind of servo did you use?

Leave a comment

About Steve Bertrand

user-pic Just Another Perl Hacker