Drehzahlmessung von zwei Motoren gleichzeitig

Hallo zusammen,

für ein Projekt möchte ich die Drehzahl von zwei Motoren gleichzeitig messen und aufzeichnen. Hierzu verwende ich pro Motor einen IR-Sensor, welcher sein Signal an einen Arduino UNO schickt.
In Sachen Arduino programmieren bin ich noch ein ziemlicher Anfänger, folgenden Code habe ich mir mal zusammengebastelt. Er fuktioniert zwar bisher, jedoch wollte ich einfach mal die Meinung von ein paar Experten (euch) zu dem Code einholen...kann ich da noch was optimieren? (garantiert, die Frage ist nur was :slight_smile: ) ist meine Lösung über die Interrupts sinnvoll oder gibts da geeignetere Varianten?

volatile unsigned long period_left = 0;
volatile unsigned long period_right = 0;

unsigned long timer_left = 0;
unsigned long lasttime_left = 0;

unsigned long timer_right = 0;
unsigned long lasttime_right = 0;

long rpm_left = 0;
long rpm_right = 0;

unsigned long timer = 0;
unsigned long lasttime = 0;

long update_interval = 500; // in Millisekunden, bei Änderung auch unten anpassen (die 120)

void setup() {
  Serial.begin(9600);
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  attachInterrupt(digitalPinToInterrupt(2), RPM_left, RISING);
  attachInterrupt(digitalPinToInterrupt(3), RPM_right, RISING);
}

void loop() {

  timer = micros();

  if (timer - lasttime > update_interval * 1000) {
  
    rpm_left = 60000000 / period_left;
    rpm_right = 60000000 / period_right;
    Serial.print(rpm_left);
    Serial.print(",");
    Serial.println(rpm_right);
    lasttime = timer;
  }
}

void RPM_left () {
  timer_left = micros();
  period_left = timer_left - lasttime_left;
  lasttime_left = timer_left;
}

void RPM_right() {
  timer_right = micros();
  period_right = timer_right - lasttime_right;
  lasttime_right = timer_right;
}

Danke im Voraus an euch,
Tobi

Hallo
In den ISRen fehlt ein NewDataFlag, das dem Hauptprogramm anzeigt, dass frische Daten abholbereit sind.

Hallo,
ich denke das sollte so ok sein, Du solltest Dir allerdings mal das ATOMIC_BLOCK-Makro. ansehen.
Den Einwand von @paulpaulson sehe ich nicht ganz so. Wenn Du eine Auswertung machst ohne das neue Werte vorliegen gelten halt noch die Alten.
https://www.arduino.cc/reference/de/language/variables/variable-scope-qualifiers/volatile/

Ohne ATOMIC_BLOCK kann Dir der Interupt dazwischen fummeln und dann hast Du Daten bei denen H und L byte nicht stimmen.
Heinz

Auswerten der volatile Variablen in loop sollte nicht unterbrechbar geschehen ("atomic")
z.B

    noInterrupts();
    rpm_left = 60000000 / period_left;
    rpm_right = 60000000 / period_right;
    interrupts();

Wie @Rentner schon geschrieben hat...

Und dass nur alle 500 ms die gerade aktuelle Drehzahl (und kein Mittelwert o.ä.) angezeigt wird, kann gewollt sein. Aber was muss passieren, damit die Drehzahl 0 wird ?

1 Like

Guter Einwand , glatt übersehen. Ich habe mal nachgesehen und das selbst mal so gemacht das ich den Zeitpunkt der letzten Messung mit abgefragt habe und wenn die Differenz > irgendwas war den Messwert auf 0 gesetzt.

 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    periode = difmcs;
    lastmess = altmcs;

  }
  if ( micros() - lastmess < 1000000 ) {
    frequenz = 1000000.0 / periode;
  }
  else frequenz = 0;

Heinz

Hallo zusammen,
vielen Dank für eure Anmerkungen.
Den Atomic Block schaue ich mir mal an, und das die Drehzhal nicht null werden kann ich mir noch gar nicht aufgefallen, vielen Dank für den Hinweiß, werde ich verbessern. Ich melde mich demnächst nochmal mit dem neuen Code :slight_smile:

Gruß,
Tobi

Hi,
habe den Code jetzt mal aktualisiert mit dem ATOMIC_BLOCK, ebenso kann die Drehzahl jetzt auch 0 werden wenn sich die Welle mit weniger als 2 U/s dreht.

Beim weiterentwickeln bin ich allerdings auf eine weiteres PRoblem gestoßen: Die entsprechenden Drehzahlen würde ich gerne auf einer SD-Karte speichern. So wie ich das jetzt allerdings verstehe, muss der befehl SD.close() am ende des programmes ausgeführt werden um die Daten zu sichern. Da die Messung allerdings mit Einschalten des Arduinos beginnt und erst endet wenn die Stromzufuhr weggenommen wird habe ich (oder kenne nur) keine Möglichkeit den Befehl am Ende der Messung auszuführen. Deswegen öffne und schließe ich die datei auf der SD-Karte jedes mal wenn ich was draufschreiben will....so jedenfalls der Hintergedanke, klappt aber nicht, nichtmal die Datei auf der SD-Karte wird erstellt, geschweige denn Daten abgelegt...

Hier der Code, evtl. findet einer von euch meinen Fehler...

`#include <SD.h>
#include <SPI.h>
File data;

#include <util/atomic.h>

volatile unsigned long period_left = 0;
volatile unsigned long period_right = 0;

unsigned long timer_left = 0;
unsigned long lasttime_left = 0;

unsigned long timer_right = 0;
unsigned long lasttime_right = 0;

long rpm_left = 0;
long rpm_right = 0;

unsigned long timer = 0;
unsigned long lasttime = 0;

long update_interval = 500;

void setup() {
  SD.begin(5);
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  attachInterrupt(digitalPinToInterrupt(2), RPM_left, RISING);
  attachInterrupt(digitalPinToInterrupt(3), RPM_right, RISING);

  data = SD.open("RPM.txt", FILE_WRITE);
  delay(100);
  data.close();

}

void loop() {

  timer = micros();

  if (timer - lasttime > update_interval * 1000) {

    if (micros() - lasttime_left < update_interval * 1000) {
      ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
        rpm_left = 60000000 / period_left;
      }
    }
    else {
      rpm_left = 0;
    }
    if (micros() - lasttime_right < update_interval * 1000) {
      ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
        rpm_right = 60000000 / period_right;
      }
    }
    else {
      rpm_right = 0;
    }
    lasttime = timer;
    
    SD.open("RPM.txt", FILE_WRITE);
    data.print(rpm_left);
    data.print(",");
    data.println(rpm_right);
    data.close();
  }

}

void RPM_left () {
  timer_left = micros();
  period_left = timer_left - lasttime_left;
  lasttime_left = timer_left;
}

void RPM_right() {
  timer_right = micros();
  period_right = timer_right - lasttime_right;
  lasttime_right = timer_right;
}`

Vielen Dank im Voraus an euch,
Tobi

Data ist da schon längst geschlossen.
Und nie wieder geöffnet worden.

Wird in der ISR modifiziert, ist aber nicht volatile, also bekommt das Hauptprogramm die Änderung nicht unbedingt mit. Das Auslesen sollte atomar erfolgen

hallo @combie
danke für den Hinweis...kann ich das atomare auslesen auch weglassen wenn ich die Variable als volatile anlege?
und wo schließe ich Data? und öffne ich das nicht mit SD.open? oder müsste dass data=SD.open heißen?
Danke,
Tobi

In setup()

Eigentlich dachte ich, dass ich mich recht klar ausgedrückt habe....
Natürlich kannst du das auch weg lassen, wenn du unbedingt deine Probleme behalten, oder diese für später aufsparen, möchtest.
Aber Sinn macht das nicht.

Dann noch:
Wieso ist das "data" Handle überhaupt global?
Warum prüfst du nicht ob die Datei überhaupt geöffnet werden konnte?

Wie gesagt, ich bin in dieser Hinsicht noch ziemlicher Anfänger.

okay, stimmt. aber ich öffne doch die datei wieder im loop, kurz bevor ich drauf schreibe, oder? hier:

    data = SD.open("RPM.txt", FILE_WRITE);
    data.print(rpm_left);
    data.print(",");
    data.println(rpm_right);
    data.close();
  }

und danach schließe ich die datei wieder, damit wenn ich den Arduino vom Strom trenne und dadurch die Messung stoppe die Dateien nicht verloren gehen...

das war im Example von der SD. Library auch so...

stimmt, das könnte ich noch einbauen...danke für den Hinweiß

volatile sagt dem Compiler, dass die Variable sich auch von außen ändern kann. Er muss also vorsichtig beim Optimieren sein. Das ist auch für einzelne Bytes relevant.
Wenn eine Variable aus mehreren Bytes besteht, darf sie sich nicht ändern, während sie gerade bearbeitet wird. Da müssen in normalem Code kurz die Interrupts geschlossen werden, damit keine ISR dazwischen funken kann.

Das sind also zwei verschiedene Probleme. Das zweite tritt nur selten auf, ist daher umso kritischer, weil es lange unentdeckt schlummern kann.

1 Like

Das stand da eben nicht so!

Beispiele sollen einfach sein.
"Guter" Code muss das nicht sein

Manchmal kann es sinnvoll sein das Handle zu "behalten", dann mag global die einfachste Variante sein.
Aber genau das willst du ja nicht, die Datei soll ja nach dem Schreiben sofort geschlossen werden.

Wäre data lokal statt global gewesen, wäre der Fehler mit dem öffnen vermutlich sofort aufgefallen! Und wenn nicht dir, dann dem Kompiler.


    File data = SD.open("RPM.txt", FILE_WRITE);
    data.print(rpm_left);
    data.print(",");
    data.println(rpm_right);
    data.close();
1 Like

oh, dann hatte ich wohl den falschen Code kopiert, ich bitte um Verzeihung.

ich passe meinen Code an und melde mich wieder sobald ich den Arduino zur Verfügung habe ob alles funktioniert. Aber vielen Dank schonmal für deine Hilfe!
Gruß

Hallo zusammen,

der Code läuft so wie ichs will, vielen Dank euch allen!!! Jetzt fehlt noch die Erweiterung um ein GPS-Modul zur Geschwindigkeitsbestimmung, aber da kümmere ich mich jetzt erstmal selbst drum :slight_smile:

hier noch der fertige Code für alle dies interessiert:

#include <SD.h>
#include <SPI.h>
#include <util/atomic.h>

volatile unsigned long period_left = 0;
volatile unsigned long period_right = 0;

unsigned long timer_left = 0;
volatile unsigned long lasttime_left = 0;

unsigned long timer_right = 0;
volatile unsigned long lasttime_right = 0;

long rpm_left = 0;
long rpm_right = 0;

unsigned long timer = 0;
unsigned long lasttime = 0;

long update_interval = 500;

void setup() {
  SD.begin(5);
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  attachInterrupt(digitalPinToInterrupt(2), RPM_left, RISING);
  attachInterrupt(digitalPinToInterrupt(3), RPM_right, RISING);
}

void loop() {

  timer = micros();

  if (timer - lasttime > update_interval * 1000) {

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
      if (micros() - lasttime_left < update_interval * 1000) {
        rpm_left = 60000000 / period_left;
      }
      else {
        rpm_left = 0;
      }
    }


    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
      if (micros() - lasttime_right < update_interval * 1000) {
        rpm_right = 60000000 / period_right;
      }
      else {
        rpm_right = 0;
      }
    }
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
      lasttime = timer;
    }
    File data = SD.open("RPM.txt", FILE_WRITE);
    data.print(round(timer/1000000));
    data.print(",");
    data.print(rpm_left);
    data.print(",");
    data.println(rpm_right);
    data.close();
  }
}



void RPM_left () {
  timer_left = micros();
  period_left = timer_left - lasttime_left;
  lasttime_left = timer_left;
}

void RPM_right() {
  timer_right = micros();
  period_right = timer_right - lasttime_right;
  lasttime_right = timer_right;
}

Hallo,
Ich würde die ISR-Variablen in nur eine Atomic Block in Variable unladen und dann mit denen weiterarbeiten. So greifst du ja unnötig mehrfach darauf zu und es werden unnötig oft die Interrupts gesperrt.
Heinz

Hallo @Rentner

ich verstehe nicht ganz wie du meinst...so?
aber die lasttime_left und lasttime_right werden ja trotzdem in der If schleife ausgelesen...

#include <SD.h>
#include <SPI.h>
#include <util/atomic.h>

volatile unsigned long period_left = 0;
volatile unsigned long period_right = 0;

unsigned long timer_left = 0;
volatile unsigned long lasttime_left = 0;

unsigned long timer_right = 0;
volatile unsigned long lasttime_right = 0;

long rpm_left = 0;
long rpm_right = 0;

unsigned long timer = 0;
unsigned long lasttime = 0;

long update_interval = 500;

void setup() {
  SD.begin(5);
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  attachInterrupt(digitalPinToInterrupt(2), RPM_left, RISING);
  attachInterrupt(digitalPinToInterrupt(3), RPM_right, RISING);
}

void loop() {

  timer = micros();

  if (timer - lasttime > update_interval * 1000) {
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
      rpm_left = 60000000 / period_left;
      rpm_right = 60000000 / period_right;
      lasttime = timer;
    }
    if (micros() - lasttime_left < update_interval * 1000) {
      rpm_left = 60000000 / period_left;
    }
    else {
      rpm_left = 0;
    }
    if (micros() - lasttime_right < update_interval * 1000) {
      rpm_right = 60000000 / period_right;
    }
    else {
      rpm_right = 0;
    }
    File data = SD.open("RPM.txt", FILE_WRITE);
    data.print(round(timer / 1000000));
    data.print(",");
    data.print(rpm_left);
    data.print(",");
    data.println(rpm_right);
    data.close();
  }
}
void RPM_left () {
  timer_left = micros();
  period_left = timer_left - lasttime_left;
  lasttime_left = timer_left;
}

void RPM_right() {
  timer_right = micros();
  period_right = timer_right - lasttime_right;
  lasttime_right = timer_right;
}

gruß und danke

Hallo , ich meinte das so :slight_smile:

es gibt noch eine Warnung für den round, hab nicht ganz verstanden wozu der gut sein soll

#include <SD.h>
#include <SPI.h>
#include <util/atomic.h>

volatile unsigned long period_left = 0;
volatile unsigned long period_right = 0;

unsigned long timer_left = 0;
volatile unsigned long lasttime_left = 0;

unsigned long timer_right = 0;
volatile unsigned long lasttime_right = 0;

long rpm_left = 0;
long rpm_right = 0;

unsigned long timer = 0;
unsigned long lasttime = 0;

unsigned long p_left,p_right; // neue Variable 
unsigned long t_left,t_right;

unsigned long update_interval = 500;

void setup() {
  SD.begin(5);
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  attachInterrupt(digitalPinToInterrupt(2), RPM_left, RISING);
  attachInterrupt(digitalPinToInterrupt(3), RPM_right, RISING);
}

void loop() {

  timer = micros();

  if (timer - lasttime > update_interval * 1000) {
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
      p_left=period_left;     // Variable umladen 
      p_right=period_right;   // und so schnell wie möglich wieder 
      t_left=lasttime_left;   // freigeben 
      t_right=lasttime_right;
    } // Atomic Block ende   
      
      rpm_left = 60000000 / p_left;
      rpm_right = 60000000 / p_right;
      lasttime = timer;
    
    if (micros() - t_left < update_interval * 1000) {
      rpm_left = 60000000 / p_left;
    }
    else {
      rpm_left = 0;
    }
    if (micros() - t_right < update_interval * 1000) {
      rpm_right = 60000000 / p_right;
    }
    else {
      rpm_right = 0;
    }
    File data = SD.open("RPM.txt", FILE_WRITE);
    data.print(round(timer / 1000000));
    data.print(",");
    data.print(rpm_left);
    data.print(",");
    data.println(rpm_right);
    data.close();
  }
}
void RPM_left () {
  timer_left = micros();
  period_left = timer_left - lasttime_left;
  lasttime_left = timer_left;
}

void RPM_right() {
  timer_right = micros();
  period_right = timer_right - lasttime_right;
  lasttime_right = timer_right;
}
1 Like

Ahh, so war das gedacht. Klar, jetzt machts klick. Danke :slight_smile: