Forum: Mikrocontroller und Digitale Elektronik Zeitmessung zwischen zwei steigenden Flanken


von Pascal S. (pascal900198)


Angehängte Dateien:

Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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)

von Pascal S. (pascal900198)


Lesenswert?

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.

von Stefan F. (sfrings)


Lesenswert?

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.

von Pascal S. (pascal900198)


Lesenswert?

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
}

von Karl H. (kbuchegg)


Lesenswert?

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?

von Pascal S. (pascal900198)


Angehängte Dateien:

Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

> 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.

von Pascal S. (pascal900198)


Angehängte Dateien:

Lesenswert?

Hups, das war zu Testzwecken...
2. PB2 ist an ICP angeschlossen...
Also immer noch das gleiche Problem.

von Karl H. (kbuchegg)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

Sehen denn die danach in main() ausgegebenen Werte plausibel aus?

von Pascal S. (pascal900198)


Lesenswert?

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?

von Karl H. (kbuchegg)


Lesenswert?

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.

von Norbert (Gast)


Lesenswert?

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