Archiv verlassen und diese Seite im Standarddesign anzeigen : Tacho-Signal Erfassung
Hallo,
ich möchte gerne mit einem ATtiny2313 bei einem E-Bike die Geschwindigkeit erfassen. Dazu habe ich das Tacho-Signal gefunden. Dieses liefert mir 20 High-Impulse pro Rad-Umdrehung, bzw. 40 Fankenwechsel pro Umdrehung.
Der Rad-Durchmesser ist 22", was einen Umfang von ca. 175cm entspricht. Dies bedeutet wiederum eine gefahrene Strecke von 4,3 cm pro Flankenwechsel am Tacho-Signal.
Wenn man 1 km/h fährt bedeutet dies 23256 Flankenwechsel bzw. 6,5 pro Sekunde. Die Zeit zwischen den Flankenwechseln ist 155 ms.
Bei 100 km/h sind dies entsprechend 2325600 Flankenwechsel bzw. 646 pro Sekunde. Die Zeit zwischen den Flankenwechseln ist 1,55 ms.
Soweit zur Theorie. Der Tacho ist am INT0 angeschlossen.
Meine erste Herangehensweise war beim Flankenwechsel von INT0 einen Zähler laufen zu lassen.
Weiterhin habe ich den Timer0 im AVR dazu benutzt um einen Takt von 1 Sekunde zu erzeugen. (Interner RC-Oszillator mit FCPU = 8000000 und den Timer0-PreScaler auf 256. Den Zähler lade ich dann immer beim Overflow mit dem Wert 5. Beim Overflow-IRQ hat er dann 250 Stellen gezählt. (5 + 250)
8000000 / 256 / 250 = 125
Diese 125 zähle ich im Overflow-Interrupt dann noch mit einem eigenen Zähler, danach ist meine Sekunde abgelaufen. Dies habe ich durch blinken einer LED überprüft.
Immer bei der Sekunde übernehme ich meinen Wert der gezählten Flanken von INT0 in eine Variable und setzte diesen Zähler dann wieder auf 0.
Diesen Wert in der Variable muss ich dann nur noch durch 3,25 teilen (6,5 Impulse pro km/h / 2, warum durch 2 ist mir gerade entfallen) und habe damit die gefahrene Geschdingkeit in km/h.
Leider ist diese Methode zu "grob". Ich würde gerne eine Auflösung in Zehntel-km/h haben, was mit diese Methode scheinbar nicht möglich ist. Zumindest waren die Sprünge auf meinem Display immer zu groß.
Kann das jemand nachvollziehen? Habe ich vielleicht irgendwo einen Denkfehler?
Ich habe dann weiterhin versucht die Zeit zwischen den Flankenwechseln zu messen.
Dafür benutzte ich den Timer1 mit einem Prescaler von 8. Dies bedeutet ich kann mit einer Auflösung von 1 µs Zeit messen. Wenn ich 1,55ms als "schnellste" Zeit für 100 km/h messen möchte ist dies mehr als ausreichend.
Im Overflow-Interrupt des Timer1 lasse ich dann noch eine weitere U16-Variable die Überläufe zählen, da die 16 Bit des Timer1 sonst nicht ausreichen würden.
Beim Flankenwechsel INT0-Interrupt berechne ich dann eine U32-Zahl, die die Zeit zwischen diesem und dem letzten Flankenwechsel in µs darstellt.
Von dieser Zahl muss ich dann nur noch in ms umrechnen und die 155 ms durch diese Zahl teilen.
Dummerweise ist dieser Wert total "instabil" und hört auch bei ca. 20 km/h auf und steigt nicht weiter. Selbst mit einer Glättung über 5 Werte springt der Wert munter um bis zu 5 km/h umher. Weiterhin verstehe ich nicht, warum nach ca. 20 km/h Schluß sein soll.
Die ganze Umrechnung mit Multiplikation und Divison übernehme ich im Hauptprogramm, welches nur aus einer Schleife besteht, die die Umrechnung durchführt und den Wert auf einem Display anzeigt.
In den Interrupt-Routinen wird so wenig wie möglich durchgeführt. Im INT0 werden nur die 3 Werte (Überlauf-Zähler, TCNT1L und TCNT1H) in temporäre Variablen kopiert und der Zähler dann auf 0 gesetzt. Im Overflow-Interrupt vom Timer1 zähle ich nur die U16-Variable um eins weiter.
Ich sitzte an diesem Problem nun schon sehr viele Stunden über die letzten Tage verteilt. Aber irgendwie stecke ich nun in einer Sackgasse. Wo könnte mein Denkfehler liegen? Was mache ich falsch?
Viele Grüße
Andreas
Du könntest dich auf die erste Variante zurückbesinnen, statt einer Sekunde aber nur 1/10 lang Zählen und die gezählten Flankenwechsel dann in eine Geschwindigkeit umrechnen. Diesen Wert vielleicht noch etwas glätten und volia hast du eine feinere aber dennoch recht genaue Anzeige der Geschwindigkeit.
Zu den anderen Problemen: Nur die Zeit zwischen zwei Flanken zu nehmen, stellt höhere Ansprüche an die Rechenleistung (sonst verpasst du schnell Mal einige Flanken) und kann ungenauer sein, da du für eine solche Berechnung eine sehr hoch aufgelöste Zeitbasis brauchst. Mitzählen über ein definiertes Intervall scheint mir da besser geeignet zu sein.
mfG
Markus
Du könntest dich auf die erste Variante zurückbesinnen, statt einer Sekunde aber nur 1/10 lang Zählen und die gezählten Flankenwechsel dann in eine Geschwindigkeit umrechnen. Diesen Wert vielleicht noch etwas glätten und volia hast du eine feinere aber dennoch recht genaue Anzeige der Geschwindigkeit.
Ich glaube das geht genau in die Falsche Richtung.
Gehen wir mal von einer "Standard"-Geschwindigkeit von 10 km/h aus. Das würde bedeuten ich erhalte in einer Sekunde 65 Flankenwechsel. Für die gewünschte Auflösung von Zenhtel-km/h bräuchte ich hier aber mindestens einen Wert von 100.
Wenn ich jetzt das Zeitfenster noch kürzer wähle erhalte ich ja noch weniger Impulse, die ich nur mit einer größeren Zahl multiplizieren muss und dadurdch wird die Auflösung noch geringer.
Oder stehe ich gerade auf dem Schlauch?
Zu den anderen Problemen: Nur die Zeit zwischen zwei Flanken zu nehmen, stellt höhere Ansprüche an die Rechenleistung
Sollte eigentlich nicht der Fall sein. Timer1 sollte bei FCPU = 8000000 mit Prescaler 8 im Overflow nur ca. 15x pro Sekunde aufgerufen werden. Und dabei wird eine U16-Variable um 1 erhöht. Das ist quasi nix.
Und der INT0 für den Flankenwechsel kommt bei 100 km/h ca. 646x pro Sekunde und dabei wird die U16-Variable in eine andere U16 kopiert und die beiden 8-Bit-Register des Timers in zwei U8-Variablen kopiert. Das ist zwar ein bischen mehr, aber eigentlich auch nix.
Die Berechnung wird dann in der main-Schleife durchgeführt. Und hier habe ich die restlichen ca. 79000000 Zyklen von FCPU frei um den Wert zu berechnen und am Display anzuzeigen. Aber ich vermute selbst dass schafft der AVR noch mehrere Male pro Sekunde.
Da die IRQ's so schnell wieder verlassen werden sollte eigentlich hier keine Flanke verpasst werden...
Hier mal der Code der Interrupts als Beweis:
// Tacho-Eingang Signalwechsel:
SIGNAL (INT0_vect)
{
TachoTimeLow = TCNT1L;
TachoTimeMiddle = TCNT1H;
TachoTimeHigh = TachoTimeCounterHigh;
TCNT1L = 0;
TCNT1H = 0;
TachoTimeCounterHigh = 0;
}
// Tacho-Eingang-Zeitmessung:
SIGNAL (SIG_TIMER1_OVF)
{
if (TachoTimeCounterHigh < 0xFFFF)
TachoTimeCounterHigh++;
}
Viele Grüße
Andreas
Zeig Mal den Code für die Berechnung, zu Testzwecken kannst du ja auf dem Display auch Mal die µS zwischen zwei Flanken anzeigen. Ich würde fast vermuten, dass bei der Berechnung irgendwo ein Datentyp überstrapaziert wird, der Zusammenhang der Zwischenflankenzeit mit der Geschwindigkeit ist ja umgekehrt proportional.
Allgemeiner Debugging-Tipp: Macht eine Berechnung komische Dinge, versuch Rohdaten zu bekommen und rechne einmal per Hand. Stimmt das Ergebnis, hast du entweder die Formel falsch einprogrammiert (falsche Klammerung o.ä.) oder dir läuft ein Datentyp über.
mfG
Markus
Edit: Erfasst du die Rohdaten mit dem Rechner, kannst du dir auch leicht mit einer Tabellenkalkulation einen Plot ausgeben lassen, was auch oft hilfreich sein kann. (Man sieht auch oft sehr schön, wie verrauscht die Messdaten doch sind ;))
Hallo,
ich habe auf dem Display die µS angezeigt. Es ist leider schwer was klares abzulesen, da sich der Wert zu oft ändert, aber die Ungenauigkeit war klar zu erkennen. Hier der gewünschte Code:
U32 TachoTime = TachoTimeHigh;
TachoTime = TachoTime<<16;
TachoTime = TachoTime + TachoTimeMiddle;
TachoTime = TachoTime<<8;
TachoTime = TachoTime + TachoTimeLow;
LCD_Cursor (1, 1);
LCD_Zahl (TachoTime);
Die Funktion LCD_Zahl gibt eine 10-stellige Zahl auf dem LCD mit führenden Nullen aus.
Ich habe die Berechnung extra in mehrere Schritte zusammengefasst, um den von dir erwähnten Überlauf zu vermeiden. Ich sitze nicht umsonst schon so lange am Problem. Ich glaube ich habe alles ausgeschlossen, was auszuschliessen ist...
U32 TachoTime = TachoTimeHigh;
TachoTime = TachoTime<<16;
TachoTime = TachoTime + TachoTimeMiddle;
TachoTime = TachoTime<<8;
TachoTime = TachoTime + TachoTimeLow;
Das sieht doch schon mal reichlich merkwürdig aus. Ist TachoTimeMiddle tatsächlich 16 Bit groß und TachoTimeLow 8 Bit?
Wie synchronisierst du deine Hauptschleife auf den Interrupt?
Außerdem: So erfasst du ja "nur" den Abstand zwischen zwei Flanken, unter Umständen ist das Signal aber ungleichmäßig (evtl. die High-Phase kürzer als die Low-Phase oder so). Versuch Mal, die Hauptschleife und die Zeitnahme auf jede zweite Flanke zu synchronisieren, so dass du einen vollen Zyklus misst.
Falls du das bereits getan hast: Sorry, aber das steht nicht in dem Code den du gepostet hast.
mfG
Markus
EDIT: Stefan hats gefunden, ich war zu Betriebsblind :P, klar, du darfst jeweils nur um 8 Bits schieben, so machst du ein Loch rein und schiebst die oberen 8 Bits raus..
Guten Morgen,
Stefan hats vermutlich wirklich gefunden. Da war ich dann wohl irgendwann zu blind dafür. Ich wollte eigentlich die 16 Bits aus TachoTimeHigh "wegschieben". Aber logisch: Die müssen um 8 geschoben werden, um den 8 Bit von TachoTimeMiddle Platz zu machen.
Ich werde diese Zusammenstellung der 3 Werte auch wieder in den Interrupt schieben, da sonst der Interrupt ja während der Berechnung die Werte verändern könnte. Sind ja immerhin 6 Befehle mit vermutlichem Zugriff auf Daten im RAM. Da ist die Wahrscheinlichkeit groß, dass mal ein IRQ "dazwischenfunkt".
@Markus: Die Flanken sind gleich, das habe ich vorher mit dem Oszi überprüft. Trotzdem Danke für's Mitdenken. Mein Plan war dann eh das ganze noch über ein paar Werte zu glätten, aber solange es grundsätzlich nicht funktioniert nützt auch das ganze Glätten nix...
Ich werde den Code ändern und dann ausprobieren. Ist nicht immer ganz Einfach das Fahrrad an den Basteltisch zu bekommen. Und ohne Tacho-Signal vom Fahrrad testet sich das ganze immer so schlecht...
Ich werde berichten. Danke erst mal!
Viele Grüße
Andreas
Ich konnte es nicht abwarten und habe es gleich probiert. Leider ohne Erfolg. Es muss noch ein Fehler drin sein. Ich poste deshalb mal etwas mehr von meinem Code, vielleicht ist es ja noch eine Kleinigkeit:
#define AVRGCC
#include <avr/io.h>
#include <compiler.h>
#include <util/delay.h>
/**
IOs:
PD2 (INT0) Tacho Eingang
**/
volatile U32 TachoTime = 0;
volatile U16 TachoTimeCounterHigh = 0;
U32 SpeedBuffer[SpeedBufferSize];
U8 SpeedBufferPointer;
U32 CurrentSpeed = 0;
int main(void)
{
DDRB = 0b11111100; // Das LCD hängt an PB7...PB5
PORTB = 0b00000000;
DDRD = 0b00000000;
PORTD = 0b01000100;
LCD_Init ();
// IRQ 0 für Tacho-Signal-Eingang:
MCUCR = 0b00000001;
GIMSK = 0b01000000;
// Timer 1: Takt für Tacho-Input-Counter:
TCCR1B |= (1<<CS11); // Prescaler auf 8:
TIMSK |= (1<<TOIE1); // IRQ für Overflow
/**
for (SpeedBufferPointer = 1; SpeedBufferPointer < SpeedBufferSize; SpeedBufferPointer++)
SpeedBuffer[SpeedBufferPointer] = 0;
SpeedBufferPointer = 1;
**/
sei ();
while (1)
{
// Geschwindigkeitsberechnung:
// TachoTime:
// 1 Sekunde entspricht FCPU / Prescaler = 8000000 / 8 = 1000000 entspricht 1µs Auflösung
// schnellste Geschwindigkeit: 100km/h entspricht 2,988ms Mindest-Auflösung
// 1 km/h entspricht 298,8 ms (ganze Periode): bzw Zeit zwischen Flankenwechsel: 149,4 ms
// --> Geschwindigkeit = 149400 µs / Flankenzeit
// --> Geschwindigkeit in Zehntel-km/h = 1494000 µs / Flankenzeit
// langsamste Geschwindigkeit: 0,1 km/h entspricht 14940000 µS
if ((TachoTime > 0) && (TachoTime < 15000000))
CurrentSpeed = 1494000 / TachoTime;
else
CurrentSpeed = 0;
/**
SpeedBuffer[SpeedBufferPointer] = CurrentSpeed;
if (SpeedBufferPointer < SpeedBufferSize)
SpeedBufferPointer++;
else
SpeedBufferPointer = 1;
CurrentSpeed = 0;
U8 i1;
for (i1 = 1; i1 < SpeedBufferSize; i1++)
CurrentSpeed = CurrentSpeed + SpeedBuffer[i1];
CurrentSpeed = CurrentSpeed / SpeedBufferSize;
**/
LCD_Cursor (1, 1);
LCD_Zahl (CurrentSpeed);
}
return(0);
}
// Tacho-Eingang Signalwechsel:
SIGNAL (INT0_vect)
{
TachoTime = TachoTimeCounterHigh;
TachoTime = TachoTime<<8;
U8 tmp = TCNT1L;
TachoTime = TachoTime + TCNT1H;
TachoTime = TachoTime<<8;
TachoTime = TachoTime + tmp;
TCNT1L = 0;
TCNT1H = 0;
TachoTimeCounterHigh = 0;
}
// Tacho-Eingang-Zeitmessung:
SIGNAL (SIG_TIMER1_OVF)
{
if (TachoTimeCounterHigh < 0xFFFF)
TachoTimeCounterHigh++;
}
Das Tacho-Signal springt immer noch wild durcheinander. Selbst mit Glättung.
Hüüüülfe! Das sollte doch eigentlich ganz easy sein!
Viele Grüße
Andreas
Hallo Andreas,
Mal vorne angefangen. Arrays in C werden von 0 bis Größe-1 indiziert, du verwendest 1-Größe (und zerballerst damit 4 Bytes nach deinem Array, in denen andere Variablen liegen). Dann fehlt dir eine Synchronisation zwischen Hauptschleife und ISR. Volatile garantiert dir nicht, dass im Hauptprogramm TachoTime atomar ausgelesen wird!
Vorschläge:
1. Arrayzugriffe korrigieren
2. Die Neuberechnung der TachoTime kannst du ruhig auch in der Hauptschleife machen, wenn du den Code in der ISR halten möchtest, solltest du die Zwischenergebnisse in einer lokalen Variable halten und erst am Ende nach TachoTime schreiben. Grund: Volatile sagt dem Compiler, dass er IMMER aus der im SRAM liegenden Variable lesen bzw. JEDE Änderung direkt dorthin zurückschreiben muss. Es würde also jede deiner Zuweisungen einmal in den SRAM geschrieben, obwohl es wesentlich effektiver wäre, die Zwischenergebnisse in den Registern zu nutzen.
3. Locking: Lies in der Hauptschleife TachoTime (oder die Rohdaten falls du die Berechnung aus der ISR rausnimmst) in lokale Puffervariablen und schalte während des Lesevorganges Interrupts aus. Um das zu vereinfachen gibt es in util/atomic.h den ATOMIC_BLOCK, es sollte aber auch reichen, den Kopiervorgang in die lokale Variable mit cli() bzw. sei() zu umgeben.
mfG
Markus
EDIT: Zu 1: Informatiker am Bahnhof - 0, 1, 2 ... wo ist mein dritter Koffer?
Außerdem wird das Zurücksetzen des Timers nicht richtig funktionieren, weil falsche Reihenfolge der Zugriffe. Warum überhaupt einzelne Zugriffe auf Low- und High-Byte? Warum nicht einfach:
TachoTime = TachoTimeCounterHigh;
TachoTime = TachoTime<<16;
TachoTime = TachoTime + TCNT1;
TCNT1 = 0;
TachoTimeCounterHigh = 0;
Besser und genauer wäre es natürlich, das Input-Capturing des Timers zu benutzen.
Powered by vBulletin® Version 4.2.5 Copyright ©2024 Adduco Digital e.K. und vBulletin Solutions, Inc. Alle Rechte vorbehalten.