Drehzahlmessung mit Hall-Sensor KY-024

  • Hallo,

    mein Sohn und ich wollen für ein GoCart einen "Bordcomputer" bauen.

    Jetzt haben wir den Hall-Sensor KY-024 mit dem Raspberry Pi 4B verbunden:


    KY-024Raspberry
    G
    Pin 9 - Gnd
    +Pin 17 - 3,3V
    DOPin 18 - GPIO24


    Unser Programm sieht wie folgt aus:

    Wir probieren das ganze derzeit über die Raspberry GUI und Thonny 3.3.10 (Phyton 3.7.3 - 32-bit).


    Wenn wir jetzt einen Magneten am Sensor vorbeibewegen, kommen diese Ausgaben:

    Code
    Current RPM =      2.5 1/min      0.2 km/h  1653326807.3179052   1653326783.1268919   24.19101333618164
    Current RPM =  20910.5 1/min   1576.6 km/h  1653326807.3207746   1653326807.3179052   0.0028693675994873047
    Current RPM =  23790.7 1/min   1793.8 km/h  1653326807.3232965   1653326807.3207746   0.002521991729736328
    Current RPM =  23632.1 1/min   1781.8 km/h  1653326807.3258355   1653326807.3232965   0.002538919448852539
    Current RPM =    202.6 1/min     15.3 km/h  1653326807.6220145   1653326807.3258355   0.29617905616760254
    Current RPM =  50361.9 1/min   3797.2 km/h  1653326807.623206   1653326807.6220145   0.0011913776397705078
    Current RPM =    876.4 1/min     66.1 km/h  1653326807.6916697   1653326807.623206   0.06846380233764648
    Current RPM =  49823.4 1/min   3756.6 km/h  1653326807.692874   1653326807.6916697   0.0012042522430419922
    Current RPM =  58991.6 1/min   4447.9 km/h  1653326807.693891   1653326807.692874   0.0010170936584472656

    Die Werte wirken ziemlich unrealistisch. Wir bewegen den Magneten vielleicht ca. 1-2-mal pro Sekunde an dem Sensor vorbei.

    Egal, wie wir die Empfindlichkeit am KY-024 einstellen. Wird die Einstellung zu unempfindlich, kommen gar keine Impulse mehr an.


    Es wirkt so, als ob der Sensor extrem viele Impulse erfasst. Kennt jemand das Problem?

    Wie können wir hier tatsächlich realistische Werte erfassen - ohne das "Prellen"?

    Eignen sich ggf. andere Sensoren besser?


    Danke für eure Hilfe.

  • Ich initialisiere beide Variablen mit der aktuellen Zeit.

    Später bei Ereigniseintritt wird this_time mit der dann aktuellen Zeit belegt und mit der Zeit beim vorherigen Ereignis „verglichen“. Danach bekommt last_time die Zeit des Ereigniseintritts zugewiesen, um einen Vergleichswert beim nächsten Ereignis zu haben.

    Oder?

  • Hallo,


    für Zeitabstände bietet sich time.monotonic an. Dann bringt es nichts wenn man die Variablen zu Programmstart initialisiert. In Python erstellt man Variablen dann wenn man sie braucht. Also der Sensor erkennt dass der Magnet am Sensor vorbei ist (fallende Flanke) start_time = time.monotonic(), danach erkennt der Sensor dass der Magnet kommt (steigende Flanke) end_time = time.monotonic() Dann kannst du die Differenz bilden und hast die Dauer einer Umdrehung.


    Versuche gleich 'global' vergessen. Das macht Programme fehleranfällig, schwierig zu lesen und schierig zur Fehlersuche. Alles muss in Funktionen stehen und die bekommen alles was sie benötigen übergeben und geben alle Werte per 'return' zurück. P.S. return () bringt nichts. Wird kein 'return' verwendet gibt Python nach Funktionsende 'None' zurück.


    Grüße

    Dennis

    “If you’re not paying for the product, then you are the product” Daniel Hövermann

  • Hallo Dennis89,

    das klingt interessant. Ich mag gar nicht fragen, wie das genau aussehen würde. :saint:

    Wie kann man denn die vielen Trigger, die gar nicht zu den tatsächlichen Ereignissen passen, verhindern?

  • Hallo Kermit29,


    "technisch" hast Du vieles richtig gemacht.


    Der Kardinalfehler liegt daran, dass Du in der Interrupt-Service-Routine (so man es in Python so nennen darf), VIEL ZU LANGE verweilst UND ZU VIEL rechnest UND dann noch eine zeitintensive formatierte Ausgabe tätigst.



    Du hast zwei Flanken. Einmal, wenn der Magnet reinfliegt und das andere Mal, wenn er wieder rausfliegt.


    Zwei Interrupts. Zwei Zeiten. Nur, wenn diese beiden Zeiten frisch gesetzt wurden, darf eine Berechnung der Geschwindigkeit - AUSSERHALB der Interrupt-Service-Routine - erfolgen.


    Ich bin mir auch nicht sicher, dass es korrekt ist, wenn die erste Zeit den Wert der zweiten Zeit bekommt und auf ein Ereignis für die zweite Zeit gewartet wird.


    Wenn das System sooo träge ist, dass die beiden Flanken nicht sauber aufgelöst werden, dann frage eben entweder nur steigende oder nur fallende Flanken ab. Dann weißt Du, wie lange eine Umdrehung dauert. Du kennst den Radumfang = zurückgelegter Weg und kannst darüber die Geschwindigkeit berechnen.



    Allgemein sollte in Interrupt-Service-Routinen nur sog. Flags gesetzt werden. Also Zeiten von Ereignissen oder Zustände. In Deinem Fall wird beim "Reinfliegen" die erste Zeit gesetzt. Beim Rausfliegen wird die zweite Zeit gesetzt und ein Flag, dass im Hauptprogramm eine Berechnung und formatierte Ausgabe auslösen soll. So wäre das sauber und klassisch korrekt gelöst.



    Beste Grüße


    Andreas

    Ich bin wirklich nicht darauf aus, Microsoft zu zerstören. Das wird nur ein völlig unbeabsichtigter Nebeneffekt sein.
    Linus Torvalds - "Vater" von Linux

    • Icon-Tutorials (IDE: Geany) - GPIO-Library - µController-Programmierung in Icon! - ser. Devices - kein Support per PM / Konversation

    Linux is like a wigwam, no windows, no gates, but with an apache inside dancing samba, very hungry eating a yacc, a gnu and a bison.

  • Wie bekommen wir denn das jetzt alles grob verheiratet?

    Brauche ich 2 Interrupt Funktionen (bei fallender Flanke am Sensor die Startzeit setzen - so wie ich das verstanden habe, als Rückgabewert / aber wie? - Bei steigender Flanke Stopzeit setzen. Wie?

    Und dann? Wo/wie sollen wir dann die Geschwindigkeit ermitteln? Im Hauptprogramm?

    Das ist ganz schön tricky.

  • Vielleicht so, ungetestet:



    Man könnte die Flankenerkennung auch in einen Thread auslagern, dann hätte man Andreas Einwand eventuell abgedeckt.

    Ich kann das leider nicht nachstellen und evetuell musst du noch ein paar Optionen bei DigitalInputDevice angeben.


    Wie du siehst verwende ich gpiozero das ist meiner Meinung nach von der Programmierung her einfacher und übersichtlicher.

    Wobei ich in der Vergangenheit gemerkt habe, dass das bei Frequenzmessungen Probleme bereiten kann. Da du sagt, das Rad dreht sich nicht so schnell, kannst du es ja mal so versuchen.


    Wie schnell läuft das GoCart denn? Bei Anwendungen in Fahrzeugen finde ich einen Mikrokontroller eigentlich geeigneter, vorallem wenn man nur einen Sensor auslesen muss. Der ESP32 bietet sich zum Beispiel sehr gut dafür an, damit habe ich schon Frequenzen um die 60kHz relativ genau ausgelesen.


    Grüße

    Dennis


    Falls das mit dem 'wait_for_...' nicht funktioniert kann man auch Callbacks definieren mit 'when_deactiveted'. Nur dann würde ich vermutlich eine Klasse bauen, die sich die Werte merken kann.

    “If you’re not paying for the product, then you are the product” Daniel Hövermann

    Edited once, last by Dennis89: Einheit hat nicht gepasst ().

  • Hallo KErmit29,

    Ich denke mal, dass wir so auf 30km/h kommen.

    Das ist jetzt ja ohne interrupts gebaut, richtig?

    Dann kann ich ja nix anderes parallel machen, oder?

    die Antwort verrät Zeile 33 Deines Codes im einleitenden Thread. Pseudo-Interrupt trifft es eigentlich besser.



    Beste Grüße


    Andreas

    Ich bin wirklich nicht darauf aus, Microsoft zu zerstören. Das wird nur ein völlig unbeabsichtigter Nebeneffekt sein.
    Linus Torvalds - "Vater" von Linux

    • Icon-Tutorials (IDE: Geany) - GPIO-Library - µController-Programmierung in Icon! - ser. Devices - kein Support per PM / Konversation

    Linux is like a wigwam, no windows, no gates, but with an apache inside dancing samba, very hungry eating a yacc, a gnu and a bison.

  • Dann wäre das vielleicht ein Versuch wert, ungetestet:

    Edit: Sorry, der Code so ist Mist. War gestern Abend wohl zu spät.



    Grüße

    Dennis

    “If you’re not paying for the product, then you are the product” Daniel Hövermann

    Edited once, last by Dennis89 ().

  • Danke, Dennis,


    ich habe den Code einmal eingefügt - vorher noch GPIOZERO installiert.


    Jetzt bekomme ich diverse Fehler:

    Ich hatte Deinen Code in eine Text-Datei "Test A.py" kopiert.


    Was sagt mir das?

  • Kermit29 Unter Linux sind Leerzeichen im Dateinamen eher hinderlich, also ersetz das mal durch einen Unterstrich (_) oder lass das ganz weg.

    Dann starte das Skript besser im Terminal mit python3 /Pfad/zum/Skript.py. Dann *stört* im Zweifel auch Thonny nicht und man hat das Ergebnis, was später im produktiven Einsatz erwartet wird.


    Hast Du einen Widerstand als Pullup oder Pulldown verbaut? Wenn nicht, dann teste Dein Skript aus Beitrag #1 mal mit

    GPIO.PUD_DOWN statt GPIO.PUD_OFF in Zeile 12.

    Wenn das das Ergebnis nicht verbessert, dann erhöhe mal die bouncetime im Callback auf 100.

  • Du hast zwei Flanken. Einmal, wenn der Magnet reinfliegt und das andere Mal, wenn er wieder rausfliegt.

    Zwei Interrupts. Zwei Zeiten. Nur, wenn diese beiden Zeiten frisch gesetzt wurden, darf eine Berechnung der Geschwindigkeit - AUSSERHALB der Interrupt-Service-Routine - erfolgen.

    Wozu zwei Flanken? Es genügt doch, immer nur die eine Flanke zu erfassen - es interessiert doch keinen, wie lange der Magnet über dem Sensor ist. Man muss doch nur die Zeit einer Umdrehung messen. Eine Interruptroutine auf steigende ODER fallende Flanke (eigentlich egal). Dort die Zeitdiffferenz zum letzten Interrupt bestimmen und das wars doch schon.

    Oh, man kann hier unliebsame Nutzer blockieren. Wie praktisch!

  • Wir haben noch ein wenig experimentiert und sind zu interessanten Erkenntnissen gekommen.

    Als Basis haben wir zunächst unseren ursprünglichen Code verwendet, da die anderen Vorschläge die beschriebenen Fehler brachten. Jedoch haben wir den Hinweis von hyle übernommen und einen PullDown Widerstand konfiguriert:

    Parallel habe ich an den Aufbau ein Oszilloskop angeschlossen, um mir das Signal am Ausgang der Hall-Sensor-Platine KY-024 ansehen zu können.

    Bei höheren Drehzahlen scheint der obige Code brauchbare Ergebnisse zu liefern - siehe IMG_9340-2.jpg

    Wenn der Sensor aber nur wenige Impulse bekommt, kommen wieder irre Werte (hohe Drehzahl) heraus. Der Ausgang des Sensors liefert leider auch kein richtiges Rechteck-Signal, sondern eher etwas wie ein Sägezahn: IMG_9335-2.jpg - aufgenommen, als der Magnet direkt vor dem Sensor stand

    Das kommt vermutlich bei langsamen Drehzahlen zum Tragen, da der Magnet dann relativ lange ein Magnetfeld liefert. Das erzeugt dann vermutlich viele Trigger und entsprechend hohe Drehzahlen. Das kann ich vermutlich durch eine höhere BounceTime eliminieren, aber dann kommen auch hohe Drehzahlen nicht durch.

    Starte ich das Ganze im Terminal anstatt via Thonny, verhält es sich identisch.


    Hat jemand eine Idee?


    Und dann wäre da ja noch das Code-Optimieren. Da stehen wir noch immer auf dem Schlauch, wie wir da jetzt vorgehen sollen - wir sind noch Python Anfänger.


    Danke für euren Input.

  • Ändere ich auf GPIO.FALLING, scheint das keinen wirklichen Unterschied zu machen:

    Bei langsameren Geschwindigkeiten werden zunächst noch langsame Werte angezeigt, umgeben von sehr schnellen Werten. Wird es dan richtig langsam, kommen nur noch sehr schnelle Werte.

  • Hallo,


    sorry mein vorheriger Code war Mist, habs auch editiert.


    Wenn du magst kannst du mal folgenden Code versuchen:


    Versuche auch mal in deinem Code 'monotonic' anstatt 'time' zu verwenden. Weil schau mal was hier bei time steht:


    Note that even though the time is always returned as a floating point
    number, not all systems provide time with a better precision than 1 second.
    While this function normally returns non-decreasing values, it can return a
    lower value than a previous call if the system clock has been set back
    between the two calls.

    Für monotonic findet man das:


    Return the value (in fractional seconds) of a monotonic clock, i.e. a clock
    that cannot go backwards. The clock is not affected by system clock updates.
    The reference point of the returned value is undefined, so that only the
    difference between the results of two calls is valid.

    Also das würde in deinem Code dann so aussehen:

    Grüße

    Dennis

    “If you’re not paying for the product, then you are the product” Daniel Hövermann