Forum: Mikrocontroller und Digitale Elektronik Drehzahl / Geschwindigkeit "nullen"


von Patrick L. (crashdemon)


Lesenswert?

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?

von Micha H. (mlh) Benutzerseite


Lesenswert?

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.

von chris (Gast)


Lesenswert?

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
}

von chris (Gast)


Lesenswert?

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

von Patrick L. (crashdemon)


Lesenswert?

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
von Svenska (Gast)


Lesenswert?

Ich empfinde 2-4 angezeigte Werte pro Sekunde als angemessen. Da du 
vermutlich wesentlich häufiger misst: Ja, ist sinnvoll. :-)

von Micha H. (mlh) Benutzerseite


Lesenswert?

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

von m.n. (Gast)


Lesenswert?

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.

von Patrick L. (crashdemon)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

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.

von Patrick L. (crashdemon)


Lesenswert?

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.

von Patrick L. (crashdemon)


Lesenswert?

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.

von Patrick L. (crashdemon)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

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.

von Patrick L. (crashdemon)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

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
von Patrick L. (crashdemon)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

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.

von Patrick L. (crashdemon)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

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.

von Patrick L. (crashdemon)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

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.

von Patrick L. (crashdemon)


Lesenswert?

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
Noch kein Account? Hier anmelden.