Geschwindigkeitsberechnung verbessern

Für meine Kettenölung berechne ich die Geschwindigkeit. Folgende Grundlagen sind gegeben:

  • der Radumfang ist bekannt
  • die Anzahl der Impulse pro Radumdrehung sind bekannt
  • jeder Impuls feuert einen Interrupt

Prinzipiell kann man die Geschwindigkeit jetzt präzise errechnen, indem man den Radumfang durch die Impulsanzahl teilt und dann bei jedem Impuls diese Strecke durch die Zeitspanne zwischen vorangegangenem Impuls und dem aktuellen Impuls teilt.

Das ist suboptimal da: - der Interrupt dann zu lange rechnet - ein Stillstand nicht erkannt werden kann (es kommt ja kein Interrupt)

Meine aktuelle Lösung sieht daher völlig anders aus: - der Interrupt zählt lediglich einen Zähler hoch - in einem quasi festen Intervall (mit millis) wird der Zähler ausgelesen und somit die Strecke berechnet und anhand der Intervallzeit die Geschwindigkeit - damit das nicht zu extrem schwankt werden die letzten 5 Werte gemittelt

Stillstand wird so erkannt und der Interrupt macht keine langen Operationen Allerdings ist diese Variante 1. träge und 2. ungenau, da das Intervall ja auch schwanken kann (der Temperatursensor braucht z.B. zum auslesen relativ lange und verzögert dann).

Jetzt suche ich nach einer Möglichkeit, das zu verbessern. Erste Idee: Beim Interrupt auch noch die aktuellen Millis speichern. Somit kann ich später das echte Intervall für die gezählten Impulse berechnen und somit sollte auch die Geschwindigkeit genau werden. Problem dabei: - der Interrupt rechnet mehr (ich weiß nicht, ob lastTime = millis() jetzt viel Zeit frisst) - ich habe beim Auslesen der beiden Werte jetzt eine RaceCondition. Ich lese die Impulszahl, dann kommt der Interrupt und dann lese ich lastTime. In dem Fall wäre lastTime schon vom nächsten Impuls, den ich aber gar nicht gelesen habe. Klar könnte ich da immer Interrupt abschalten aber dann verliere ich ggf. Impulse, das will ich ja auch nicht.

Hat jemand ne Idee, wie man das noch besser lösen könnte? Oder ist die genannte Lösung schon gut und die RaceCondition ist unkritisch?

Bei 1,5m Radumfang hast du bei 300km/h zwischen 2 Sensorpulsen 18ms. (Stimmt das?) Das ist eine kleine Ewigkeit. Da kannst du einiges erledigen....

Raceconditions ausschließen, ist eine Gute Tat, welche dich vor Folgeproblemen schützt. Stichworte: "Interrupt Volatile Atomic" Das geschützte Auslesen der Variablen geht in ein paar µs. Ich sehe nicht, dass dir Interrupts verloren gehen können. Du hast schließlich fast 36ms Zeit dafür.

Wie ich es machen würde: in der Interruptroutine bei jedem Impuls einen Zähler hoch setzen. So bekommt man die absolute Anzahl der Umdrehungen. Die Zeit zwischen diesem und vorherigem Impuls berechnen und speichern. Das liefert die aktuelle Geschwindigkeit. Vielleicht für dich von Interesse: Gleitender Mittelwert.

Nachtrag:

die Anzahl der Impulse pro Radumdrehung sind bekannt

Oh... Wieviele sinds denn?

Impulse sind bei mir aktuell 1, weil ich einen Magnetkontakt verwende. Das mache ich aber auch nur, weil ich noch nicht weiß, wie ich an das Tachosignal rankomme. Das echte Tachosignal könnte dann bis zu 100 Impulse pro Umdrehung haben (ich weiß bei meinem Motorrad nicht, wie viele es sind, ich kanns ja nicht messen).

Also kann ich meine oben genannte Variante machen (zähler hochsetzen und letzte millis merken) und mit ATOMIC_BLOCK beide Werte gesichert auslesen. Der Interrupt müsste ja dann automatisch warten, bis der Block durch ist.

Dahinter kann ich dann in meiner zyklisch laufenden Methode gemütlich die Geschwindigkeit berechnen.

Eine Mittelwertbildung mache ich ja bereits (ein Array mit 5 Werten, die durchgeschoben werden - FIFO). Der Tiefpassfilter wird glaube ich nicht funktioneren. Wenn ich z.B. immer Xn-1 + (Xn-1-Xn)/5 verwende, dann findet bei einstelligen Werten irgendwann keine Änderung mehr statt. Außerdem wird das ebenso träge sein.

Mit der neuen Berechnung müssten die Schwankungen doch sowieso schon ziemlich klein werden (weil die Zeiten ja genauer sind), da kann man den Mittelwert vielleicht einfach weglassen. Ich werds auf jeden Fall mal ausprobieren.

den Mittelwert vielleicht einfach weglassen

Nicht dass die Anzeige der Geschwindigkeit weit hinter der gefühlten Beschleunigung her hinkt ;)

Nee darum gehts ja gar nicht. Soll ja auch kein Tacho sein, sondern lediglich zur Kontrolle (Plausibilität) und zur Optimierung der Ölung (es wird vorzugsweise in einem bestimmten Geschwindigkeitsbereich geölt, damit nicht gleich wieder alles wegfliegt).

Trotzdem hab ich an mich selbst den Anspruch, es möglichst korrekt zu machen :D

Nun ist die aktuelle Geschwindigkeit für einen Kettenöler ja nicht so kritisch.

Ich würde das schon auch so machen:

  • der Interrupt zählt lediglich einen Zähler hoch

Allerdings würde ich dann nicht in festem Zeitfenster auslesen, sondern wenn der Zähler einen bestimmten Wert erreicht hat. Dann bist du genauer.

Und noch eine Timeout funktion für die Stillstandserkennung.

Einen Tiefpass (Glättungsfunktion) brauchst du meiner Meinung nach nicht, aber du solltest unmögliche Werte abfangen.

Wenn dein Tempertursensor die Loop blockiert, würde ich einfach die erste Geschwindigkeitsmessung nach der Temperaturmessung wegwerfen, weil die Blödsinn bringen kann. Letztlich reicht es ja, die Temperatur nur alle 10min zu messen.

Auch ne interessante Idee, den Zähler als Bedingung zu nehmen und dann die Zeit zu messen. Da entsteht aber das Problem, dass je nach dem sehr selten gerechnet wird (nur 1 Impuls pro Radumdrehung und geringe Geschwindigkeit) oder extremst oft (bei z.B. 50 Impulsen pro Umdrehung und sehr hoher Geschwindigkeit).

Wobei auch das sich eindämmen ließe. In der Bedingung verwendet man einfach die Impulszahl und erwartet somit eine Radumdrehung. Bei 3,6km/h würde man dann jede Sekunde 1x rechnen. Der Timeout würde dann bei ca. 1 sec liegen, dann passt das (alles unter 3,6km/h ist zero).

Die Variante schwankt dann aber wieder, weil zwischen Impuls und tatsächlicher Auswertung Zeit verloren gegangen sein kann (Datenübertragungen im Onewire oder SPI). Und der Temperatursensor liest alle 5sec oder so. Das könnte ich zwar erhöhen aber mit der anderen Variante kann ich genauer rechnen ohne dass ich das einschränken muss.

Ich denke ich versuche erstmal die Variante mit millis bei Interrupt.

TelosNox: Auch ne interessante Idee, den Zähler als Bedingung zu nehmen und dann die Zeit zu messen. Da entsteht aber das Problem, dass je nach dem sehr selten gerechnet wird (nur 1 Impuls pro Radumdrehung und geringe Geschwindigkeit) oder extremst oft (bei z.B. 50 Impulsen pro Umdrehung und sehr hoher Geschwindigkeit).

Nicht unbedingt. Zwischen 50 und 1 Puls pro Umdrehung ist ja ein Hardwareunterschied. Ich würde das ganze so einstellen, das ca alle 5-10Umdrehungen die Geschwindigkeit erreichnet wird.

Aber das hängt auch von deiner Restsoftware ab: Wenn du genügend Rechenpower übrig hast, um bei jedem Puls die Zeit mit abzuspeichern ist das sicherlich die genauere Variante.

Sodele, ich habe fertig. Die millis seit dem letzten Impuls waren sowieso schon vorhanden (brauche ich, um einen Ausfall des Tachosignals zu erkennen und die Ölung auf Zeitbasiert umzuschalten). Die Glättung hab ich im ersten Schritt komplett rausgenommen. Das führt aber dazu, dass ich das Berechnungsintervall ziemlich groß machen muss oder dass bei niedriger Geschwindigkeit und 1 Impuls pro Radumdrehung ein Toggeleffekt eintritt (zwischen 0 und X). Ich habe also das Intervall auf 1sec festgelegt und bilde das Mittel mit dem vorangegangenen Wert. Das führt zwar in einem kleinen Bereich immer noch zum Toggeln, aber bei der Geschwindigkeit kippt man eh um, da schaut man nicht aufs Display.

Die eigentliche Berechnung

void ChainOiler::calculateSpeed() {
 static unsigned long nextSpeedMillis = 0;
 static unsigned long lastCalcMillis = 0;
 static int lastSpeed = 0;

 unsigned long currentMillis = millis();
 if (currentMillis > nextSpeedMillis) {
 nextSpeedMillis = currentMillis + SPEED_INTERVALL;

 int currentTicks = _speedTicks;
 unsigned long lastTick = _lastTickMillis;
 long relevantMillis = lastTick - lastCalcMillis;
 lastCalcMillis = lastTick;

 long calcSpeed = ((long)currentTicks * _speedTickFactor) / relevantMillis;
 calcSpeed /= 1000;
 _speed = (calcSpeed + lastSpeed) / 2;

 _speedTicks -= currentTicks;
 lastSpeed = calcSpeed;
 }
}

Im interrupt

void ChainOiler::processTick() {
 _speedTicks++;
 _lastTickMillis = millis();
}

_speedTickFactor errechnet sich mit

_speedTickFactor = rotationLength * 3600 / tickPerRotation;

rotationLength ist der Radumfang in mm.

Getestet hab ich das Ganze einfach mit nem zweiten Arduino und dem Blink Sketch. Die Blinkfrequenz bestimmt die simulierte Geschwindigkeit. Verbunden sind lediglich die beiden jeweiligen Digitalpins miteinander. Ich nehme mal an, dass es mit der Flankenerkennung sauber klappt, weil beide am selben USB hängen und somit auch die Masse teilen.

Von der Frequenz her gehts mit nem Kontakt am Rad (und somit 1 Impuls) auf jeden Fall ohne Probleme. Ich hab mal auf 500hz erhöht (mehr bekomm ich ja nicht sinnvoll simuliert) und auch das ging. Allerdings hatte das scheinbar irgendwann Auswirkungen aufs Display. Da war auf einmal alles um ca. 50% nach unten (bzw. oben) verschoben. Das muss ich auf lange Sicht mal noch beobachten.