closeThis post has been published 4 years 4 months 6 days ago and may be out of date. Please read it carefully, I'm not responsible for any mistakes.

Frequenzen messen zu können ist immer mal hilfreich (Drehzahl von Motoren, PWM, Schwingkreise, usw.), wofür man jedoch einen dedizierten Frequenzzähler benötigt. Dennoch ist das Prinzip dahinter nicht kompliziert, somit ist eine Messung mit einem normalen Mikrocontroller auch ohne zusätzlichen Schaltungsaufwand möglich, in der einfachsten Version leider nur im Bereich von wenigen 100 kHz.

 

Allgemeine Grundlagen

Doch wie misst man eigentlich eine Frequenz?

Definitionsgemäß ist eine Frequenz in der Elekrotechnik die Anzahl von Schwingungen eines
periodischen Signals in einer bestimmten Zeiteinheit.
(Der Einfachheit halber wählt man meistens eine Sekunde)

Jetzt gibt es zwei Möglichkeiten diese Anzahl zu messen, entweder man macht es wie die Defintion besagt und zählt die Anzahl der Impulse pro Zeit, oder die Periodenlänge, also den zeitlichen Abstand zwischen zwei Impulsen.

Was ist nun besser? Nun, wenn man die Takte einfach nur zählt, hat man den Vorteil, dass das ziemlich einfach zu lösen ist, meist wird über einen Interrupt-Pin eine Countervariable hochgezählt und sekündlich deren Wert ausgegeben. Außerdem kann man mit dieser Methode relativ genau hohe Frequenzen messen, allgemein wird dieses Prinzip Frequenzzählung genannt.
Der Nachteil besteht allerdings darin, dass die Genauigkeit immer 1Hz beträgt. Das ist beispielsweise bei 100000 Hz eine ziemlich genaue Angabe, weil der Wert verhältnismäßig groß ist, im Gegensatz zu geringen Frequenzen wie 8 Hz, wo ±1Hz viel ausmachen.

Um diesen Nachteil der Ungenauigkeit bei niedrigen Schwingungszahlen zu umgehen, wird häufig die Frequenzmessung angewendet. Hierbei wird die Zeit zwischen zwei Takten gemessen und über den Kehrbruch die Frequenz ausgerechnet. Da die meisten Mikroprozessoren einen Timer im µ-Sekundenbereich laufen haben, ist es möglich, 25 Hz mit bis zu 3 Nachkommastellen (also 25.012 Hz) anzugeben, allgemein lohnt sich die Messung aber erst ab Frequenzen mit weniger als ca. 300 Hz.

Als Endergebnis kann man festhalten, dass beide Methoden ihre Defizite haben, kombiniert man diese jedoch, hat man die Vorteile beider!
Klingt nicht nur gut, funktioniert auch in der Praxis.

 

Kombination beider Methoden

Wenn man nun den Vorteil der Frequenzmessung bei niedrigen Frequenzen, sowie den Vorteil der Frequenzzählung bei hohen Frequenzen vereint, hat man ein sehr gutes Mittelmaß an Präzision und Bandbreite (also der Abstand zwischen minimal und maximal messbarer Frequenz) erreicht.

Doch wie wird das praktisch umgesetzt? Da es zwei verschiedene Messmethoden sind, kann man diese nicht wirklich “kombinieren”, wohl aber selektiv ausführen. Das bedeutet für eine Messung, dass zunächst eine Frequenzzählung gestartet wird. Sollte die gemessene Frequenz unter einem bestimmten Schwellenwert liegen, bei dem eine Frequenzmessung genauer wäre als eine Zählung (beispielsweise so um die 300Hz), wird noch mal die Frequenz gemessen, jetzt allerdings über die Messung statt Zählung.

Somit hat man eine automatische Fallunterscheidung, ein Ablaufschema könnte folgendermaßen aussehen:

Frequenzzählung    –>    wenn Zähler größer/gleich 300    –>    return Ergebnis (Zähler) [Bsp: 491 Hz]
wenn kleiner 300    –>    Frequenzmessung                          –>    return Ergebnis (Messung) [Bsp: 243.4 Hz]

 

Beispiel an einem Arduino

Damit ein Pin des AVR-Chips als High-Pegel registriert wird, muss eine Spannung von mehr als 0,7*VCC anliegen, bei einem Arduino mit 5V-Logik also entsprechend 3,5 V, bei einem Arduino mit 3,3 V dann ~2,3 V. Unterhalb dieser Schwelle wird logischerweise kein Impuls feststellbar sein. Die Pins selbst sind mit Schutzdioden versehen, fügt man vor diesen noch einen 100k-Widerstand (und am Besten noch eine Diode gegen VCC), kann man auch höhere Spannung als 3,3/5V messen.

In dieser Zeile wird ein Interrupt auf den Pin 2 festgelegt, dessen Methode ausgeführt wird,
wenn der Pin auf HIGH wechselt.

Hier ist die Methode des Interrupts. Sobald eine Frequenz am Pin vorliegt, wird bei jedem Takt eine Zählervariable hochgezählt (Frequenzzählung), sowie der Zeitpunkt der Messung in µS gespeichert und vom alten Zeitpunkt abgezogen, was dann der Periodendauer entspricht.
Wie man sieht, werden beide Messvarianten gleichzeitig angewendet, wobei für das Endergebnis immer nur eine Methode benutzt wird.

Im Hauptteil wird einmal pro Sekunde die ermittelte Frequenz über den seriellen Port ausgegeben. Der Grund warum ich kein “delay(1000)” eingefügt habe, sondern die Verzögerung über einen Timer realisiere, ist ganz einfach der, dass im loop-Teil zwischen den Messwert-Ausgaben weiterer Code ausgeführt werden kann, während delay den ganzen Programmfluss für eine bestimmte Zeit anhält.

In der Praxis funktioniert das folgendermaßen:
Nach dem letzten Ausführen der if-Bedingung wird der Wert des aktuellen Timers auf die Variable “startzeit” übertragen. Die if-Bedingung wird dann erst wieder TRUE, wenn die Differenz zwischen aktueller Laufzeit und der letzten Ausführung (“startzeit”) größer/gleich der Variable “messzeit” ist, in diesem Fall beträgt diese genau 1000000, was 1 Sekunde entspricht. (1s = 1*106 µs)

Um Übertragungsfehler am seriellen Port zu vermeiden, wird während der Übertragung der Interrupt deaktiviert (Zeile 7) und nach Beendigung wieder aktiviert. (Zeile 24)

[Total: 3    Average: 2/5]

21 Comments

  1. Rene

    Was passiert wenn die micros() Funktion überläuft also zurück auf Null geht? Dann wird einmal ein falscher Wert geliefert, richtig?

    • Breadboarding

      Breadboarding

      Ja, das ist korrekt. Solange man den Arduino nicht als Langzeit-Frequenzplotter für’s Labor nutzen will, sollte das aber nicht so problematisch sein.

  2. Dennis

    Hallo ich möchte die Drehzahl meines Hinterrads am Fahrrad messen. Das Problem besteht darin, das die Frequenz relativ gering ist.
    Im Umkehrschluss heißt das ich bei teilweise bei unter einem Hertz lande. Das wiederum ergibt 0. Gibt es eine Möglichkeit auch Frequenzen unter einem Hertz zu berechnen ?

    Hier mein Programm Code:

    // read RPM

    volatile float rpmcount = 0.0;//see http://arduino.cc/en/Reference/Volatile
    float rpm = 0;
    unsigned long lastmillis = 0;

    void setup(){
    Serial.begin(9600);
    attachInterrupt(0, rpm_backwheel, FALLING);//interrupt cero (0) is on pin two(2).
    }

    void loop(){

    if (millis() – lastmillis == 1000){ /*Uptade every one second, this will be equal to reading frecuency (Hz).*/

    detachInterrupt(0); //Disable interrupt when calculating

    rpm = rpmcount * 60; /* Convert frecuency to RPM, note: this works for one interruption per full rotation. For two interrups per full rotation use rpmcount * 30.*/

    Serial.print(“RPM =\t”); //print the word “RPM” and tab.
    Serial.print(rpm); // print the rpm value.
    Serial.print(“\t Hz=\t”); //print the word “Hz”.
    Serial.println(rpmcount); /*print revolutions per second or Hz. And print new line or enter.*/

    rpmcount = 0; // Restart the RPM counter
    lastmillis = millis(); // Uptade lasmillis
    attachInterrupt(0, rpm_backwheel, FALLING); //enable interrupt
    }
    }

    void rpm_backwheel(){ /* this code will be executed every time the interrupt 0 (pin2) gets low.*/
    rpmcount++;
    }

    Ich hoffe Ihr könnt mir weiterhelfen.

    Vielen Dank und viele Grüße

    • Breadboarding

      Breadboarding

      Servus,

      das ist schon fast richtig so! Aber wie oben beschrieben, wird bei niedrigen Frequenzen keine Zählung verwendet, da somit nur ganze Zahlen möglich wären (1 Hz oder 2 Hz).
      Wenn der Interrupt ausgelöst wird, wird der aktuelle Stand des Timers in einer Variablen gespeichert und vom Zeitpunkt des vorherigen Interrupts subtrahiert. Somit erhält man die Periodendauer der Impulse (Frequenzmessung).
      Kennt man ja vom Fahrradtacho, dreht sich das Rad sehr langsam, sinkt die Aktualisierungsrate des Displays, da erst auf einen Impuls gewartet werden muss. Anders bei der Frequenzzählung : hier steht der Faktor Zeit im Vordergrund, damit sekündlich ein Wert berechnet werden kann, unabhängig davon, ob Impulse aufgenommen wurden.

      Als Code sähe das so aus (hoffe, es funktioniert, konnte es nicht testen):
      http://pastebin.com/PjJZ4qUq

      Wichtig: Nach jedem Impuls wird die Drehzahl berechnet, d.h. zu dieser ist die Aktualisierungsrate proportional. Kommt nur alle 10s ein Impuls, kann auch nur alle 10s die Drehzahl ermittelt werden.

      Bei Fragen gerne melden.
      MfG

      • Dennis

        Hey,

        Vielen Dank !!!! Das Programm läuft gut. Ich habe gehört das wenn ein Interrupt vorliegt, das Hauptprogramm jedesmal unterbrochen wird. Sollte ich dann bei größeren Programmen die Berechnung der rpm (float) in der Loop ausführen und nur die Period in die Loop übergeben um den interrupt so schlank wie möglich zu halten ? Oder führt das dann eventuell zu einer Verzögerung.

        Viele Grüße

      • Breadboarding

        Breadboarding

        Gerne, freut mich!

        Ja, die main-loop wird unterbrochen, aber es ob zu Komplikationen kommt, wenn die Interrupt-Routine zu lange ausgeführt wird, weiß ich ehrlich gesagt nicht. Kam da noch nie in eine Limitierung.
        Aber du hast absolut Recht, deutlich schöner ist eine Berechnung in der main-loop. Ich würde im Interrupt nur ne volatile boolean auf ‘true’ setzen und im loop-Teil dann periodisch diese Variable abfragen. Falls ja, Berechnung ausführen und wieder auf ‘false’ setzen.

        MfG,
        Stefan

  3. tool

    Hallo!
    Ich bin sehr unerfahren was die Programmierung und Mikrocontroller angeht, aber werde mich in nächster Zeit damit beschäftigen.
    Ich möchte den Stecker eines Ventiles auf Schaltaussetzer prüfen. Das Ventil wird mehrere hundert mal pro Sekunde geschaltet.
    Wenn ich die Schaltzeit als Konstant wähle ist diese ja als Frequenz annehmbar. Könnte ich dieses Tutorial dann hierfür anwenden um abweichungen zu erkennen und ggf aufzuzeichnen? Evtl. hast du noch andere Tipps oder hilfreiche Anmerkungen.
    LG
    tool

    • Breadboarding

      Breadboarding

      Hallo,

      ja, das würde so funktionieren. Ob man jetzt die Impulse pro Sekunde als Ist-Wert nimmt oder die Periodendauer, ist funktional gesehen egal, Ersteres ist eben nur ressourcenschonender für den Mikrocontroller. Wichtig ist auch noch, einen Fehleranteil zu definieren, d.h. dass erst bei 2% (oder mehr) über oder unter dem Sollwert ein Fehler registriert werden soll (im Zuge der Messungenauigkeit).
      Mal eine Verständnisfrage: welches (mechanische!) Ventil erlaubt denn eine Ansteuerung im dreistelligen Frequenzbereich? Oder sind Minuten gemeint?

      Gruß,
      Stefan

      • tool

        Hallo,
        erstmal danke für die schnelle Antwort!
        Ich habe mich heute morgen zwischen Tür und Angel bisschen verhauen… Es geht um ein Einspritzventil für einen Motor.
        Das Ventil schaltet jedoch nicht mehrere hundert mal pro Sekunde, sondern hat eine Schaltdauer von ca. 1,5ms. Habe da was vertauscht.

      • Breadboarding

        Breadboarding

        Servus,

        das klingt plausibel. Wobei die Impulsrate des Ventils dann abhängig von der Drehzahl des Motors ist, also sollte man diese als Referenzwert hinzuziehen oder man mittelt die letzten n (10, 20, …) Periodenzeiten, da sich diese nur mit einer begrenzten Geschwindigkeit verändern kann.

        Gruß,
        Stefan

      • tool

        Servus,
        Ja die Drehzahl des Motors soll konstant gehalten werden, somit sollte die Einspritzfrequenz auch konstant sein.
        Ich versuche nun schon die ganze Zeit die Abtastrate des Arduino Uno rauszufinden. Ich bin mir unsicher ob die Impulsdauer auch wichtig ist.
        Ich habe etwa 10 Impulse pro Sekunde, ein Impuls hat in dem Bereich den ich messen möchte eine Dauer von ca. 0,00005 Sekunden. Kann mein Arduino dies?

      • Breadboarding

        Breadboarding

        Hallo,
        50µs sind mit einem 8-Bit Mikrocontroller noch sehr gut machbar. Es muss ja nur die positive Flanke erkannt werden, solange diese mind. ein paar Taktzyklen lang ist, funktioniert das.
        (Eine Interruptroutine benötigt 8 Taktzyklen um komplett ausgeführt zu werden, der enthaltene Code kommt noch extra hinzu.
        Ein Takt entspricht einer Zeit von 62,5 ns (Fcpu = 16 MHz), gehe dann mal von ~32 Taktzyklen beispielshalber aus (-> 2 µs), wenn man nur eine Variable im ISR hochzählt. Ist aber nur bei schnell aufeinanderfolgenden Impulsen relevant.)
        Gruß

  4. Martin

    Hallo Stefan,

    okay dann werde ich mal versuchen, ob ich mir den Setup-Teil selbst zusammengesetzt bekomme…. DANKE ! :o)

    Grüße, Martin

  5. Martin

    Hallo,

    leider bekomme ich beim Kompilieren folgende Fehlermeldung :

    expected constructor, destructor, or type conversion before ‘(‘ token

    Kannst du mir einen Tip geben, wo der Hund begraben liegt….?

    • Breadboarding

      Breadboarding

      Hallo,

      eventuell ist ein Semikolon falsch gesetzt, ohne Code kann ich das aber nicht beurteilen,
      da eine Fehlermeldung viele Ursachen haben kann.

      Gruß,
      Stefan

      • Martin

        Hallo Stefan,
        ich weiß nicht ob ich den kompletten Code eingefügt bekomme, aber ich werde es mal versuchen… Letztendlich habe ich nur deine Code-Bestandteile zusammengefügt, da ich gerne an einem “lebenden Objekt” herumbastele, um dann die Funktionsweise zu verstehen… Muss ich noch auf irgendwelche Bibliotheken oder ähnliches verweisen ?! Es fehlt doch auch noch der ganze setup-Teil im Code , oder ?!

        Code :

        attachInterrupt(0, Messung, RISING);

        void Messung()
        {
        zaehler++;
        timer = micros() – timerOld;
        timerOld = micros();
        }

        void loop()
        {
        if ((micros() – startzeit) >= messzeit)
        {
        float f = timer; //Datentyp ‘float’, wegen untenstehender Division
        f = 1000000/f; //Aus Periodendauer Frequenz berechnen
        detachInterrupt(0);
        if(f >= 300){
        Serial.print(“Zaehler: “);
        Serial.println(zaehler);
        }
        else if(f = 30){
        Serial.print(“Messung: “);
        Serial.println(f, 1);
        }
        else if(f = 3){
        Serial.print(“Messung: “);
        Serial.println(f, 3);
        }
        else if(f < 3){
        Serial.print("Messung: ");
        Serial.println(f, 5);
        }
        attachInterrupt(0, Messung, RISING);
        zaehler = 0; //Frequenzzähler zurücksetzen
        startzeit = micros(); //Zeitpunkt der letzten Ausgabe speichern
        }

        Grüße, Martin

      • Breadboarding

        Breadboarding

        Hallo Martin,

        ja ich habe leider nicht den kompletten Code online gestellt, sondern nur die relevanten Teile, um Platz zu sparen.
        Die Fehlermeldung liegt dem “attachInterupt()” in der ersten Zeile zugrunde (muss in setup() sein),
        da außerhalb der Methoden setup und loop keine Methodenaufrufe erlaubt sind, nur Deklarationen.
        Librarys sind keine notwendig.

        Gruß,
        Stefan

  6. Nader

    Hallo noch einmal :-)

    Danke erst einmal für die schnelle Antwort und natürlich auch für ihre Mühe.

    Sie haben geschrieben, dass das Signal nur eine Frequenz beinhalten darf. Habe ich das so verstanden, dass es nicht möglich ist, sagen wir mal per Mikrofon die Umgebung abzuhören, und bei einer bestimmten, herausstechenden Frequenz einen Pin per Arduino zu schalten?

    Viele Grüße aus Düsseldorf.

    • Breadboarding

      Breadboarding

      Hallo,

      naja parasitäre Signale sind immer vorhanden, aber das Signal selbst muss die höchste Amplitude besitzen (über dem Triggerlevel) die Amplituden der anderen Frequenzen müssen unter diesem Level liegen.
      Bei einem Mikrofon kann auch das Nutzsignal je nach Entfernung schwanken, d.h. man müsste über einen AGC (automatische Lautstärkeregelung) den Hub anpassen. Sind beide Bedingungen (Störabstand sowie Amplitude) erfüllt, kann die Frequenz gemessen werden. Falls unterschiedliche Frequenzen verschiedene Aktionen auslösen sollen (und diese Frequenzen nicht allzu weit auseinander liegen) kann man mit einem steilen Bandpassfilter die Störsignale außerhalb dämpfen.
      Das bezieht sich vorerst nur auf den obigen Code, der per Interrupt Impulse zählt. Natürlich kann man das Signal auch mit dem ADC kontinuierlich erfassen und dann mit einer digitalen Signalanalyse die Frequenzen mit der höchsten Intensität errechnen (FFT).
      In diesem Bereich muss ich aber auf Google verweisen, es gibt meines Wissens jedoch entsprechende Librarys für Arduino/AVR.

      Ich hoffe das hilft ein wenig.
      Gruß Stefan

  7. Nader

    Guten Tag,

    danke erst einmal für den Bericht. Dieser war sehr informativ, aber dennoch verständlich.
    Eine Frage habe ich aber trotzdem:

    Eignet sich diese Methode auch für Audio? Also kann ich mit dieser Methode die Frequenz einer bestimmten Quelle bestimmen? Wenn ja, wie könnte die Einspeisung der Daten erfolgen?

    Vielen Dank im Voraus und viele Grüße!

    • Breadboarding

      Breadboarding

      Servus,

      freut mich! Der Artikel ist mittlerweile aber auch etwas älter, was heißen soll, dass ich das Funktionsprinzip mit meinen damaligem (noch anfänglichem) Wissensstand versucht habe, zu erklären. Eventuell werde ich das mal überarbeiten, man lernt ja ständig dazu.

      Eine Messung eines Audiosignals sollte kein Problem darstellen, da die Signalform (Sinus, Rechteck, usw.) nicht von Relevanz ist. Was allerdings wichtig ist, ist der Signalpegel, der 5.3V nicht überschreiten sollte (sowie mind. größer sein als -0.3V). Damit ein High-Pegel erkannt wird, muss zudem die Spannung am Pin etwa 0.6*Vcc sein, sprich 3.0V, andernfalls wird nicht getriggert.
      Wechselspannungen (ich nehme an, das ist mit Audiosignal hier gemeint) kann man so nicht direkt messen, sondern nur über einen Einweggleichrichter – eine Diode in Durchlassrichtung also.
      Die Anbindung des Signals an den Pin stelle ich mir wie folgt vor: über einen Impedanzwandler wird das Signal gepuffert und per Spannungsteiler knapp über das Triggerlevel angeglichen. Falls jedoch der Signalhub zu gering ist, sollte man diesen per OPV verstärken. Um die 0.6 bzw. 0.3V Spannungsabfall an der Diode zu verhindern, kann man auch einen OPV als Präzisionsgleichrichter hernehmen.
      Das Signal selbst darf natürlich nur primär eine Frequenz enthalten, die Frequenz eines breitbandigen Frequenzspektrums zu messen ergibt, wenn überhaupt, nur digitalen Käse. :-)

Leave Comment

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload CAPTCHA.