Hallo! Ich versuche gerade die Zeit zwischen zwei steigenden Flanken zu messen. Mein uC ist ein ATmega8 der mit 16MHz läuft. Hierfür verwende ich den INT0. Löst der Interrupt aus, wird ein Timer gestartet. Löst er ein zweites Mal aus, wird der Timer gestoppt. Danach gebe ich den Zählerstand/2 auf einem LCD aus. Die beiden Interrupts werden noch zum Testen softwareseitig ausgelöst. Also setze ich den Pin einmal auf high, dann low und wieder high. Da habe ich die beiden steigenden Flanken. Dazwischen hab ich mal unterschiedlich lange delays gemacht. Immer bin ich 5us über dem richtigen Wert. Auch wenn kein delay dazwischen ist, zeigt er mir 5us an. Was dauert hier so lange? Etwa das Starten und Beenden des Timers? Oder vielleicht der Aufruf der ISR? Gruß Pascal
Das Reingehen in die ISR kostet etwas Zeit, genauso wie das Rausgehen. Je nachdem sind da ein paar CPU-Register zu sichern und wieder herzustellen. Deshalb verwendet man für solche Sachen, wenn es auf den Takt genau sein soll, auch nicht den externen Interrupt sondern den Input Capture Interrupt (in deinem Fall den vom Timer 1)
Okay, danke für die Antwort. Ich dachte ich könnte den Timer 1 noch aufsparen. Aber dann werde ich es wohl mal so probieren.
Zum einen verleist Du Zeit durch das Starten und Stoppen des Timers. Zweitens dauert es eine Weile, bis die Interrupt-Service Routine angesprungen wird. Es ist besser, den Timer ständig laufen zu lassen und beim Interrupt den aktuellen Zählerstand zu erfassen und dann die Differenz der beiden Werte berechnen. Dabei ist es wichtig, das vom Auslösen der Interrupt-Routine bis zum Erfassen des Zählerstandes immer die gleiche Zeit verstreicht, sowei man das unter Kontrolle hat. volatile uint8_t counter1; volatile uint8_t counter2; ISR(INT0_vect) { counter1=counter2; counter2=TCNT0; } int main(void) { ... while (1) { uint8_t diff=counter2-counter1; // diff anzeigen } } In diesem Fall wird die DIfferenz zwischen zwei Impulsen fortlaufend gemessen. Wenn der Counter zwischen zwei Messungen über läuft, ist das Ergebnis (diff) trotzdem richtig, sofern die Variablem vom gleichen Typ sind, wie das Counter Register (also hier 8 bit unsigned). Vermeide es, in Interrupt-Routinen irgendweile langwierigen Ein/Ausgaben, Zahlen-zu-String Umwandlungen und Punkt-Rechnungen (Multiplikation, Division) durchzuführen. Denn das dauert unter Umständen einige hundert Mikrosekunden. Andere folgende Interrupts verzögern sich entsprechend. Solange Du nur eine Messung machst, fällt das noch nicht auf. Aber das Programm wird sich noch umfangreicher werden, nicht wahr? Dieses Prinzip funktioniert auch mit dem Input Capture Interrupt von Timer 1. Dann musst Du 16 Bit Variablen verwenden und das Input Capture Register in der ISR auslesen. Der Vorteil ist, dass das ICR genau den Zählerstand vom Zeitpunkt des Signals enthält, auch wenn die ISR etwas verzögert ausgeführt wird.
So, habe das jetzt mal mit der ICU probiert. Leider funktioniert es noch nicht ganz. Anscheinend funktioniert die Zuweisung der Differenz nicht. Weiter unten im Programm gebe ich die Differenz aus. Dort gibt es keine Probleme mit der Berechnung. Sie ist korrekt, deswegen denke ich, dass es eher ein Problem der Zuweisung ist. In Zeile 6 tritt das Problem auf. Am Ende ist period immer noch 0. Was ist hier das Problem?
1 | volatile uint16_t firstStamp; |
2 | volatile uint16_t period; |
3 | |
4 | ISR(TIMER1_CAPT_vect){ |
5 | if(firstStamp){ |
6 | period = ICR1 - firstStamp; |
7 | }else{ |
8 | firstStamp = ICR1; |
9 | }
|
10 | }
|
Pascal S. schrieb: > So, habe das jetzt mal mit der ICU probiert. Leider funktioniert es noch > nicht ganz. Anscheinend funktioniert die Zuweisung der Differenz nicht. > Weiter unten im Programm gebe ich die Differenz aus. Dort gibt es keine > Probleme mit der Berechnung. Sie ist korrekt Das sagen sie alle. > deswegen denke ich, dass > es eher ein Problem der Zuweisung ist. In Zeile 6 tritt das Problem auf. Eher nicht. Dein Problem liegt an einer Stelle im Code, die du nicht gezeigt hast. > Am Ende ist period immer noch 0. Was ist hier das Problem? Woher weißt du in der Hauptschleife, dass eine Messung beendet ist? Mit welchem Prescaler taktet der Timer?
Okay, ich hätte gleich den ganzen Code posten sollen. Sorry dafür. Ich generiere nur zwei steigende Flanken. Deswegen weiß ich wann die Messung fertig ist. Der ICP ist mit PD2 verbunden.
> period &= ICR1 - firstStamp;
Da steht aber ein &= und kein =
Das da 0 rauskommt, ist jetzt nicht wirklich verwunderlich :-)
Und es zeigt wieder mal:
Poste IMMER deinen richtigen Source Code. Halte Abstand von der Technik
extra fürs Forum mal kurz was einzutippen. Manchmal sind es wirklich nur
solche Kleinigkeiten, die im Originalcode vorhanden sind und in der
Forumsversion nicht.
Hups, das war zu Testzwecken... 2. PB2 ist an ICP angeschlossen... Also immer noch das gleiche Problem.
1 | PORTB |= (1<<PB2); |
2 | _delay_us(50); |
3 | PORTB &= ~(1<<PB2); |
4 | _delay_us(50); |
5 | PORTB |= (1<<PB2); |
6 | |
7 | lcd_printString(lcd_convertIntToDecString(period,1)); |
da würde ich sicherheitshalber nach dem letzten Port setzen noch eine kleine Verzögerung reinbauen, damit mir der Interrupt nicht reinknallt, während gerade das auslesen von period begonnen hat.
Sehen denn die danach in main() ausgegebenen Werte plausibel aus?
Und siehe da: Es funktioniert! Danke! Ja wahrscheinlich knallt der Interrupt gerade dann rein, wenn er auf period zugreift. Und da period ja eine 16 Bit Variable ist, ist der Zugriff nicht atomar. Also geht da etwas schief. Man sollte bei solchen Zugriffen also Interrupts lieber deaktiviern. Ist der Grund für den Fehler so richtig beschrieben?
Pascal S. schrieb: > Und siehe da: Es funktioniert! Danke! Ja wahrscheinlich knallt der > Interrupt gerade dann rein, wenn er auf period zugreift. Nach dem Setzen des Port Pins auf 1 vergeht noch ein bischen Zeit, bis dann am ICP-Eingang die Veränderung ankommt (paar Takte). Drumm muss man auch einen NOP einlegen, wenn man auf einen Portpin ausgibt und denselben Pin gleich danach abfragen will. Ich schätze mal, dass hier so ein ähnliches Problem vorlag. Wenn sich der Compiler die Adresse von period irgendwo in einem Register vorgehalten hatte, kann er ja mehr oder weniger sofort mit dem Auslesen der Variable nach dem Pinsetzen anfangen und wenn das zu früh war, dann war die ISR noch gar nicht drann. Kann sich aber nur um ein paar Takte gerissen haben. Man müsste da jetzt den Assembler Output untersuchen, wenn man es ganz genau wissen will. Da das aber nur ein Testprogramm ist und dein endgültiges Pgm mit Sicherheit ganz anders aussieht, kann man das Problem aber auch zu den Akten legen. > Und da period > ja eine 16 Bit Variable ist, ist der Zugriff nicht atomar. Also geht da > etwas schief. Man sollte bei solchen Zugriffen also Interrupts lieber > deaktiviern. das sowieso.
> Und da period > ja eine 16 Bit Variable ist, ist der Zugriff nicht atomar. Also geht da > etwas schief. Man sollte bei solchen Zugriffen also Interrupts lieber > deaktiviern. So isses...
1 | #include <util/atomic.h> |
2 | |
3 | volatile uint16_t period; |
4 | uint16_t safeperiod; |
5 | |
6 | main() |
7 | {
|
8 | ...
|
9 | ...
|
10 | ...
|
11 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) |
12 | {
|
13 | safeperiod = period; |
14 | }
|
15 | |
16 | lcd_printString(lcd_convertIntToDecString(safeperiod,1)); |
17 | }
|
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.