Tacho "überlauf"

Hi, ich habe mal eine Frage bezüglich eines Tachos.

Kurzer einblick in mein Projekt:
ich möchte an meinem Moped (Simson S51) einen Arduino Nano verbauen. Er soll mir später die aktuelle Geschwindigkeit, Drehzahl, Getriebeöltemperatur, Zylinderkopftemperatur und die Batteriespannung anzeigen.

Als erstes habe ich mit dem Tacho begonnen und bin leider direkt auf ein Problem gestoßen.
Ich nehme das Drehzahlsignal vom Rad mit Hilfe eines klassischen Reed-Kontaktes welcher auf einen Interrupt Eingang schaltet auf.
Anschließend habe ich mir einen kleinen Testaufbau erstellt wo der Magnet an einer Welle eines Elektromotors befestigt ist welchen ich über einen Frequenzumrichter unterschiedlich schnell drehen lassen kann. Grundsätzlich funktioniert auch alles.
Aber jetzt zum Problem... ab einer gewissen Geschwindigkeit "springt" die Anzeige zurück bzw. läuft über und fängt von vorne an. (Bsp. 3km/h - 4km/h - 5km/h - 6km/h - 3kmh - 4km/h - 5km/h - 6km/h - 3kmh usw.)

Könnt Ihr mir da evtl. weiterhelfen und sagen wo mein Fehler liegt ?

Mein Programm ist als Anhang hochgeladen.

PS: Sorry für den vielen Test aber ich wollte möglichst viele Radarinformationen mitgeben, dass Ihr euch ein besseres Bild machen könnt.

Viele Grüße
Erik

float start, fertig;
float vergangen, time;
float Radumfang = 0.236; //Umfang in Meter (von der Scheibe des E-Motors - deshalb so klein)
float Geschwindigkeit;

const byte Reed = 3;

void setup() {

  Serial.begin(115200);
  start = millis();
  attachInterrupt(digitalPinToInterrupt(Reed), Geschwindigkeitsberechnung, RISING);
}

void loop() {
  
  Serial.println("0 km/h");
  delay(5000);
}


void Geschwindigkeitsberechnung() {

  if((millis()-start)>100) {
    
    vergangen=millis()-start;
    start=millis();
    Geschwindigkeit=(3600*Radumfang)/vergangen;
    Serial.print(Geschwindigkeit);
    Serial.println("km/h");
  }

}

Tacho.ino (666 Bytes)

Setze Deinen Code bitte direkt ins Forum. Benutze dazu Codetags (</>-Button oben links im Forumseditor oder [code] davor und [/code] dahinter ohne *).
Dann ist er auch auf mobilen Geräten besser lesbar.
Das kannst Du auch noch nachträglich ändern.

Gruß Tommy

Ah ok wusste nicht wie das geht, Dankeschön!

Hallo,

die Interrupt Routiene muss so schnell wie möglich abgearbeitet werden. Berechnungen und serielle Ausgaben haben darin nichts zu suchen.

Du solltest innerhalb der ISR nur die zeit zwischen zwei Impulsen messen und alles Andere im loop machen. Damit das dann auch richtig klappt musst du die Variable für die Zeitmessung als

volatile unsigned long

festlegen und bei der Abfrage des Wertes verhindern das während der übergabe des Wertes dieser wieder durch die ISR verändert werden kann. Das geht mit der Verwendung eines Atomic Blockes

Zudem würde ich micros() nehmen. Dann muss Du noch den Fall abfangen das keine Impulse mehr kommen das gilt dann für eine Mindestgeschwindigkeit.

Heinz

code nicht lauffähig

volatile  unsigned long vergangen;

....

loop{

ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    periode = vergangen;
   
  }
  Serial.print(periode);
// Hier die Berechnung
...

}

void isr_messen{

uint32_t static altzeit;

vergangen= micros() -altzeit;

}

(deleted)

@ Heinz
Vielen Dank für die schnelle Antwort allerdings bekomme ich es so leider nicht wirklich ans laufen. Wenn ich im Atomic Block "periode = vergangen" angeben will zählt er immer weiter hoch. Sprich ich bekomme "vergangen" nicht zurückgesetzt. Somit mache ich die Rechnung im Atomic Block zu nicht da er durch eine immer größer werdende Zahl teilt und so die Geschwindigkeit quasi "rückwärts" läuft.

@ Peter
Ja das stimmt allerdings hatte ich einen Reed Kontakt über und wollte es erstmal damit versuchen. Wenn er zu viel Prellt kann man das ja recht einfach austauschen.

LG

  1. Float Variablen für die Zeiten um millis() sind falsch. ein Floatwert kann nur 6 bis 7 signifkante Stellen haben während ein unsigned long fast 10 hat. Differenzen zwishen 2 Floatwerte werden dann plötzlich null.

  2. Dein Test-aufbau ist unrealistisch. Du fährst mit Deinem Moped keine Schallmauer nieder.

Grüße Uwe

erikhantscho:
@ Heinz
Vielen Dank für die schnelle Antwort allerdings bekomme ich es so leider nicht wirklich ans laufen. Wenn ich im Atomic Block "periode = vergangen" angeben will zählt er immer weiter hoch. Sprich ich bekomme "vergangen" nicht zurückgesetzt. Somit mache ich die Rechnung im Atomic Block zu nicht da er durch eine immer größer werdende Zahl teilt und so die Geschwindigkeit quasi "rückwärts" läuft.

@ Peter
Ja das stimmt allerdings hatte ich einen Reed Kontakt über und wollte es erstmal damit versuchen. Wenn er zu viel Prellt kann man das ja recht einfach austauschen.

LG

Hallo ,

Da Du es eigentlich mit recht niedrigen Frequenzen zu tun hast ist eine Periodendauer Messung schon richtig. Ob man das nun mittels Interrupt machen muss ist eine andere Fragen, es ginge auch im Loop.

sorry habe gerade gesehen in der ISR fehlt eine Zeile

code nicht getestet.

void isr_messen{

uint32_t static altzeit;

vergangen= micros() -altzeit;
altzeit=micros(); // ..............fehlte .............
}

oder besser

void isr_messen{

uint32_t static altzeit;
uint32_t jetzt;

jetzt=micros();

vergangen= jetzt -altzeit;
altzeit=jetzt;
}

so sollte es gehen

Heinz

(deleted)

@Heinz und Peter
Vielen Dank für die Hilfe. Ich konnte aus beiden Codes etwas rausnehmen und habe jetzt einen Code der nach meinen Wünschen funktioniert! Allerdings benötige ich jetzt wie schon von Peter vermutet einen Hall-Sensor da der Reed bei höheren "Geschwindigkeiten" anfängt zu prellen (konnte man auf dem Oszilloskope gut erkennen). Also Danke! :slight_smile:

@Uwe
grundsätzlich hast Du da recht. Da ich den Motor aber wie erwähnt über einen Frequenzumrichter ansteuer bin ich bei der Drehzahl flexibel und kann so realistische Raddrehzahlen simulieren.

Viele Grüße
Erik

(deleted)

Hallo,

Du könntest den Reedkontakt mit einer RC Beschaltung versehen. Versuch mal 100 nF paralell zum Kontakt. Zusammen mit dem internen Pullup sollte das eventuell schon reichen.

Mit der Messung Anzahl Ereignisse über eine bestimmte Zeit(Tormessung) wird der Messwert bei kleinen Frequenzen ziemlich ungenau.

Ein Beispiel Dein Rad hat 1,5m Umfang. Bei einem Impuls je Umdrehung macht das macht bei 50Km/h etwa 9,25 Hz.Damit bekommst Du mit einer Sekunde Messzeit ca.10 Impulse und somit ist der Zählerstand entweder 9 oder 10. Damit ist die Auflösung dann 10% und die Anzeige an dem Deinem Tacho etwa 45 oder 50Km/h.

Freguenzen unter 100Hz sollte man immer mittels Periodendauer messen oder halt lange Messzeiten in Kauf nehmen, aber wer will das schon.

Heinz

Hallo,

klar gerne. Der Code ist jetzt im Endeffekt doch sehr ähnlich zu meinem ersten Ansatz.
Die ist immer noch viel zu lang etc. allerdings funktioniert er jetzt für mein Einsatzgebiet ausrechend gut.
Muss jetzt nur noch die Entprellung, z.B. wie Heinz es vorgeschlagen hat. Bei einer simulierten Geschwindigkeit von ~90km/h ist die Genauigkeit aber eigentlich noch recht ausrechend. Zumal ich beim Serien Motor am Moped ja eh nur an die 70-80 komme.

Viele Grüße
Erik

float start;
float vergangen, time;
float Radumfang = 1.680; //Umfang in Meter

volatile byte Geschwindigkeit;

const byte Reed = 3;

void setup() {

  Serial.begin(9600);
  Serial.println("----------Tacho_Test----------");
  delay(2000);
  start = millis();
  attachInterrupt(digitalPinToInterrupt(Reed), Geschwindigkeitsberechnung, RISING);
  
}

void loop() {

  Serial.println("0 km/h");
  delay(1000);
  
}


void Geschwindigkeitsberechnung() {
    
    vergangen=millis()-start;
    start=millis();
    Geschwindigkeit=(3600*Radumfang)/vergangen;
    if(Geschwindigkeit > 0) {
     Serial.print(Geschwindigkeit);
     Serial.println("km/h");
    }
          
}

(deleted)

Hallo,

das ist eine Periodendauer-Messung , soweit ok. Nur das Alles innerhalb der ISR ? sogar ein delay?

Wenn Du dann später noch was mehr in der Loop machst wird es irgendwann so nicht mehr gehen.

Heinz

Verzeiht dass ich mich mit einer etwas harschen Wortmeldung hier einmische.

Es gibt ein paar Sachen, die man in einer Interrupt Service Routine gar nie nicht macht.
Dazu gehören delay() und die Benutzung der seriellen Schnittstelle. Punkt.

Begründung (besonders gültig für Ein-Prozessor-Systeme):
Die ISR dient nur dazu, schnellstmöglich auf ein externes Ereignis zu reagieren (eine Ressource wird verfügbar oder gesperrt, ein Meßwert steht bereit oder so). Im schlimmsten Fall verliert man Ereignisse, weil man gerade in der Abarbeitung des vorherigen (oder einer anderen Eventquelle) rumgetrödelt hat.
Eine CPU kann halt nur eins zu einer Zeit machen, der Interrupt unterbricht alles andere.

Die Verarbeitung soll dann wenn immer möglich passieren, wenn Zeit dafür ist und wenn es nix ausmacht dass man u.U. unterbrochen wird.
Dazu würde ich dann eigentlich auch die Berechnung der Geschwindigkeit aus der Periodendauer zählen.

Just my 2ct,
Walter

(deleted)

Peter-CAD-HST:
Und wenn der Arduino nix anderes mehr macht, dann passt das.

Da mag ich nicht grundsätzlich widersprechen - kann sein, dass es hier funktioniert.

Wir werden etwas off topic, aber das Thema ist m.E. schon wichtig :slight_smile:

@Erik:
Ich empfehle trotzdem, dass Du Dir das gar nicht erst angewöhnst. Irgendwann später bei komplizierteren Projekten suchst Du Dir dann einen Wolf, wenn das Programm (meist sporadisch) nicht funktioniert wie erwartet und/oder stehenbleibt.

Soweit mir bekannt, benutzt die Serial() selbst Interrupts. Die sind wohl von niedrigerer Priorität als die Pin-Interrupts, deshalb wird die Zeitmessung funktionieren. Es können aber Datenverluste auf der seriellen Schnittstelle auftreten.

In der Beschreibung von attachInterrupt() steht dann noch dies:

millis() verlässt sich zum Zählen auf Interrupts, wird also in einer Interrupt Service Routine niemals hochzählen.
delay() benutzt ebenfalls Interrupts und wird deshalb gar nicht in einer Interrupt Service Routine funktionieren.
micros() wird anfangs gut funktionieren, aber nach circa 1 bis 2 ms sich unvorhersehbar verhalten.
delayMicroseconds() benutzt keine Zähler und wird deshalb normal funktionieren.

Gruß Walter

Hallo Walter,
ja das stimmt schon und an das Problem werde ich wohl noch kommen da das Programm später ja noch einiges mehr können soll (Getriebeöltemperatur, Zylinderkopftemperatur, Batteriespannung, Uhrzeit, Drehzahl, Geschwindigkeit und das dann alles auf ein Oled Display).

-> sprich, die Probleme werde ich wahrscheinlich noch bekommen allerdings hatte ich Schwierigkeiten den den gemessenen Zeitwert außerhalb der isr in der Geschwindigkeitsformel zu verwenden.

Ich kann meine Schaltung leider nicht komplett simulieren da mir noch die meisten Teile fehlen (bestellt).

Viele Grüße
Erik

Alles gut; muss ja nicht alles heute nachmittag fertig sein :slight_smile:

Wenn Du ein wenig Englisch kannst: Hier ist ein schönes kleines Beispiel für diesen Zugriff zu finden. Heinz hatte das in Beitrag #3 schon so empfohlen.

Prinzip kurz erklärt:

  • Vor setup() muß die Variable, die in der ISR manipuliert werden soll, als "volatile" definiert werden (Warum - das wäre eine eigene Geschichte).
  • In der ISR wird die dann beschrieben. Das wird nicht unterbrochen, also kein Problem.
  • Außerhalb im loop() erzeugst Du Dir dann eine lokale Kopie des Wertes - und das in einem ATOMIC_BLOCK. Der verhindert, dass innerhalb des Blocks die ISR aufgerufen wird und damit Byte-Salat entsteht.
  • Mit der Kopie arbeitest Du weiter.

In dem Beispiel ist der ATOMIC_BLOCK auskommentiert, um das mögliche Problem sichtbar zu machen.
Der Block sind alle Anweisungen zwischen den geschweiften Klammern - dort nur eine (kopieren des Wertes). Das dürften auch mehrere Zeilen sein, aber für diese Blöcke gilt sinngemäß das gleiche wie für die ISR: So schnell wie möglich - also darin nur das Nötigste machen.

Gruß Walter