.net, Clean Code, Making, Raspberry Pi 3

A servo library in C# for Raspberry Pi – Part #3: Implementing the interface

Last time, I developed an interface that would allow me to control a servo directly from my Raspberry Pi 3 which is hosting Windows 10 IoT Core. In this post, I’ll describe an implementation of this interface. The code will a cleaner implementation of the code I got working in Part #1 of the series.

Let’s look at the interface I described last time:

public interface IServoController : IDisposable
{
    int Frequency { get; set; }
 
    double MaximumDutyCycle { get; set; }
 
    double MinimumDutyCycle { get; set; }
 
    int ServoPin { get; set; }
 
    Task Connect();
 
    void Go();
 
    IServoController SetPosition(int degree);
 
    IServoController AllowTimeToMove(int pauseInMs);
}

Implementing the interface

The code implementation is quite straightforward – I needed to specify the control pin for the servo, and to check that the Lightning provider is being used – so I put these items in the constructor.

public ServoController(int servoPin)
{
    if (LightningProvider.IsLightningEnabled)
    {
        LowLevelDevicesController.DefaultProvider = LightningProvider.GetAggregateProvider();
    }
 
    ServoPin = servoPin;
}

When I set the position of the servo, I have to calculate what duty cycle is necessary to move the servo’s wiper to that position. This is a very simple calculation, given that we know the duty cycles necessary to move to the minimum (0 degree) and maximum (180 degree) positions. The difference between the two extreme duty cycle values divided by 180 is the incremental value corresponding to 1 degree of servo movement. Therefore, we just multiply this increment by the number of degrees we want to move from the starting position, add the minimum duty cycle value, and this gives us the duty cycle corresponding to the servo position we want.

public IServoController SetPosition(int degree)
{
    ServoGpioPin?.Stop();
 
    // For example:
    // minimum duty cycle = 0.03 (0.6ms pulse in a period of 20ms) = 0 degrees
    // maximum duty cycle = 0.12 (2.4ms pulse in a period of 20ms) = 180 degrees
    // degree is between 0 and 180
    // => 0.0005 per degree [(0.12 - 0.03) / 180]
 
    var pulseWidthPerDegree = (MaximumDutyCycle - MinimumDutyCycle) / 180;
 
    var dutyCycle = MinimumDutyCycle + pulseWidthPerDegree * degree;
    ServoGpioPin.SetActiveDutyCyclePercentage(dutyCycle);
 
    return this;
}

The full code for the class is below – it’s also available here.

public class ServoController : IServoController
{
    public ServoController(int servoPin)
    {
        if (LightningProvider.IsLightningEnabled)
        {
            LowLevelDevicesController.DefaultProvider = LightningProvider.GetAggregateProvider();
        }
 
        ServoPin = servoPin;
    }
 
    public int Frequency { getset; } = 50;
 
    public double MaximumDutyCycle { getset; } = 0.1;
 
    public double MinimumDutyCycle { getset; } = 0.05;
 
    public int ServoPin { getset; }
 
    public int SignalDuration { getset; }
 
    private PwmPin ServoGpioPin { getset; }
 
    public async Task Connect()
    {
        var pwmControllers = await PwmController.GetControllersAsync(LightningPwmProvider.GetPwmProvider());
 
        if (pwmControllers != null)
        {
            // use the on-device controller
            var pwmController = pwmControllers[1];
 
            // Set the frequency, defaulted to 50Hz
            pwmController.SetDesiredFrequency(Frequency);
 
            ServoGpioPin = pwmController.OpenPin(ServoPin);
        }
    }
 
    public void Dispose()
    {
        ServoGpioPin?.Stop();
    }
 
    public void Go()
    {
        ServoGpioPin.Start();
        Task.Delay(SignalDuration).Wait();
        ServoGpioPin.Stop();
    }
 
    public IServoController SetPosition(int degree)
    {
        ServoGpioPin?.Stop();
 
        // For example:
        // minimum duty cycle = 0.03 (0.6ms pulse in a period of 20ms) = 0 degrees
        // maximum duty cycle = 0.12 (2.4ms pulse in a period of 20ms) = 180 degrees
        // degree is between 0 and 180
        // => 0.0005 per degree [(0.12 - 0.03) / 180]
 
        var pulseWidthPerDegree = (MaximumDutyCycle - MinimumDutyCycle) / 180;
 
        var dutyCycle = MinimumDutyCycle + pulseWidthPerDegree * degree;
        ServoGpioPin.SetActiveDutyCyclePercentage(dutyCycle);
 
        return this;
    }
 
    public IServoController AllowTimeToMove(int pauseInMs)
    {
        this.SignalDuration = pauseInMs;
 
        return this;
    }
}

Using this code

There are three key things to remember:

  1. Enable the Microsoft Lightning Provider’s “Direct Memory Mapped Driver” through the Pi’s web interface – described under the “Runtime Requirements” heading at the URL: https://developer.microsoft.com/en-us/windows/iot/win10/LightningProviders.htm
  2. In your Windows UWP project, change your package.appxmanifest to enable the necessary capabilities. Change the Package root node to include the xmlns.iot namespace, and add “iot” to the Ignorable Namespaces, i.e.
    <Package
        xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
        xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
        xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
        xmlns:iot="http://schemas.microsoft.com/appx/manifest/iot/windows10"
             IgnorableNamespaces="uap mp iot">

    b. Add the iot:Capability and DeviceCapability to the capabilities node, i.e.

    <Capabilities>
        <iot:Capability Name="lowLevelDevices" />
        <DeviceCapability Name="109b86ad-f53d-4b76-aa5f-821e2ddf2141" />
    </Capabilities>
  3. In your Windows UWP project:
    • Open the Reference Manager (to open the reference manager, right click on your project’s references and select “Add reference…”);
    • Expand “Universal Windows”;
    • Select “Extensions”;
    • Enable the “Windows IoT Extensions for the UWP”;
    • Click “OK”.

I’ve packaged the code into a NuGet package which is available here. I’ve also included a ReadMe for this library here.

So assuming that you’ve connected your servo’s control line to Pin GPIO 5 (pin 29 on the Pi 3) – then you can call a method like the one below to move to the 90 degree position:

private async void MoveServoToCentre()
{
    using (var servo = new ServoController(5))
    {
        await servo.Connect();
 
        servo.SetPosition(90).AllowTimeToMove(1000).Go();
    }
}

Conclusion

So that’s it for this series – obviously this is still Alpha code and I’ve only tested it on my own 9g Tower Pro servo. But hopefully this code and implementation will provide some inspiration for other makers out there who are trying to get a servo working with a Raspberry Pi 3 and Windows 10 IoT Core.

In the future, I’m planning to use the Adafruit servo driver to control several servos at once – this wouldn’t be possible with just the Raspberry Pi as it’s not powerful enough to drive numerous devices like a servo. I’ll write about this soon.