[RPi3] Entfernung über Ultraschallsensor messen

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
robin_
User
Beiträge: 41
Registriert: Montag 3. August 2020, 17:59

Alles klar, ich werde es mit Pin_IRQs (muss erstmal gucken was das meinst) probieren. Oszillator ist nicht vorhanden.

Ich habe, wie gesagt, mehrere Beispiele im Netz gesehen, wo das Messen recht präzise mit dem im Ausgangspost genannten Code funktioniert. Mit Pico + HC-SR04. Hatte es ebenfalls mit nem Pi3B+ probiert.
Da das nicht gefruchtet hat vermute ich, dass irgendwas am Setup nicht stimmt. Also falsche Pins oder so... benutze GPIO 6 und 7 (Pin 9 und 10).

Gibt es GPIO's die eher verwendet werden sollten als andere?
__deets__
User
Beiträge: 13937
Registriert: Mittwoch 14. Oktober 2015, 14:29

Oszilloskop. Nicht Oszillator. Meines Wissens Nacht gibt es keine Pins, die bevorzugt sind. Du kannst aber erstmal die prinzipielle Funktionsfähigkeit testen, indem du mit einem Kabel oder gar Schalter prüfst, ob eine LOW/HIGH/LOW Sequenz erkannt wird. Auch wenn die händisch natürlich unpräzise und langsam ist, erkennst du daran, ob die Programmierung überhaupt tut.
robin_
User
Beiträge: 41
Registriert: Montag 3. August 2020, 17:59

So, hier nun meine aktuelle Version, die vom Prinzip her funktioniert.

Code: Alles auswählen

from machine import Pin
import utime
import micropython


micropython.alloc_emergency_exception_buf(100)

# Pin 9 / 10
TRIGGER_GPIO = 6
ECHO_GPIO = 7

SOUND_VELOCITY_M_S = 343


class Measurement:

    def __init__(self):
        self.trigger = Pin(TRIGGER_GPIO, Pin.OUT)
        self.echo = Pin(ECHO_GPIO, Pin.IN, Pin.PULL_DOWN)

        self.echo_start = utime.ticks_us()
        self.echo_end = utime.ticks_us()
        self.finished_measurement = False

    def loop(self):
        print("=== loop()")
        self.measure()

        while self.finished_measurement == False:
            print("... waiting for finish...")
            utime.sleep(1)
        
        diff = utime.ticks_diff(self.echo_end, self.echo_start)
        delay_s = diff / 1e6


        print("Signal was high for ", delay_s, " seconds.")
        self.finished_measurement = False


    def measure(self):
        print("=== measure()", self.echo.value(), " is current echo status")
        self.trigger.high()
        utime.sleep(2)
        self.trigger.low()

        while self.echo.value() == 0:
            print("low...")
            utime.sleep_us(1)
        
        print("WE ARE HIGH")
        self.echo_start = utime.ticks_us()
        self.echo.irq(self.irq_callback, self.echo.IRQ_FALLING, hard=True)

    def irq_callback(self, _):
        print("--- irq_callback()")
        self.echo_end = utime.ticks_us()
        self.finished_measurement = True


if __name__ == "__main__":
    print("starting up ...")

    m = Measurement()
    m.loop()
Und zwar:
Ich habe als Trigger eine LED genommen und diese 2 Sekunden lang leuchten lassen, die geht schonmal an.
Um das Echo zu testen hab ich einfach am Steckbrett auf den GPIO 7 (Echo) mit nem Kabel 3,3V vom Pin 36 angelegt.
Ich konnte damit die Zeit in Sekunden messen, die die Reihe im Steckbrett dann mit 3,3V versorgt war (durchs Kabel einstecken).

2. Variante: Um den Spannungswandler zu testen, wollte ich das ganze wiederholen, nur dass ich das Kabel, welches mein Schalter simulieren soll, mit 5V (Pin40) verbunden ist. Da das ja so nicht an die GPIO's darf, hab ich dass durch den Y-Anschluss vom Wandler gejagt (und die A-Seite liegt am GPIO an). Also eigentlich das gleiche, nur dass mein 'Schalter' (in Form des Kabels) 5V hat und erst durch den Wandler geht, so wie beim Sensor halt auch.
Und das klappt nicht.

Entweder passiert gar nichts, als wäre nichts gesteckt, oder es wird dauerhaft die irq_callback() aufgerufen. Klingt mir mich als Laie danach, dass die Spannung entweder gar nicht anliegt oder zu gering ist. Also sprich, das Wandeln von 5V -> 3V klappt anscheinend nicht.

Nun muss ich sehen wie ich das Problem gelöst kriege...
__deets__
User
Beiträge: 13937
Registriert: Mittwoch 14. Oktober 2015, 14:29

Da sind ein paar Fehler drin. IRQs nur einmal registrieren, und die sollten so wenig wie möglich machen - vor allem nix ausgeben. Und das warten auf das Echo sollte auch keine print Ausgabe noch Wartezeit beinhalten. Auf einem Micro ist ein tighter poll loop schon ok, alternativ sollte der IRQ beide Richtungen beherrschen. Aber dann muss man ja auch Pollen, bis der Abbruch kommt.

Was die Elektronik angeht - ja, das sieht nach einem elektronischen Problem aus. Die Komponente kann kaputt sein. Wenn du öfter sowas machen willst, lohnt sich wirklich ein Oszi. . Billige analoge bekommt man zum Preis von 2 Kästen Bier. Und spart sich Stunden an Probiererei. Vielleicht habt ihr auch ein repair Café oder Ähnliches, wo man sowas mal benutzen oder sich helfen lassen kann.
robin_
User
Beiträge: 41
Registriert: Montag 3. August 2020, 17:59

__deets__ hat geschrieben: Mittwoch 9. August 2023, 22:22 Da sind ein paar Fehler drin. IRQs nur einmal registrieren, und die sollten so wenig wie möglich machen
Ja stimmt, dass hatte ich vergessen, im ersten Draft hatte ich im Callback die IRQ auf IRQ_FALLING gesetzt, also quasi ein callback für HIGH und dann einen für LOW. Das war aber nicht so stabil, zumindest mit meiner "Ich steck das Kabel ins Steckbrett" - Variante.

Das mit dem Prints weiß ich, musste nur irgendwie debuggen.
Auf einem Micro ist ein tighter poll loop schon ok, alternativ sollte der IRQ beide Richtungen beherrschen. Aber dann muss man ja auch Pollen, bis der Abbruch kommt.
Genau, das mit dem IRQ in beide Richtungen funktioniert vielleicht mit dem Sensor, aber nicht mit meiner Ausprobiererei. Beim Reinstecken ins Board gabs anscheinend paar mal Kontakt - Kein Kontakt wodurch das ja dann direkt ausgelöst wurde.

Mit "Tighter poll loop" meinst du das hier dann?

Code: Alles auswählen

while self.echo.value() == 0:
    pass
Wenn du öfter sowas machen willst, lohnt sich wirklich ein Oszi. . Billige analoge bekommt man zum Preis von 2 Kästen Bier. Und spart sich Stunden an Probiererei. Vielleicht habt ihr auch ein repair Café oder Ähnliches, wo man sowas mal benutzen oder sich helfen lassen kann.
Danke für den Tipp, ich schau mal in der Uni + Ebay ;)
robin_
User
Beiträge: 41
Registriert: Montag 3. August 2020, 17:59

So,

Ich hab den Sensor jetzt zufriedenstellend ans laufen bekommen - vielen Dank!
Lösung war, wie schon im letzten Post vermutet, dass das Echo Signal nicht durch kam.

Ich habe das jetzt mit 2 in Reihe geschalteten Wiederständen gemacht, 10kOhm und 22kOhm: Das Echo Signal geht durch beide in GND, nach dem ersten (10kOhm) greife ich mir das ab und das ist dann die Verbindung zum Pico GPIO.
Sollte dann so bei 3.4V liegen, also im Rahmen.

Hier nun die zwischenfertige Lösung:

Code: Alles auswählen

from machine import Pin
import utime
import micropython

micropython.alloc_emergency_exception_buf(100)

TOGGLE_GREEN_LT = 0.1
TOGGLE_RED_GT = 0.2
THRESHOLD = 0.02

# Pin 1,2,4
RED_GPIO = 0
YELLOW_GPIO = 1
GREEN_GPIO = 2


# Pin 9 / 10
TRIGGER_GPIO = 6
ECHO_GPIO = 7

SOUND_VELOCITY_M_S = 343

class Measurement:

    def __init__(self, freq_hz = 5):
        self.freq_hz = freq_hz

        self.trigger = Pin(TRIGGER_GPIO, Pin.OUT)
        self.echo = Pin(ECHO_GPIO, Pin.IN, Pin.PULL_DOWN)
        self.echo.irq(self.irq_callback, self.echo.IRQ_FALLING, hard=True)
        
        self.echo_start = utime.ticks_us()
        self.echo_end = utime.ticks_us()
        self.finished_measurement = False

        self.red = Pin(RED_GPIO, Pin.OUT)
        self.yellow = Pin(YELLOW_GPIO, Pin.OUT)
        self.green = Pin(GREEN_GPIO, Pin.OUT)
        self.current_light = None
        
        self.lights_off = {RED_GPIO: [self.yellow, self.green], YELLOW_GPIO: [self.green, self.red], GREEN_GPIO: [self.yellow, self.red]}
        self.lights = {RED_GPIO: self.red, YELLOW_GPIO: self.yellow, GREEN_GPIO: self.green}


    def off(self):
        for p in self.lights.values():
            p.low()


    def signal_length_to_distance_m(self, diff_us):
        diff_s = diff_us / 1e6
        distance_m = SOUND_VELOCITY_M_S * diff_s / 2
        return distance_m


    def toggle(self, gpio):
        self.lights[gpio].high()
        for p in self.lights_off[gpio]:
            p.low()


    def control_lights(self, distance_m):
        TOGGLE_GREEN_COND = distance_m < TOGGLE_GREEN_LT
        TOGGLE_YELLOW_COND = TOGGLE_GREEN_LT + THRESHOLD < distance_m < TOGGLE_RED_GT - THRESHOLD
        TOGGLE_RED_COND = distance_m > TOGGLE_RED_GT
        
        if TOGGLE_GREEN_COND and self.current_light != GREEN_GPIO:
            self.toggle(GREEN_GPIO)
        
        elif TOGGLE_YELLOW_COND and self.current_light != YELLOW_GPIO:
            self.toggle(YELLOW_GPIO)
        
        elif TOGGLE_RED_COND and self.current_light != RED_GPIO:
            self.toggle(RED_GPIO)
            

    def loop(self):
        while True:
            self.measure()
            while self.finished_measurement == False:
                utime.sleep(1 / self.freq_hz)
            
            diff = utime.ticks_diff(self.echo_end, self.echo_start)
            self.finished_measurement = False
            distance_m = self.signal_length_to_distance_m(diff)
            self.control_lights(distance_m)


    def measure(self):
        self.trigger.high()
        utime.sleep_us(10)
        self.trigger.low()

        while self.echo.value() == 0:
            pass
        
        self.echo_start = utime.ticks_us()
       

    def irq_callback(self, _):
        self.echo_end = utime.ticks_us()
        self.finished_measurement = True


if __name__ == "__main__":
    m = Measurement()
    try:
        m.loop()
    except KeyboardInterrupt:
        m.off()
Ich werde das zukünftig erweitern und vmtl. mittels MQTT die Daten an nen Server schicken.

Vermutlich ist die Variante mit den Widerständen nicht so schön, eig. wäre dafür ja der Spannungswandler gewesen... vielleicht krieg ich das zukünftig dann mal hin.

Danke:)
__deets__
User
Beiträge: 13937
Registriert: Mittwoch 14. Oktober 2015, 14:29

So ein spannungsteiler ist völlig ok. Kannst du lassen. Den Echo Puls reicht eigentlich ja auch ein Transistor. Aber Hauptsache es tut.
Benutzeravatar
__blackjack__
User
Beiträge: 12490
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Anmerkungen zum Quelltext: Das Hauptprogramm sollte in einer Funktion stehen. Momentan ist `m` (kein guter Name!) eine globale Variable.

`Measurement` ist auch kein guter Name für die Klasse. Die enthält auch zu viel. Letztlich ist das eine Gott-Klasse die *alles* macht.

Der `off()`-Aufruf sollte nicht nur passieren wenn der Benutzer das mit Strg+C abbricht, sondern *immer*, nehme ich mal an. Dann gehört das in einen ``finally``-Zweig.

Das `current_light`-Attribut wird nicht benutzt.

`lights_off` ist überflüssig und fehleranfällig wenn man so etwas manuell mit den ”invertierten” Werten füllt. Und bei `lights` ist die Abbildung überflüssig und das der Code über die Pin-Nummern-Konstanten geht, statt direkt die `Pin`-Objekte zu verwenden.

`high()` und `low()` sind nicht auf allen Portierungen vorhanden, `on()` und `off()` dagegen schon.

`freq_hz` macht eigentlich nicht wirklich Sinn. Man verzögert damit letztlich nur die Erkennung des Ereignisses.

In `control_lights()` sind lokale Namen wie Konstanten benannt. Und warum werden die ganzen Bedingungen überhaupt vor dem ``if``/``elif`` ausgewertet? Da ist doch noch gar nicht klar ob überhaupt alle Ergebnisse benötigt werden.

`toggle()` ist ein extrem irreführender Name. Eine Methode die so heisst und den Wert für eine LED übergeben bekommt, schaltet diese eine LED in den Zustand in dem sie gerade nicht ist. Wenn die was anderes macht, ist das total überraschend, denn alle anderen Methoden die so heissen machen genau das.

Ungetestet:

Code: Alles auswählen

import micropython
import utime
from machine import Pin

micropython.alloc_emergency_exception_buf(100)

TOGGLE_GREEN_LT = 0.1
TOGGLE_RED_GT = 0.2
THRESHOLD = 0.02

# Pin 1,2,4
RED_GPIO = 0
YELLOW_GPIO = 1
GREEN_GPIO = 2

# Pin 9 / 10
TRIGGER_GPIO = 6
ECHO_GPIO = 7

SOUND_VELOCITY_M_S = 343


class Instrument:
    def __init__(self):
        self.process_echo = self._process_echo
        self.trigger = Pin(TRIGGER_GPIO, Pin.OUT)
        self.echo = Pin(ECHO_GPIO, Pin.IN, Pin.PULL_DOWN)
        self.echo_start = None
        self.distance_m = None
        self.echo.irq(
            self.irq_callback, Pin.IRQ_RAISING | Pin.IRQ_FALLING, hard=True
        )
        self.red = Pin(RED_GPIO, Pin.OUT)
        self.yellow = Pin(YELLOW_GPIO, Pin.OUT)
        self.green = Pin(GREEN_GPIO, Pin.OUT)
        self.lights = [self.red, self.yellow, self.green]

    def _process_echo(self, echo_end):
        diff = utime.ticks_diff(echo_end, self.echo_start)
        self.distance_m = SOUND_VELOCITY_M_S * (diff / 1e6) / 2

    def irq_callback(self, pin):
        if pin.value():
            self.echo_start = utime.ticks_us()
        else:
            micropython.shedule(self.process_echo, utime.ticks_us())

    def switch_to(self, target_light):
        for light in self.lights:
            light.value(light == target_light)

    def update_lights(self):
        if self.distance_m < TOGGLE_GREEN_LT:
            self.switch_to(self.green)

        elif (
            TOGGLE_GREEN_LT + THRESHOLD
            < self.distance_m
            < TOGGLE_RED_GT - THRESHOLD
        ):
            self.switch_to(self.yellow)

        elif self.distance_m > TOGGLE_RED_GT:
            self.switch_to(self.red)

    def start_measurement(self):
        self.distance_m = None
        self.trigger.on()
        utime.sleep_us(10)
        self.trigger.off()

    def off(self):
        self.switch_to(None)


def main():
    instrument = Instrument()
    try:
        while True:
            instrument.start_measurement()
            while instrument.distance_m is None:
                pass
            instrument.update_lights()
    except KeyboardInterrupt:
        pass
    finally:
        instrument.off()


if __name__ == "__main__":
    main()
“Weltsprache No. 1 dürfte weder amerikanisches noch britisches, sondern schlechtes Englisch sein.”
— Ralf Callenberg in de.etc.sprache.deutsch
Benutzeravatar
hyle
User
Beiträge: 94
Registriert: Sonntag 22. Dezember 2019, 23:19
Wohnort: Leipzig

Könnte evtl. *jemand* mal der Titel anpassen? Es geht ja schon länger nicht mehr um einen RPi3 oder/und "normales" Python. 🙊

//Edit: Sorry, ich hatte ein "Bitte!" vergessen
Alles was wir sind ist Sand im Wind Hoschi.
__deets__
User
Beiträge: 13937
Registriert: Mittwoch 14. Oktober 2015, 14:29

Solche Muehe gebt ihr euch vielleicht im Raspberry Pi Forum - dafuer streicht ihr da ja auch die dicke Werbekohle ein! Hier, im bescheidenen Python-Forum, das grau und frei von "dieser eine komische Trick, dein Bauchfett zu verlieren" ist, obwohl ich den wirklich dringend braeuchte, ist mir das zu aufwendig 😬
__deets__
User
Beiträge: 13937
Registriert: Mittwoch 14. Oktober 2015, 14:29

So, ich habe das ganze mal in Rust durchimplementiert. Die PIO Statemachine zaehlt munter die verflossenen Ticks, und die Ergebnisse sind meistens plausibel - ich vermute bei Abweichungen eher den Sensor als Problemquelle. Und im Oszilloskop sieht auch alles tippi toppi aus, es wackelt halt gerade bei kleinen Abstaenden.

https://github.com/deets/pico-experiments
Antworten