Monday, June 12, 2017

Controlling analog PWM servos from a Raspberry Pi

As part of a project I'm working on I needed a way to generate motion from my Raspberry Pi. It needed to be a precise variable motion rather than the all or nothing type motion of a straight DC motor. I opted to go with PWM (Pulse Width Modulation) based servos as I'm familiar with them and have some on hand for RC planes. Stepper motors or a DC motor with a rotary encoder were other options, but I have neither in my spare parts bin.

So PWM servos it is. Now, how to control them from the Pi? Getting the Pi to feed the hard real-time response requirements of a servo motor directly can be difficult, so I opted instead to offload the task to an Adafruit 16-Channel PWM / Servo HAT.

Some assembly required

Software

Because I'm a .NET person I used Windows IoT to program the Pi. Adafruit even provide the AdafruitClassLibrary for Windows IoT. The rough code, less exception handling.

var i2cSettings = new Windows.Devices.I2c.I2cConnectionSettings(0x40);

var gpio = Windows.Devices.Gpio.GpioController.GetDefault();

Pca9685 hat = new Pca9685(0x40);
await hat.InitPCA9685Async();

hat.SetPWMFrequency(50);

ushort servoMinPulseLength = 150;
ushort servoMaxPulseLength = 600;

hat.SetAllPWM(0, servoMinPulseLength);

           
while (true)
{
    hat.SetAllPWM(0, servoMinPulseLength);

    await System.Threading.Tasks.Task.Delay(500);

    hat.SetAllPWM(0, servoMaxPulseLength);

    await System.Threading.Tasks.Task.Delay(500);
}

The exception handling turned out to be one of the harder parts of getting this to work. I kept getting debug messages like Value cannot be null.:I2C Write Exception: {0}. Not very helpful. Once I found the corresponding Github project and worked directly from the source the problem was easier to isolate. They are catching the majority of the exceptions in their code and dumping them out to the debug log and then carrying on. This was causing all sorts of problems, with the first occuring in the `InitPCA9685Async` call. The ultimate resolution was to up the Microsoft.NETCore.UniversalWindowsPlatform NuGet package to at least 5.2.2.

After that I just needed to set sensible values for the PWMFrequency and minimum and maximum pulse lengths. Most of the demo code I've seen has a default frequency of 1000. That seemed way to high and the servos became a jittery mess. The general consensus for an analog RC servo is somewhere in the 30Hz to 60Hz range. Some good reading on the difference between PPM and PWM.

The Result

All going well I'll have more posts soon to bring this together with the other parts I'm working on.