As I’ve started to find my feet in using the Raspberry Pi with Windows 10 IoT Core, I’ve tried to take some of the common hardware sensors that I’ve used with my Arduino and develop ways to make them work with the Raspberry Pi.

Obviously there’s a software challenge in porting that code across to C# from the Arduino programming language – but there are also interesting challenges presented by the hardware differences also.

When writing this code, I found it helpful to refer to the information at these links:

http://stackoverflow.com/questions/30124861/ultrasonic-sensor-raspberry-pi-2-c-sharp-net

http://www.guruumeditation.net/en/digital-io-with-windows-10-iot-raspberry-pi-2-and-the-ultrasonic-ranging-module-hc-sr04/

How to talk to the HC-SR04

I’ve previously used the HC-SR04 as an ultrasonic distance measurement device with my Arduino. It’s a fantastic peripheral device, which I’ve found to be reliable and intuitive to use. It was first on my list of devices to test with the Raspberry Pi.

The protocol for using it is:

  1. Set the trigger pin to logic zero for at least 10 microseconds, and then bring this pin to logic 1.
  2. Immediately after this, measure the length of time that the pulse sent through the echo pin is at logic 1.

I had read in several online sources that C# on the Raspberry Pi wasn’t capable of sending or measuring pulses at this level of fidelity so I was skeptical whether I could make the HC-SR04 work directly with the Pi 3, but I wanted to give it a try.

The usual way of holding a pin at a particular level is to set it to that level, and then call a “Sleep” function (effectively the same as Thread.Sleep or Task.Delay) for the length of time that you want to hold it low.

Selecting a pin with C# and setting it as input or output is very easy – the code below shows how to do it.

Since I wanted to hold the pin low for only 10 microseconds, I decided to use the ManualResetEvent object (which I’ve blogged about before), and tell it to wait for a time determined by TimeSpan.FromMilliseconds(0.01). I put this into its own static function.

private static ManualResetEvent manualResetEvent = new ManualResetEvent(false);
 
public static void Sleep(int delayMicroseconds)
{
    manualResetEvent.WaitOne(
        TimeSpan.FromMilliseconds((double)delayMicroseconds / 1000d));
}

This method has a flaw – the Pi and C# presently can’t handle signals with microsecond accuracy. I’ll post more about this soon.

Next, I wanted to measure the length of the pulse back on the echo pin. First I set this pin to be an input. Ideally I needed something similar to the pulseIn feature available on the Arduino, but this isn’t available as a standard method through C#.

It’s reasonably simple to replicate this function in C# however.

private static Stopwatch stopWatch = new Stopwatch();
 
public static double GetTimeUntilNextEdge(GpioPin pin, GpioPinValue edgeToWaitFor)
{
    stopWatch.Reset();
 
    while (pin.Read() != edgeToWaitFor) { };
 
    stopWatch.Start();
 
    while (pin.Read() == edgeToWaitFor) { };
 
    stopWatch.Stop();
 
    return stopWatch.Elapsed.TotalSeconds;
}

I put both of these static functions into a static class named Gpio.

So my code presently was quite simple, but should initiate a request to read the distance in front of the device, and then measure the length of the pulse that was returned.

public class HCSR04
{
    private GpioPin triggerPin { get; set; }
    private GpioPin echoPin { get; set; }
    private const double SPEED_OF_SOUND_METERS_PER_SECOND = 343;
 
    public HCSR04(int triggerPin, int echoPin)
    {
        GpioController controller = GpioController.GetDefault();
 
        //initialize trigger pin.
        this.triggerPin = controller.OpenPin(triggerPin);
        this.triggerPin.SetDriveMode(GpioPinDriveMode.Output);
 
        //initialize echo pin.
        this.echoPin = controller.OpenPin(echoPin);
        this.echoPin.SetDriveMode(GpioPinDriveMode.Input);
    }
 
    private double LengthOfHighPulse
    {
        get
        {
            // The sensor is triggered by a logic 1 pulse of 10 or more microseconds.
            // We give a short logic 0 pulse first to ensure a clean logic 1.
            this.triggerPin.Write(GpioPinValue.Low);
            Gpio.Sleep(5);
            this.triggerPin.Write(GpioPinValue.High);
            Gpio.Sleep(10);
            this.triggerPin.Write(GpioPinValue.Low);
 
            // Read the signal from the sensor: a HIGH pulse whose
            // duration is the time (in microseconds) from the sending
            // of the ping to the reception of its echo off of an object.
            return Gpio.GetTimeUntilNextEdge(echoPin, GpioPinValue.High, 100);
        }
    }
 
    public double Distance
    {
        get
        {
            // convert the time into a distance
            // duration of pulse * speed of sound (343m/s)
            // remember to divide by two because we're measuring the time for the signal to reach the object, and return.
            return (SPEED_OF_SOUND_METERS_PER_SECOND / 2) * LengthOfHighPulse;
        }
    }
}

Time to connect up the HC-SR04

One thing to be particularly aware of is that the HC-SR04 takes a 5v input, and echos a 5v signal. The Raspberry Pi’s pins can handle a potential difference maximum of 3.3v – if you’re senting 5v to your Pi, sooner or later you’re going to burn it out. Fortunately it’s very simple to divide the voltage returned with a couple of resistors, bringing it down to 3.3v.

I connected the HC-SR04 and voltage divider to my Pi…and it worked.

And then it stopped. Argh!

I found that the hardware sometimes freezes up – often sending another request for a reading fixes the problem. So if I wrap the function to read a pulse in an asynchronous call which times out after 50ms, this effectively resolves the problem for me. I blogged about this technique here, and changed my function to measure the signal so that it also has a maximum time to wait before returning a default value of -1.

public static double GetTimeUntilNextEdge(GpioPin pin, GpioPinValue edgeToWaitFor, int maximumTimeToWaitInMilliseconds)
{
    var t = Task.Run(() =>
    {
        stopWatch.Reset();
 
        while (pin.Read() != edgeToWaitFor) { };
 
        stopWatch.Start();
 
        while (pin.Read() == edgeToWaitFor) { };
 
        stopWatch.Stop();
 
        return stopWatch.Elapsed.TotalSeconds;
    });
 
    bool isCompleted = t.Wait(TimeSpan.FromMilliseconds(maximumTimeToWaitInMilliseconds));
 
    if (isCompleted)
    {
        return t.Result;
    }
    else
    {
        return -1d;
    }
}

Next time I’m going to look at the issues with the Pi and sending signals with microsecond resolution.