Hallo Leute, ich Bastel gerade an einer Geschwindigkeit- / Drehzahlerfassung mittels Atmega8. Dazu nutze ich die Timer des Atmega und die Externen Interrupt Eingänge. ICP wird schon für etwas anderes verwendet. Dafür gehe ich wie folgt vor: Im Hintergrund läuft ein Timer der alle 100µs eine Variable hochzählt. Um auf die Drehzahl- / Geschwindigkeit zu schließen berechne ich die Zeitdifferenz zwischen zwei externen Interrupt Ereignissen. Ist ja soweit nix wildes. Der externe Interrupt reagiert dabei auf fallende Flanken und ruft dann die entsprechende ISR auf die die Differenz zum vorherigen Aufruf bestimmt. Problem: Wenn man jetzt z.B. die Drehzahl verringert so dass es irgendwann nicht mehr zu einem Interrupt kommt, steht in der Variablen die die Differenz-Zeit repräsentiert ja immer noch ein Wert. Es wird also z.B. noch 9 km/h angezeigt obwohl die Differenzzeit ja schon so groß ist, dass 0 km/h angezeigt werden müsste. Es wird aber nicht 0 km/h angezeigt weil ja die ISR nicht mehr aufgerufen wird. Gibt es da eine elegante Lösung so ein Problem zu lösen, so eine Drehzahl- / Geschwindigkeitsmesser gibt es ja zu haufe, die scheinen das ja auch in den Griff zu kriegen?
Ich setze dazu ein bit in der ISR. In der Auswertefunktion wird das nach der Geschwindigkeitsanzeige gelöscht, falls beim nächsten Durchlauf das Bit weiterhin gelöscht ist -> Geschwindigkeit = 0.
du könntest im Timerinterrupt eine Variable hochzählen, die du im externen Interrupt zurücksetzt. Kommt jetzt kein externer Interrupt mehr, erreicht die Variable einen festgelegten Grenzwert --> Timeout --> 0 km/h den Grenzwert legst du z.B. so fest, dass er z.B. knapp unter der ugehörigen Zeit von 1 km/h liegt also ungefähr so
1 | volatile uint8_t timeout_counter = 0; |
2 | volatile uint8_t timeout_vorhanden = 0; |
3 | #define TIMEOUT 200
|
4 | |
5 | ISR_TIMER() |
6 | {
|
7 | if (timeout_counter < TIMEOUT) |
8 | {
|
9 | timeout_counter++; |
10 | }
|
11 | else
|
12 | {
|
13 | timeout_vorhanden = 1; // Flag wird in Anzeigeroutine ausgewertet --> 0 km/h |
14 | }
|
15 | }
|
16 | |
17 | ISR_EXTERN() |
18 | {
|
19 | // irgendwas
|
20 | // ...
|
21 | |
22 | timeout_counter = 0; // Zähler zuücksetzen |
23 | }
|
1 | ISR_TIMER() |
2 | {
|
3 | if (timeout_counter < TIMEOUT) |
4 | {
|
5 | timeout_counter++; |
6 | timeout_vorhanden = 0; // Flag löschen, da kein Timeout |
7 | }
|
8 | else
|
9 | {
|
10 | timeout_vorhanden = 1; // Flag wird in Anzeigeroutine ausgewertet --> 0 km/h |
11 | }
|
12 | }
|
Oben vergessen: In der Timer ISR muss das timeout_vorhanden Flag wieder gelöscht werden, falls es schonmal gesetzt war. Sonst bliebe ja immer 0 auf dem Display stehen
Hab es gerade mal getestet klappt sehr gut. Danke schonmal für die Hilfe. Macht es vllt noch Sinn die Drehzahl bzw. Geschwindigkeit zu mitteln damit diese nicht so stark schwankt? Wobei ja das Fahrzeug selber schon wie eine art Tiefpass ist. Die Geschwindigkeit / Drehzahl eines Fahrzeuges kann sich ja nicht abbrupt ändern. Was mein ihr macht ein (gleitender) Mittelwert sinn?
:
Bearbeitet durch User
Ich empfinde 2-4 angezeigte Werte pro Sekunde als angemessen. Da du vermutlich wesentlich häufiger misst: Ja, ist sinnvoll. :-)
Die Geschwindigkeit mittle ich nicht, die steht bei mir sehr stabil. Bei einer Updaterate von 1 Sekunde passt sich die Anzeige trotzdem sehr schnell auf den aktuellen Wert an. Micha
Ich weiß ja nicht, welche Eingangsfrequenzen Deiner Drehzahlmessung zu Grunde liegen. Elegant auf der Anzeige würde es wohl aussehen, wenn je 100ms eine Messung gemacht wird, auch mit dem Ergebnis 0. Eine gleitende Mittelwertbildung über die letzten 10 Messungen, 'beruhigt' die Anzeige und zeigt auch die Tendenz des Ergebnisses. Mit dem Timeout hast Du ja schon eine Lösung gefunden; bei 100µs Intervallen braucht man eigentlich keinen separaten Zähler für Timeout, sondern kann die Messzeit nach Abschluß einer Messung auf 0 setzen und diesen Zählerwert auch fürs Timeout nutzen. Ein Beispiel, wie man Drehzahlen auch mit dem Ergebnis 0 ermitteln kann, ist hier gezeigt. Beitrag "4-Kanal Drehzahlmessung mit ATmega88" Drei der vier Kanäle mußt Du Dir wegdenken, dann wird es übersichtlicher.
m.n. schrieb: > Mit dem Timeout hast Du ja schon eine Lösung gefunden; bei 100µs > Intervallen braucht man eigentlich keinen separaten Zähler für Timeout, > sondern kann die Messzeit nach Abschluß einer Messung auf 0 setzen und > diesen Zählerwert auch fürs Timeout nutzen. Ja so mache ich das im Moment auch. Ich werde aber vllt. auf meinen 10ms Timer gehen da ich bei dem 100µs Timer im Moment eine 16bit Variable benötige. Was natürlich stört, man muss schon so 3sec warten bis dann der Wert Null angezeigt wird. Wie man auch in der Grafik sieht muss ich ja die Grenze über 3100 ticks setzen (1 tick = 100µs): http://defcon-cc.dyndns.org/wiki/images/a/af/Berechnungen_Drehzahl_Geschwindigkeit.gif > Ein Beispiel, wie man Drehzahlen auch mit dem Ergebnis 0 ermitteln kann, > ist hier gezeigt. Beitrag "4-Kanal Drehzahlmessung mit ATmega88" > Drei der vier Kanäle mußt Du Dir wegdenken, dann wird es > übersichtlicher. Ja werde ich mir mal ansehen.
Patrick L. schrieb: > Was natürlich stört, man muss schon so 3sec warten bis dann der Wert > Null angezeigt wird. Die Auswertung kann eben nicht in die Zukunft sehen. Abhilfe würde nur ein höher auflösender Geber oder einer mit phasenverschobenen sin/cos-Signalen schaffen.
m.n. schrieb: > Die Auswertung kann eben nicht in die Zukunft sehen. Abhilfe würde nur > ein höher auflösender Geber oder einer mit phasenverschobenen > sin/cos-Signalen schaffen. Ja stimmt hatte ich jetzt noch gar nicht dran gedacht, dann verringert sich natürlich die Zeit in der die ISR aufgerufen.
Nachteilig bei einem digitalen Mittelwert wäre ja das er abhängig ist von der Zykluszeit meines Programmes und da die leider schwankt würde ja auch die Mittelung nicht immer in konstanten Zeitabständen durchgeführt werden. Ich müsste eh mal nachschauen wielange den überhaupt so ein Programmzyklus dauert.
Ich werde wohl einen einfachen arithmetischen Mittelwert nehmen, da ich bei einem gleitenden Mittelwert ja die vergangenen Messwerte gewichten müsste und deshalb mit float rechnen müsste. Das versuche ich aber gerade weitesgehend aus meinem Quellcode zu verbannen bzw. durch Festkommaarithmetik zu ersetzen. Beim arithmetischen Mittelwert könnte ich ja dann mit int rechnen.
Patrick L. schrieb: > Beim arithmetischen Mittelwert könnte ich ja dann mit int rechnen. Rechne bitte mit float! Falls Du AVR-Studio verwendest, füge unter Project/Configuration Options/Libraries die libm.a zum Projekt hinzu. Der Code wird dadurch um ca. 1kB größer und die Berechnungen um Größenordnungen einfacher. Leider laufen hier sehr viele Flachmänner herum, die die Entwicklung der letzten 20 Jahre verschlafen haben und auf alten Krücken im Programm bestehen. Mach es nicht und lerne, dass float kein Problem, sondern die Lösung ist.
Sorry m.n., aber Dein beitrag ist völlig unqualifiziert. Die berechnung
kann mit integer Arithmetik ebensogut realisiert werden.
> lerne, dass float kein Problem, sondern die Lösung ist.
falsch. Die Verwendung von float ist EINE mögliche Lösung von vielen.
Offensichtlich hast Du noch nicht viele Steuerungen auf Mikrocontrollern
implementiert.
Stefan us schrieb: > Die Verwendung von float ist EINE mögliche Lösung von vielen. Na gut, wenn ich Ergebnisse mit mehr als sieben gültigen Stellen benötige, rechne ich auch gerne mit double.
m.n. schrieb: > Falls Du AVR-Studio verwendest, füge unter Project/Configuration > Options/Libraries die libm.a zum Projekt hinzu. Hab ich schon drin. Stefan us schrieb: > Die berechnung > kann mit integer Arithmetik ebensogut realisiert werden. Ich denke auch das man bei einem arithmetischen Mittelwert ohne Probleme mit int Rechnen kann, den Genauigkeitsvorteil der nachkommastellen wenn man mit float rechnen würde halte ich für marginal. Außerdem wird die Codegröße geringer und die Rechengeschwindigkeit schneller wenn kein Float verwendet wird.
Patrick L. schrieb: > Hab ich schon drin. Dann wird Dein Code aber nicht mehr kleiner, sondern größer :-) Und das mit der Rechengeschwindigkeit solltest Du mal exakt überprüfen. Als Faustformel für einen 16MHz Atmega habe ich 20-30µs pro FMUL oder FDIV im Hinterkopf. Das ist keine Zeit, über die ich mir noch große Gedanken mache. Selbst double-Berechnungen bleiben unter 100µs. Bei Deinen 3-10 Messungen/s langweilt sich der ATmega und für unverbrauchte Bits im Flash gibt es kein Geld zurück. Ach, ich zeigt Dir noch eine Drehzahlberechnung für vier Stellen mit uint32_t:
1 | skalierung = 0; |
2 | f_ref = F_CPU; |
3 | while(f_ref < 0x80000000) { // auf max. Größe bringen |
4 | f_ref *= 2; |
5 | skalierung++; |
6 | }
|
7 | while(auswerte_zeit >= 0x10000) {// auf 16 bit reduzieren |
8 | auswerte_zeit /= 2; |
9 | skalierung++; |
10 | }
|
11 | f = f_ref/auswerte_zeit; // 1. Berechnung 32/16 |
12 | f *= auswerte_impulse; // bis zu 65535 Impulsen kein Überlauf möglich |
13 | while(f > 0x10000) { // Ergebnis erneut auf 16 Bit reduzieren |
14 | f /= 2; |
15 | skalierung--; |
16 | }
|
17 | if(schalter_status & BIT(LCD_CLK)) // Frequenz ist angewählt |
18 | f *= 1000; // für Frequenz-Ausgabe skalieren |
19 | else
|
20 | f *= 60000; // Drehzahl Ausgabe skalieren |
21 | |
22 | while(skalierung--) f /= 2;// abschließend in richtigen Bereich bringen |
23 | zeige_fk_zahl(f); // uint32_t Anzeige |
Ein Profi schreibt soetwas natürlich in Assembler; ich hingegen schreibe es so:
1 | f = (float) f_ref * (float) auswerte_impulse / (float) auswerte_zeit; |
2 | if(!(schalter_status & BIT(LCD_DATA))) |
3 | f /= 1000.0; // Anzeige in kHz und kUpm |
4 | if(!(schalter_status & BIT(LCD_CLK))) |
5 | f *= 60; // für Drehzahl-Ausgabe skalieren |
6 | zeige_x(f); // Ausgabe als float |
Näheres siehe hier: Beitrag "Frequenz / Drehzahl, 4-stell. 7-Segm.-LCD, ATtiny45" Entscheide selbst.
:
Bearbeitet durch User
m.n. schrieb: > Dann wird Dein Code aber nicht mehr kleiner, sondern größer :-) Komisch bei mir wird es weniger. > Und das mit der Rechengeschwindigkeit solltest Du mal exakt überprüfen. > Als Faustformel für einen 16MHz Atmega habe ich 20-30µs pro FMUL oder > FDIV im Hinterkopf. Das ist keine Zeit, über die ich mir noch große > Gedanken mache. Selbst double-Berechnungen bleiben unter 100µs. Gut das kann schon sein, dass die einzelnen Rechenoperationen nicht viel Zeit in anspruch nehmen. Aber das Problem ist ich mache ja nicht "nur" Drehzahlmessung oder "nur" Geschwindigkeistmessung, sondern alles Gleichzeitig. Dann noch eine Menü-Steuerung, Anzeige auch einem 4x20 Zeilen LCD (USART kommt vllt. noch). Das kostet mich in Summe alles viel Zeit. Dann noch mehrere Interrupts die sich in gehege kommen könnten. Aber damit ich weiß von welchen Größenordnungen wir hier sprechen werde ich heute Abend mal schauen wie lang meine Zykluszeit so im Schnitt ist.
Patrick L. schrieb: > Aber damit ich weiß von welchen Größenordnungen wir hier sprechen werde > ich heute Abend mal schauen wie lang meine Zykluszeit so im Schnitt ist. Mach das! Und teste vielleicht noch, wieviel Zeit eine 32-Bit Integer-Division braucht. Wenn es nicht gerade x/1 oder x/256 ist kommt man auch schnell in den Bereich 20-30µs. Die Rohdaten kann man ja als uint32_t verarbeiten, aber sobald eine Division fällig wird, bietet sich float an. Zur LCD-Ausgabe: ich habe Programme, wo ich Stelle für Stelle als float-Ziffer wandel und auf ein 2x16 Display ausgebe. Dabei muß man immer noch Warteschleifen einfügen, damit die Anzeige nicht 'überrannt' wird. Und eine USART-Routine braucht nur wenige µs/Byte trotz FIFO für Rx und TX. Patrick L. schrieb: > Dann noch mehrere Interrupts die sich in gehege kommen könnten. Ich denke, da hast Du schon Deine Erfahrungen, und führst keine Berechnungen im Interrupt selbst aus. Ein wichtiger Punkt ist auch noch, dass man keine Funktionen aus Interrupts aufrufen sollte, wenn der Interrupt schnell bearbeitet werden soll. So kurz die aufgerufene Funktion auch sein mag, der Compiler sieht in der Regel nicht, was in der Funktion an Registern benötigt wird und rettet und restauriert daher alle Register. Dafür gehen dann grob 5µs verloren. Ich halte mich dann mal wieder zurück und überlasse der Fix-Komma-Nix-Fraktion das Feld.
m.n. schrieb: > Als Faustformel für einen 16MHz Atmega habe ich 20-30µs pro FMUL oder > FDIV im Hinterkopf. Also ich hab mal die Zeiten von so ein Paar Rechnungen gemessen, erstaunlich flott der Atmega hätte ich nicht gedacht und das ohne FPU. Also da muss ich dir Recht geben, vom Zeitaufwand tut sich da nicht soviel. Die Zykluszeit meines Programmes liegt so bei ca. 5ms, evtl. noch ein wenig drüber hab jetzt nicht alle Funktionen drin gehabt.
Patrick L. schrieb: > Also ich hab mal die Zeiten von so ein Paar Rechnungen gemessen, > erstaunlich flott der Atmega hätte ich nicht gedacht und das ohne FPU. > > Also da muss ich dir Recht geben, vom Zeitaufwand tut sich da nicht > soviel. Es freut mich, dass Du Dich mit dem Thema beschäftig und nicht ungeprüft alte Vorurteile wiedergekäut hast. Letztlich mache es so, wie Du es für richtig findest; da rede ich Dir nicht hinein.
Hab mich jetzt übrigens doch für den gleitenden Mittelwert entschieden, da der doch besser für meine Anwendung ist. Zunächst erstmal nur zwischen zwei Werten und ohne Gewichtung. avg_rpm(t) = (rpm(t) + rpm(t-1))/2 Ich werde mir erstmal die Messwerte per USART an den PC senden wenn der Drehzahlmesser an dem Fahrzeug angeschlossen ist, um ein gefühl dafür zu bekommen welche Drehzahlgradienten das Fahrzeug so macht. Dann weiß ich auch ob ich den gleitenden Mittelwert ggf. noch über ein paar mehr Messwerte ziehen sollte.
Eine zunächst ungeprüfte, spinnerte Idee: vielleicht bringt es ja etwas, alle ca. 300ms ein Ergebnis zu 'generieren', wobei dies durchaus auch 0 sein kann. Durch die gleitende Mittelwertbildung wird damit die sinkende Tendenz angezeigt, ohne dass man bis zum nächsten Impuls oder aufs Timeout warten muß, was dann sprunghaft zu einer '0'-Anzeige führt. Nur als Überlegung.
Ja wäre auch eine Idee, hab jetzt allerdings schon die von euch vorgeschlagene Lösung umgesetzt, nur das ich jetzt den 10ms Timer nutze, damit ich als Timeout-Variable eine 8-bit und keine 16-bit nehmen kann.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.