Code: HC-SR04 Ultraschall-Entfernungsmesser

Um den HC-SR04 Ultraschall-Entfernungssensor komfortabel mit dem .Net-Microcontroller mit C# benutzen zu können, habe ich eine Klasse entwickelt.

Als Trigger kann entweder ein PWM-Port oder ein normaler digitaler OutputPort verwenden werden.

HC-SR04 EntfernungsmesserDie Klasse arbeitet mit Interrupts. Der Benutzer dieser Klasse kann daher die aktuelle Distanz ohne Zeitverzögerung abfragen.

Alternativ kann auch das DistanceReceived-Event aboniert werden.
Falls nicht laufend die aktuelle Entfernung bereitgestellt sein muss, kann auch die GetDistance-Methode genutzt werden. Diese arbeitet jedoch synchron, sodass auf das Echo gewartet werden muss.

Im Beitrag Ultraschall-Entfernungsmesser HC-SR04 habe ich die Ansteuerung genauer beschrieben.

 

Beispielaufrufe

PWM / Distance-Property

Mit PWM wird alle 20 ms der Sensor getriggert.
Asynchron kann im Programmablauf auf die aktuelle Distance-Property zugegriffen werden.

HcSr04 distanceSensor =
    new HcSr04(new PwmTrigger(Cpu.PWMChannel.PWM_3), FEZCerbuinoBee.Pins.D1);

distanceSensor.Start();

while (true)
{
 Debug.Print("Distanz: " + distanceSensor.Distance.ToString() + " mm");
 Thread.Sleep(5);
}

Thread.Sleep(Timeout.Infinite);
PWM / DistanceReceived-Event

Wieder wird per PWM regelmäßig der Sensor getriggert.
Dieses Mal wird das DistanceReceived-Event aboniert, das immer gefeurt wird, sobald ein aktueller Entfernungswert ermittelt wurde.

HcSr04 distanceSensor =
    new HcSr04(new PwmTrigger(Cpu.PWMChannel.PWM_3), FEZCerbuinoBee.Pins.D1);

distanceSensor.DistanceReceived += (sender, e) =>
    {
        Debug.Print("Distanz: " + e.Distance + " mm");
    };

distanceSensor.Start();
PWM / GetDistance()-Methode (Synchron)

Im Programmablauf kann die Entfernung mit der GetDistance-Methode abgefragt werden.
Die Methode arbeitet synchron: es wird auf das Echo gewartet.

HcSr04 distanceSensor =
    new HcSr04(new PwmTrigger(Cpu.PWMChannel.PWM_3), FEZCerbuinoBee.Pins.D1);

while (true)
{
    Debug.Print("Distanz: " + distanceSensor.GetDistance().ToString() + " mm");
    Thread.Sleep(50);
}
OutputPort statt PWM

Falls kein PWM-Ausgang zur Verfügung steht, kannst du den OutputPortTrigger verwenden. Dieser triggert Timer-gesteuert den Sensor an, was etwas Prozessorlast verursacht.

// Initialisierung mit OutputPort-Trigger statt PWM-Trigger
HcSr04 distanceSensor =
    new HcSr04(new OutputPortTrigger(FEZCerbuinoBee.Pins.D5), FEZCerbuinoBee.Pins.D1);

// ...

 

Klasse

/// MIT-Lizenz
///
/// Copyright (c) 2015 Daniel Sevenig
///
/// Hiermit wird unentgeltlich jeder Person, die eine Kopie
/// der Software und der zugehörigen Dokumentationen (die
/// "Software") erhält, die Erlaubnis erteilt, sie uneingeschränkt
/// zu benutzen, inklusive und ohne Ausnahme dem Recht, sie
/// zu verwenden, kopieren, ändern, fusionieren, verlegen,
/// verbreiten, unterlizenzieren und/oder zu verkaufen, und
/// Personen, die diese Software erhalten, diese Rechte zu
/// geben, unter den folgenden Bedingungen:
///
/// Der obige Urheberrechtsvermerk und dieser Erlaubnisvermerk
/// sind in allen Kopien oder Teilkopien der Software beizulegen.
///
/// DIE SOFTWARE WIRD OHNE JEDE AUSDRÜCKLICHE ODER IMPLIZIERTE
/// GARANTIE BEREITGESTELLT, EINSCHLIESSLICH DER GARANTIE ZUR
/// BENUTZUNG FÜR DEN VORGESEHENEN ODER EINEM BESTIMMTEN ZWECK
/// SOWIE JEGLICHER RECHTSVERLETZUNG, JEDOCH NICHT DARAUF
/// BESCHRÄNKT. IN KEINEM FALL SIND DIE AUTOREN ODER COPYRIGHTINHABER
/// FÜR JEGLICHEN SCHADEN ODER SONSTIGE ANSPRÜCHE HAFTBAR ZU
/// MACHEN, OB INFOLGE DER ERFÜLLUNG EINES VERTRAGES, EINES
/// DELIKTES ODER ANDERS IM ZUSAMMENHANG MIT DER SOFTWARE ODER
/// SONSTIGER VERWENDUNG DER SOFTWARE ENTSTANDEN.    

public class HcSr04 : IDisposable
{
    private const int TICKS_TO_MS_FACTOR = 10000;

    public event DistanceReceivedDelegate DistanceReceived;

    public int Distance;

    public const uint DEFAULT_TRIGGER_FREQUENCY = 20;
    public const uint TRIGGER_PULSE_DURATION = 1;

    private const int DEFAULT_GETDISTANCE_TIMEOUT = 10;

    private IHcSr04Trigger _trigger;
    private InterruptPort _echoInterruptPort;
    private long _echoStartTime = 0;

    public HcSr04(IHcSr04Trigger trigger, Cpu.Pin echoPin)
    {
        _trigger = trigger;

        _echoInterruptPort = new InterruptPort(
                        echoPin,
                        false,
                        Port.ResistorMode.PullDown,
                        Port.InterruptMode.InterruptNone);
        _echoInterruptPort.OnInterrupt += echo_OnInterrupt;
    }

    public void Start()
    {
        _echoInterruptPort.Interrupt = Port.InterruptMode.InterruptEdgeBoth;
        _echoInterruptPort.EnableInterrupt();
        _trigger.Start();
    }

    public void Stop()
    {
        _echoInterruptPort.DisableInterrupt();
        _trigger.Stop();
    }

    public int GetDistance()
    {
        return GetDistance(DEFAULT_GETDISTANCE_TIMEOUT);
    }

    public int GetDistance(int timeoutMs)
    {
        int timeoutTicks = timeoutMs * TICKS_TO_MS_FACTOR;

        int distance = -1;
        bool isReceived = false;

        DistanceReceivedDelegate receivedDelegate = (sender, e) =>
            {
                isReceived = true;
                distance = e.Distance;
            };
        DistanceReceived += receivedDelegate;

        Start();

        // Warten bis das DistanceReceived-Event ausgelöst wurde oder der Timeout
        // überschritten wurde.
        long startTime = DateTime.Now.Ticks;
        while (!isReceived && DateTime.Now.Ticks - startTime < timeoutTicks)
        {
            Thread.Sleep(1);
        }

        DistanceReceived -= receivedDelegate;

        Stop();

        return distance;
    }

    public void Dispose()
    {
        if (_trigger != null)
            _trigger.Dispose();
    }

    private void echo_OnInterrupt(uint portNumber, uint value, DateTime interruptTime)
    {
        if (value == 1)
        {
            // Am Echo-Pin wurde eine steigende
            // Flanke (0 auf 1) erkannt:
            // Zeitmessung starten
            _echoStartTime = interruptTime.Ticks;
        }
        else
        {
            // Am Echo-Pin wurde eine fallende
            // Flanke (1 auf 0) erkannt:
            // Zeit stoppen und auswerten

            // Schallgeschwindigkeit: Etwa 343 Meter pro Sekunde bei 20° C
            const int SPEED_OF_SOUND = 343;
            const int FACTOR = SPEED_OF_SOUND * 10 / 2;
            const int DIVISOR = 100000;

            long elapsedTicks = interruptTime.Ticks - _echoStartTime;
            long objectDistanceMm = elapsedTicks * FACTOR / DIVISOR;

            Distance = (int)objectDistanceMm;
            onDistanceReceived(Distance);
        }
    }

    private void onDistanceReceived(int distance)
    {
        var handler = DistanceReceived;
        if (handler != null)
        {
            handler(this, new DistanceReceivedEventArgs(distance));
        }
    }
}

public delegate void DistanceReceivedDelegate(object sender, DistanceReceivedEventArgs e);
public class DistanceReceivedEventArgs : EventArgs
{
    public int Distance;

    public DistanceReceivedEventArgs(int distance)
    {
        Distance = distance;
    }
}

public interface IHcSr04Trigger : IDisposable
{
    void Start();
    void Stop();
}

public class PwmTrigger : IHcSr04Trigger
{
    public PWM Pwm;

    public PwmTrigger(Cpu.PWMChannel pwmChannel)
    {
        init(pwmChannel, HcSr04.DEFAULT_TRIGGER_FREQUENCY);
    }

    public PwmTrigger(Cpu.PWMChannel pwmChannel, uint triggerFrequency)
    {
        init(pwmChannel, triggerFrequency);
    }

    private void init(Cpu.PWMChannel pwmChannel, uint triggerFrequency)
    {
        Pwm = new PWM(pwmChannel, triggerFrequency, HcSr04.TRIGGER_PULSE_DURATION, PWM.ScaleFactor.Milliseconds, false);
    }

    public void Start()
    {
        Pwm.Start();
    }

    public void Stop()
    {
        Pwm.Stop();
    }

    public void Dispose()
    {
        Pwm.Dispose();
    }
}

/// <summary>
/// Alternative zum PwmTrigger, falls kein PWM-Port mehr frei ist.
/// Hier kann jeder GPIO-Pin verwendet werden. Dieser Trigger verursacht
/// etwas Last, weswegen der PwmTrigger zu bevorzugen ist.
/// </summary>
public class OutputPortTrigger : IHcSr04Trigger
{
    /// Erklärung:
    /// Nachdem der Trigger gestartet wurde, wird per Timer in der angegebenen
    /// Trigger-Frequenz der Portausgang auf HIGH gesetzt. Gleichzeitig wird
    /// der Abschalttimer gesetzt, der den Portausgang nach PulseDuration
    /// wieder abschaltet.

    public OutputPort OutputPort { get; private set; }

    private Timer _triggerFrequenceTimer;
    private Timer _pulseOffTimer;

    public int TriggerFrequency;
    public int PulseDuration;

    public OutputPortTrigger(Cpu.Pin pin)
    {
        init(pin, (int)HcSr04.DEFAULT_TRIGGER_FREQUENCY);
    }

    public OutputPortTrigger(Cpu.Pin pin, int triggerFrequency)
    {
        init(pin, triggerFrequency);
    }

    private void init(Cpu.Pin pin, int triggerFrequency)
    {
        OutputPort = new OutputPort(pin, false);
        TriggerFrequency = triggerFrequency;
        PulseDuration = (int)HcSr04.TRIGGER_PULSE_DURATION;
        _triggerFrequenceTimer = new Timer(triggerFrequenceTimerElapsed, this, Timeout.Infinite, Timeout.Infinite);
        _pulseOffTimer = new Timer(pulseOffTimerElapsed, this, Timeout.Infinite, Timeout.Infinite);
    }

    private void triggerFrequenceTimerElapsed(object state)
    {
        OutputPort.Write(true);
        _pulseOffTimer.Change(PulseDuration, Timeout.Infinite);
    }

    private void pulseOffTimerElapsed(object state)
    {
        OutputPort.Write(false);
    }

    public void Start()
    {
        _triggerFrequenceTimer.Change(0, TriggerFrequency);
    }

    public void Stop()
    {
        _triggerFrequenceTimer.Change(Timeout.Infinite, Timeout.Infinite);
    }

    public void Dispose()
    {
        _triggerFrequenceTimer.Dispose();
        _pulseOffTimer.Dispose();
        OutputPort.Write(false);
        OutputPort.Dispose();
    }
}

Disclaimer

Die hier zusammen gestellten Informationen und Anleitungen habe ich mit bestem Wissen und Gewissen erstellt. Falls sich Fehler eingeschlichen haben oder du Verbesserungsvorschläge hast, schicke mir bitte eine Nachricht oder schreibe einen Kommentar unter den jeweiligen Beitrag. Informiere dich selber wie alles funktioniert. Ein Nachbau erfolgt auf eigene Gefahr. Du allein bist dafür verantwortlich, dass das, was du tust, funktioniert und keine Sach- oder Personenschäden verursacht.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.