Forum: Mikrocontroller und Digitale Elektronik Hilfe bei Zeitmessung mit Interrupt


von Hermann G. (df2ds)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,
ich komme mit meiner Software zur Zeitmessung einfach nicht weiter...

Der Plan:
Eine Lichtschranke wird periodisch unterbrochen, die Zeit zwischen N 
Unterbrechungen soll genauer als 1ms bestimmt werden.

Die Umsetzung:
Hardware: Arduino Uno.

Jede Unterbrechung der Lichtschranke löst einen Timer1-Capture-Interrupt 
aus. Der 1. Interrupt enabled den T1-Overflow-Interrupt, da die Zeiten 
zwischen den Impulsen einige Sekunden lang sein können. Der letzte 
T1-Capture-Interrupt disabled den T1-Overflow und den 
T1-Capture-Interrupt. Aus den beiden Captures und den Overflows wird die 
Zeit berechnet.

Hilfskonstruktion:
Mit Timer2 wird ein periodisches Signal erzeugt, welches auf den 
Capture-Eingang gegeben wird..
--> Funktioniert, habe ich mit dem Oszi nachgeprüft.

Das Problem:
a) Ich bekomme keine T1-Overflow-Interrupts, auch die Captures sind 
meist Null.
b) Nach einem Durchlauf bleibt das Programm hängen!

Bin für jeden zielführenden Tipp dankbar!

Gruß
  Hermann

von Hermann G. (df2ds)


Lesenswert?

Hallo nochmal,
hab ich in meiner Frage etwas vergessen zu erwähnen, oder hat wirklich 
niemand einen Hinweis für mich???

Viele Grüße
  Hermann

von Norbert (Gast)


Lesenswert?

Hermann G. schrieb:
> Hallo nochmal,
> hab ich in meiner Frage etwas vergessen zu erwähnen, oder hat wirklich
> niemand einen Hinweis für mich???
>
> Viele Grüße
>   Hermann

Variablen, welche in einer Interrupt-Routine verändert und an anderer 
Stelle gelesen werden, sollten besser volatile deklariert werden.

Wäre mal so der erste Versuch.

von Karl M. (Gast)


Lesenswert?

Ich glaube man kann es komplizierter nicht machen!

Male Dir mal ein Zeitdiagramm auf.

Dann ist eine fallende Flanke am INTx für den Start eines Timers, z.B. 
Timer1 zuständig, den man per Software beliebig auf 32 Bit oder 64 Bit 
erweitern kann.

Die nächste steigende Flanke am INTx stopp den Timer und man setzt ein 
Flag "Messung beendet" auf true.

Im der Mainloop wird dann das Ergebnis ausgewertet und verrechnet.

In der Interrupt Serviceroutine wird natürlich auch der INTx um 
konfiguriert  - Flanke high-low und umgekehrt - man achtet auf das 
Zeitverhalten der Routine.

von Manuel W. (multisync)


Lesenswert?

Hallo!

Ich habe mir den Code nicht angesehen, hoffe aber trotzdem, dass dir 
dieser Hinweis hilft: Konzentriere deinen Blick auf's Wesentliche, 
reduziere den Code, und lass dich nicht ablenken vom Drumherum.

Aktuell ist dein Problem, dass ein Timer-Overflow keinen Interrupt 
auslöst.

Lösche (oder kommentiere) allen Code, der damit nichts zu tun hat. Das 
alles lenkt jetzt nur ab. Wenn das dann hinhaut, kannst du den Programm 
Schritt um Schritt ergänzen. Prüfe nach jedem Schritt, dass wirklich 
noch alles tut.

Ich frage mich, warum dieses Problem jetzt erst auftaucht, wo dein 
Programm schon quasi fertig zu sein scheint. Müsste dieses Problem nicht 
schon von Beginn an existieren? Das kommt mir ein bisschen vor, als 
würde man, während man am Dachgeschoss arbeitet draufkommen, dass die 
Wasserleitung im Keller schon seit Beginn an leckt.

von Wolfgang (Gast)


Lesenswert?

Karl M. schrieb:
> Die nächste steigende Flanke am INTx stopp den Timer und man setzt ein
> Flag "Messung beendet" auf true.

Warum den Timer stoppen? Es reicht doch, per Capture auf der Flanke den 
Zählerstand in ein Register zu übernehmen. In der Capture ISR kann man 
dann in Ruhe das Register lesen und in eine Variable übernehmen. Der 
Wert ist gleichzeitig das Ende der abgelaufenen Taktperiode und der 
Anfang der nächsten. Die Differenz zweier solcher Capture Werte, 
erweitert um volle Timer-Durchläufe, die einer Overflow ISR mitgezählt 
werden können, liefert die Zeitdifferenz zwischen den Flanken. Die 
Zeitauflösung entspricht der Taktfrequenz des Timers, kann also locker 
im µs-Bereich liegen.

von Karl M. (Gast)


Lesenswert?

Wolfgang schrieb:
> Die
> Zeitauflösung entspricht der Taktfrequenz des Timers, kann also locker
> im µs-Bereich liegen.

Der Plan:
Eine Lichtschranke wird periodisch unterbrochen, die Zeit zwischen N
Unterbrechungen soll genauer als 1ms bestimmt werden.

von asdf (Gast)


Lesenswert?

Karl M. schrieb:
> Wolfgang schrieb:
>> Die
>> Zeitauflösung entspricht der Taktfrequenz des Timers, kann also locker
>> im µs-Bereich liegen.
>
> Der Plan:
> Eine Lichtschranke wird periodisch unterbrochen, die Zeit zwischen N
> Unterbrechungen soll genauer als 1ms bestimmt werden.

Ist eine µs-Auflösung denn nicht genauer als eine ms?

von Wolfgang (Gast)


Lesenswert?

Karl M. schrieb:
> Eine Lichtschranke wird periodisch unterbrochen, die Zeit zwischen N
> Unterbrechungen soll genauer als 1ms bestimmt werden.

Was willst du damit sagen?

"genauer als 1ms" kriegt man doch fast noch mit der Handstopuhr zu 
fassen. Zusammen mit einem µC ist das eher keine ernste Anforderung, 
wenn die Lichtschranke nicht zu sehr unter Schwierigkeiten mit 
Umweltstörungen zu kämpfen hat.

von Mein grosses V. (vorbild)


Lesenswert?

Hermann G. schrieb:
> Bin für jeden zielführenden Tipp dankbar!
1
volatile unsigned long nOverflow = 0;
2
volatile unsigned long nTime = 0;
3
volatile unsigned char bReady = 0;
4
5
ISR(TIMER1_CAPT_vect)
6
{
7
  static unsigned long last = 0;
8
  unsigned long icr = ICR1 + nOverFlow;
9
  nTime = icr -last;
10
  last = icr;
11
  bReady = 1;
12
}
13
14
ISR(TIMER1_OVF_vect)
15
{
16
  nOverFlow += 65536;
17
}
18
19
int main(void)
20
{
21
.
22
.
23
.
24
25
 if(bReady)
26
 {
27
  bReady = 0;
28
  /* Tu, was zu tun ist*/
29
 }
30
}

Kein INTx-Blödsinn, kein Timer-Interrupt-Geschalte-Unsinn.

: Bearbeitet durch User
von Lurchi (Gast)


Lesenswert?

Das hat zwar noch nichts mit der groben Funktion zu tun, aber noch ein 
Hinweiss, wenn es zuverlässig werden soll:

Bei der Nutzung von Überläufen zur erweiterung der Auflösung auf mehr 
als 16 Bit muss man etwas aufpassen, wenn ICP event und Timer Überlauf 
zusammenfallen. Da passt dann die Zählung der Überläufe ggf. nicht zum 
ICP1 Stand. Meistens geht es gut aber etwa eine von 10000 Messungen wird 
deutlich (1 Überlauf) daneben liegen, wenn man nicht aufpasst. Das 
Problem lässt sich aber lösen (siehe z.B. hier: 
http://rn-wissen.de/wiki/index.php?title=Timer/Counter_%28Avr%29#Input_Capture).

Für einfach nur besser als 1 ms Auflösung könnte man ggf. einfach den 
Timer langsam genug laufen lassen, dass man mit der 16 Bit Auflösung des 
Timers auskommt.

Der geziegte Code ist einfach recht lang / unübersichltich. Das hat 
findet sich nicht unbedingt einer zur Fehlersuche. Je weiter der Code 
auf das wesentliche reduziert ist, desto eher wird man Hilfe finden.

von Wolfgang H. (Firma: AknF) (wolfgang_horn)


Lesenswert?

Danke, Lurchi,


> ... wenn ICP event und Timer Überlauf zusammenfallen.
Das Risiko hatte ich bisher noch nicht im Blickfeld.
Gut, dass Du mich jetzt schon drauf gebracht hast, bevor wegen eines 
extrem seltenen Fehlers lange rätseln muss.

Ciao
Wolfgang Horn

von Falk B. (falk)


Lesenswert?

Hier mal die lesbare Version des Bugfixes. Doch Vorsicht! Die Abfrage 
auf <128 ist nicht ganz korrekt! Den im Extremfall kann ein anderer, 
hochpriorer Interrupt die Ausführung sowohl von ICP1 als auch TOV1 für 
mehr als 128 Timertakte verhindern (auch vor dem Timerüberlauf!), vor 
allem bei hohen Timerfrequenzen (kleiner Vorteiler)! Dann stimmt der 
Vergleich NICHT! Mit der Abfrage auf ICP1<32000 hat man nahezu die halbe 
Timerperiode Zeit, so einen Fehler auszugleichen. Länger darf der TOV1 
Interrupt nicht blockiert werden. Wenn doch, knallt es und man mißt 
Fahrkarten.
Hier ist auch eine schöne Anwendung von Short Circuit Evaluation in C. 
Wenn der 1. Vergleich (TIFR1 & (1<<TOV1)) false ist, was er zu 99,99% 
immer sein wird, wird der 2. "aufwändige" Vergleich nicht ausgeführt.
1
ISR(TIMER1_CAPT_vect) {
2
  static unsigned long last = 0;
3
  unsigned long icr;
4
5
  // check race condition of timer overflow
6
  if ((TIFR1 & (1<<TOV1)) && (ICR1 < 32000)) {
7
    nOverFlow++;
8
    TIFR1 = (1<<TOV1); // clear flag, increment already done
9
  }
10
11
  icr = ICR1 + nOverFlow;
12
  nTime = icr - last;
13
  last = icr;
14
  bReady = 1;
15
}

: Bearbeitet durch User
von Lurchi (Gast)


Lesenswert?

Im oben verlinkten Code bezieht sich der Vergleich mit 128 auf das ober 
Byte von ICR1, macht also ein ICR1 Wert von 32768. Das ist kein 
wesentlicher Unterschien zum Vergleich mit 32000.

von Falk B. (falk)


Lesenswert?

Uuups, übersehen ;-)

von Hermann G. (df2ds)


Lesenswert?

Danke für die vielen Tipps. Ich werde das morgen versuchen umzusetzen 
und werde dann berichten.

Danke nochmals, bis morgen
  Hermann

von Sebastian S. (amateur)


Lesenswert?

Ich dachte immer, dass es im Arduino-System, bereits einen Timer gibt.
Den braucht man doch nur bei jeder Unterbrechung auslesen, und 
anschließend feststellen, wann das letzte Mal was los war.
Das geht natürlich erst ab dem n+1-ten Mal.
Natürlich sollte der Hintergrund nicht total zugemüllt, mit 
irgendwelchen Unterbrechungen, sein.

von M. K. (sylaina)


Lesenswert?

Mein grosses V. schrieb:
> Kein INTx-Blödsinn, kein Timer-Interrupt-Geschalte-Unsinn.

intx ist kein Blödsinn sondern schon eine sehr sinnvolle Sache, einfach 
mal damit beschäftigen ;)

von Hermann G. (df2ds)


Lesenswert?

So, es geht.

Vielen Dank an alle, die konstruktive Vorschläge gepostet haben. Ich 
habe die Interrupt-Struktur überarbeitet und so weit wie möglich 
vereinfacht und etliche der Hinweise berücksichtigt.

@Falk: Danke für den Tipp, den Overflow-Interrupt ggf. im 
Capture-Interupt abzufangen. ("Race"). Das hat dem Prohramm den letzten 
Schliff gegeben.

Grüße an alle Mitleser
  Hermann

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.