PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Geschwindigkeit eines Programms (Interrupts, Timer)



Björn
23.08.2008, 10:18
Hallo!

Ich habe ein Programm welches das Signal zweier Lichtschranken auswertet. Dies geschieht über Interrupts (fallende Flanke). µC ist ein ATmega8 mit externem 16 MHz Quarz.
Wenn der erste Interrupt (erste Schranke) ausgelöst wird, wird ein Timer gestartet, der eine Variable hochzählt. Wird der zweite Interrupt ausgelöst wird der Timer gestoppt und die Zählvariable(n) in eine Zeit und anschließend eine Geschwindigkeit umgerechnet und auf dem LCD ausgegeben.
Meine Frage ist jetzt folgende: Wie "klein" kann ich den Timer konfigurieren, also wie klein sollte seine Frequenz sein?
Denn letztlich möchte ich wissen, welche Messstrecke ich für eine bestimmte Genauigkeit benötige (Genauigkeit: +- 0,1km/h - Geschwindigkeit: max. 100km/h). Dafür müsste der Timer natürlich möglichst oft ausgeführt werden.

Grüße, Björn

Björn
24.08.2008, 20:21
Hier mal das ganze Programm:

Man sieht, der Timer ist auf 1MHz eingestellt, also Messeinheit 1µs. Schafft der µC das?



'################################################# ####
$regfile = "m8def.dat" 'ATmega8 mit externem 16 MHz Quarz
$crystal = 16000000
$baud = 9600

'############ Variablen ##############################
Dim Mikrosekunden As Long
Dim Sekunden As Single
Dim Geschwindigkeit As Single
Dim Strecke As Single
Dim Ausgabe As String * 5
Strecke = 0.1 'Länge der Messstrecke in Metern

'############ Interrupts & Timer konfigurieren #######
Config Int0 = Falling 'Beide Male bei fallender Flanke reagieren
Config Int1 = Falling
Enable Interrupts
Enable Int0 'Die Erste Schranke aktivieren, die zweite noch sperren
On Int0 Isr_schranke1
On Int1 Isr_schranke2

Config Timer1 = Timer , Prescale = 1
On Timer1 Timer_irq
Const Timervorgabe = 65520

'############ Ein- und Ausgänge ######################
Config Portd.6 = Output
Config Portd.7 = Output
Config Portb.0 = Output
Portd.2 = 1 'Interne Pull-Up Widerstände aktivieren
Portd.3 = 1

'############ LCD Display ############################
Config Lcdpin = Pin , Db4 = Portc.3 , Db5 = Portc.2 , Db6 = Portc.1 , _
Db7 = Portc.0 , E = Portc.4 , Rs = Portc.5
Config Lcd = 16 * 1

Cls
Cursor Off Noblink

'############ Bezeichnungen ##########################
Schranke1 Alias Pind.2
Schranke2 Alias Pind.3
Led1 Alias Portd.6
Led2 Alias Portd.7
Lcdlicht Alias Portb.0

'############ Hauptschleife ##########################
Do
If Schranke1 = 0 Then Led1 = 0 Else Led1 = 1
If Schranke2 = 0 Then Led2 = 0 Else Led2 = 1
Loop

End

'############ Interrupt-Routinen #####################
Isr_schranke1:
Disable Int1
Enable Timer1
Return

Isr_schranke2:
Disable Timer1
Sekunden = Mikrosekunden / 1000000
Geschwindigkeit = Strecke / Sekunden
Geschwindigkeit = Geschwindigkeit * 3.6
Ausgabe = Fusing(geschwindigkeit , "#.#")

Cls
Lcd Ausgabe

Mikrosekunden = 0
Enable Int0
Return

Timer_irq:
Timer1 = Timervorgabe
Incr Mikrosekunden
Return


Grüße, Björn

Sauerbruch
24.08.2008, 23:29
Mit dem Timer-Reload von 65520 erreichst Du zwar, dass der Zähler 16 Takte nach seinem Start überläuft. Man muss aber bedenken, dass so ein Interrupt auch eine erhebliche Anzahl an Takten "verbraucht", bis es in die eigentliche ISR geht (es müssen allerlei Registerinhalte gerettet werden etc.). Diese Zyklen kommen also noch dazu, und wenn ich ihren Wert auch nicht genau weiss, liegen sie aber in einer Größenordnung, die die angepeilte Mikrosekunde erheblich verfälschen würden.
Brauchst Du die Zeit denn tatsächlich in Mikrosekunden?

Björn
25.08.2008, 07:23
Mit dem Timer-Reload von 65520 erreichst Du zwar, dass der Zähler 16 Takte nach seinem Start überläuft. Man muss aber bedenken, dass so ein Interrupt auch eine erhebliche Anzahl an Takten "verbraucht", bis es in die eigentliche ISR geht (es müssen allerlei Registerinhalte gerettet werden etc.). Diese Zyklen kommen also noch dazu, und wenn ich ihren Wert auch nicht genau weiss, liegen sie aber in einer Größenordnung, die die angepeilte Mikrosekunde erheblich verfälschen würden.
Brauchst Du die Zeit denn tatsächlich in Mikrosekunden?

Nein, nicht unbedingt. wären 100µs, also 0,1ms Schritte möglich? Der Timer also mit einer Frequenz von 10Khz?

Grüße, Björn

Sauerbruch
25.08.2008, 08:12
wären 100µs, also 0,1ms Schritte möglich? Der Timer also mit einer Frequenz von 10Khz?

Klar, im Prinzip schon: 0,1ms entspricht bei einer Taktfrequenz von 16MHz genau 1600 Zyklen. Der Timer-Preset müsste also mit etwa (!) 65535-1600, also 63935 gewählt werden.
Das Problem mit den Zyklen, die bis zum Sprung in die ISR vergehen, hast Du natürlich auch hier, nur fallen bei 1600 Taktzyklen 10-15 nicht ganz so ins Gewicht. Unschön ist´s aber irgndwie trotzdem, aber es gäbe zwei Alternativen:

Probier mal im Simulator aus, wieviele Taktzyklen bei einem Timer-Überlauf-Interrupt vergehen - die müsstest Du dann von den 1600 abziehen. Mit diesem Timer-Preset hättest Du dann genau 100µs.

Oder ganz anders: Mit ISR_schranke1 startest Du den Timer, bei 16MHz und einem Prescaler von 1024 zählt der Timer alle 64µs um eins weiter. In der ISR_schranke2 stoppst Du ihn, das Ergebnis noch mit 1000 multiplizieren und anschließend durch 64 teilen - fertig ist die Zeit in ms.

Die zweite Methode hat als Einschränkung, dass der Timer nach etwas über 4 Sekunden überläuft - die Zeit zwischen den beiden Lichtschranken-Passagen darf also nicht länger sein. Bei 100km/h entsprechen 4 Sekunden ja aber schon ener ganz ordentlichen Strecke... 8-[

Dann fahrt mal immer schön vorsichtig O:) (*lach...),

Daniel

Björn
25.08.2008, 14:23
wären 100µs, also 0,1ms Schritte möglich? Der Timer also mit einer Frequenz von 10Khz?

Klar, im Prinzip schon: 0,1ms entspricht bei einer Taktfrequenz von 16MHz genau 1600 Zyklen. Der Timer-Preset müsste also mit etwa (!) 65535-1600, also 63635 gewählt werden.
Das Problem mit den Zyklen, die bis zum Sprung in die ISR vergehen, hast Du natürlich auch hier, nur fallen bei 1600 Taktzyklen 10-15 nicht ganz so ins Gewicht. Unschön ist´s aber irgndwie trotzdem, aber es gäbe zwei Alternativen:

Probier mal im Simulator aus, wieviele Taktzyklen bei einem Timer-Überlauf-Interrupt vergehen - die müsstest Du dann von den 1600 abziehen. Mit diesem Timer-Preset hättest Du dann genau 100µs.

Oder ganz anders: Mit ISR_schranke1 startest Du den Timer, bei 16MHz und einem Prescaler von 1024 zählt der Timer alle 64µs um eins weiter. In der ISR_schranke2 stoppst Du ihn, das Ergebnis noch mit 1000 multiplizieren und anschließend durch 64 teilen - fertig ist die Zeit in ms.

Die zweite Methode hat als Einschränkung, dass der Timer nach etwas über 4 Sekunden überläuft - die Zeit zwischen den beiden Lichtschranken-Passagen darf also nicht länger sein. Bei 100km/h entsprechen 4 Sekunden ja aber schon ener ganz ordentlichen Strecke... 8-[

Dann fahrt mal immer schön vorsichtig O:) (*lach...),

Daniel

Hallo Daniel!

Vielen Dank für die Tipps! Und die zweite Variante wäre auch genau? Dann würde ich die nehmen. Die Durchfahrtszeit sind liegt ja im Sekundenbruchteil-Bereich, somit wäre das Timer-Überlaufen kein Problem.
Oder spricht sonst etwas gegen die zweite Variante :D ?

Grüße, Björn

//Edit: Welchen Timervorgabe-Wert muss ich denn dann nehmen?

Sauerbruch
25.08.2008, 18:03
Die 2. Variante ist IMHO definitiv genau. Bei beiden Interrupts entsteht die gleiche (minimalste) Zeitverzögerung zwischen Auslösen des Interrupts und Einspringen in die ISR – die Zeit, die der Timer läuft, entspricht also exakt der Zeit zwischen den beiden Lichtschranken-Passagen.

Ich habe aber natürlich trotzdem einen Denkfehler gemacht: Der Timer zählt (bei 16 MHz und einem Prescaler von 1024) alle 64µs ein Bit weiter – man muss den Zählerstand also mit 64 malnehmen, um auf die Zeit zu kommen, und ihn nicht durch 64 teilen. Dieses Produkt dann durch 1000 geteilt ergibt die Zeit in ms.

Folglich muss der Zähler immer bei 0 starten – hat er z.B. bis 7628 gezählt, sind 488 ms vergangen. Nach dem Stoppen in der ISR_lichtschranke2 übergibst Du seinen Wert an eine Variable und setzt ihn auf 0 zurück – für den nächsten Start (die Variable muss dabei ausreichend groß dimensioniert sein, um sich noch mit 64 multiplizieren zu lassen!). Außerdem wäre es elegant, in dieser Stop-ISR ein Flag-Bit zu setzen, das der Hauptschleife anzeigt, dass jetzt die Zeit berechnet werden kann und soll. Damit bleibt die ISR schön kurz und hält das Hauptprogramm nicht länger auf als nötig.


Alles klar??

Björn
25.08.2008, 18:13
Die 2. Variante ist IMHO definitiv genau. Bei beiden Interrupts entsteht die gleiche (minimalste) Zeitverzögerung zwischen Auslösen des Interrupts und Einspringen in die ISR – die Zeit, die der Timer läuft, entspricht also exakt der Zeit zwischen den beiden Lichtschranken-Passagen.

Ich habe aber natürlich trotzdem einen Denkfehler gemacht: Der Timer zählt (bei 16 MHz und einem Prescaler von 1024) alle 64µs ein Bit weiter – man muss den Zählerstand also mit 64 malnehmen, um auf die Zeit zu kommen, und ihn nicht durch 64 teilen. Dieses Produkt dann durch 1000 geteilt ergibt die Zeit in ms.

Folglich muss der Zähler immer bei 0 starten – hat er z.B. bis 7628 gezählt, sind 488 ms vergangen. Nach dem Stoppen in der ISR_lichtschranke2 übergibst Du seinen Wert an eine Variable und setzt ihn auf 0 zurück – für den nächsten Start (die Variable muss dabei ausreichend groß dimensioniert sein, um sich noch mit 64 multiplizieren zu lassen!). Außerdem wäre es elegant, in dieser Stop-ISR ein Flag-Bit zu setzen, das der Hauptschleife anzeigt, dass jetzt die Zeit berechnet werden kann und soll. Damit bleibt die ISR schön kurz und hält das Hauptprogramm nicht länger auf als nötig.


Alles klar??

Also so ganz hat's noch nicht geklickt.
Denn "... übergibts du seinen Wert an eine Variable und setzt ihn auf 0 zurück ..." - also verwende ich keine Variable mehr, die "inkremiert" wird?

Grüße, Björn

Sauerbruch
25.08.2008, 18:33
also verwende ich keine Variable mehr, die "inkremiert" wird?

...exakt!

Ein Programm sagt mehr als 1000 Worte =P~ :



Dim Zeit as Long
Dim Flag as Bit

Config Timer1=timer, prescale=1024
Stop Timer1
Timer1=0

Config INT0=falling
On INT0 ISR_lichtschranke1
Enable INT0

Config INT1=falling
On INT1 ISR_lichtschranke2
Enable INT1

Enable Interrupts


Do

(werkel - werkel...)

If Flag=1 then
Zeit = Zeit * 64
Zeit = Zeit / 1000 'Zeit ist jetzt die Zeit in ms. Umrechnung in km/h
hängt vom Abstand der Lichtschranken ab!!
Flag = 0
End if

Loop


ISR_lichtschranke1: '1. Lichtschranke wurde passiert
Start Timer1
Return

ISR_lichtschranke2: '2. Lichtschranke wurde passiert
Stop Timer1
Zeit=Timer1
Timer1=0
Flag=1
Return



Isses jetzt etwas klarer?

LG,

Daniel

Björn
25.08.2008, 18:52
Ah.. jetzt ist's klar ;) - Hast du die Timer-Konfiguration am Anfang weggelassen? Oder braucht man die nicht?

Grüße, Björn

Sauerbruch
25.08.2008, 19:06
ja - ist aber inzwischen editiert.
(den Prescaler braucht man unbedingt!!)

Ich hab´ die Auflösung von 64µs nochmal überschlagen: In dieser Zeit legt man bei 100km/h etwa 1,8mm zurück (boah - immerhin). Angesichts des ja auch nur mit einer endlichen Genauigkeit messbaren Abstands der Lichtschranken, scheint mir diese zeitliche Auflösung sicher nicht der genauigkeitslimitierende Faktor zu sein O:) .

Gruß,

Daniel

Björn
25.08.2008, 21:19
ja - ist aber inzwischen editiert.
(den Prescaler braucht man unbedingt!!)

Ich hab´ die Auflösung von 64µs nochmal überschlagen: In dieser Zeit legt man bei 100km/h etwa 1,8mm zurück (boah - immerhin). Angesichts des ja auch nur mit einer endlichen Genauigkeit messbaren Abstands der Lichtschranken, scheint mir diese zeitliche Auflösung sicher nicht der genauigkeitslimitierende Faktor zu sein O:) .

Gruß,

Daniel

Ja super!
Eine Frage noch: Ist es problematisch wenn ich zeitweise jeweils einen Interrupt deaktiviere?


ISR_lichtschranke1: '1. Lichtschranke wurde passiert
Start Timer1
Disable lichtschranke1
Return

ISR_lichtschranke2: '2. Lichtschranke wurde passiert
Stop Timer1
Zeit=Timer1
Timer1=0
Flag=1
Disable lichtschranke2
Enable lichtschranke1
Return


Grüße, Björn

Sauerbruch
25.08.2008, 21:30
Ist es problematisch wenn ich zeitweise jeweils einen Interrupt deaktiviere?

Nö - das ist kein Problem!
Wahrscheinlich willst Du sicherstellen, dass die beiden Lichtschranken wirklich nur abwechselnd auslösen können und nicht mehrfach nacheinander, oder? Das wäre eine gute Lösung - nur musst Du in der laufenden ISR den jeweils anderen Interrupt wieder aktivieren (in Deinem Code fehlt in der 1. ISR die Aktivierung der 2.)

Schönen Abend noch,

Daniel

Björn
25.08.2008, 21:32
Ist es problematisch wenn ich zeitweise jeweils einen Interrupt deaktiviere?

Nö - das ist kein Problem!
Wahrscheinlich willst Du sicherstellen, dass die beiden Lichtschranken wirklich nur abwechselnd auslösen können und nicht mehrfach nacheinander, oder? Das wäre eine gute Lösung - nur musst Du in der laufenden ISR den jeweils anderen Interrupt wieder aktivieren (in Deinem Code fehlt in der 1. ISR die Aktivierung der 2.)

Schönen Abend noch,

Daniel

Oh ja, das habe ich auf die schnelle vergessen.
Dann ist alles klar!
Vielen Dank für deine Hilfe!

Grüße, Björn