Hallo Zusammen,
seit einigen Tagen versuche ich einen Kanal eines RC Empfängers
auszulesen denn ich an INT0 eines atmega32 angeschlossen habe.
Ablauf:
-Nach Auftritt der steigenden Flanke an INT0 starte ich den Timer 0 (ISR
gesteuert) im CTC Modus und inkrementiere eine Variable beim Erreichen
des OCR0 Wertes (ISR gesteuert).
-Beim Erkennen der fallenden Flanke (ISR gesteuert) stoppe ich den Timer
0 bis die nächste steigende Flanke erkannt wird und gebe die Variable
aus.
-Der Timer arbeitet mit 16 MHz, also Vorteiler 1 und der OCR0 Wert ist
auf 15 Eingestellt also 1Mhz. Somit wird theoretisch jede Mikrosekunde
die Variable hoch gezählt. Die Zeit zwischen einer steigenden- und
fallenden Flanke beträgt zwischen 1000 und 2000 Mikrosekunden, aber als
Ergebnis bekomme ich immer etwas zwischen 192 und 330.
Kann der AVR überhaupt so schnell zählen? Theoretisch hat er 16 Takte um
die Variable zu inkrementieren. Ist das ausreichend?
Beschäftige mich erst seit kurzem mit AVR’s, also bitte nicht erschlagen
wenn ich etwas Grundlegendes nicht verstehe!
Den Quelltext liefere ich heute Abend nach.
Schon Mal Danke und Gruß
Rainer
@ Stefan: Den code stelle ich heute Abend ein, bin derzeit nicht an
meinem Rechner.
@Paule: Es geht mir ums Verständnis, eine Funktionierende Lösung habe
ich auch mit dem Timer1.
> Kann der AVR überhaupt so schnell zählen?
Ja kann er, vor allem die Timer, denn dafür sind sie gemacht.
Irgendwo hast Du sicher einen fehler im Programm.
Innerhalb von 16 Taktzyklen wird deine Interrupt-Routine sicher nicht
fertig. Das heißt, es gehen viele Timer-Interrupts verloren.
Ich würde es so machen:
* Statt Timer0 den Timer1 verwenden, denn der zählt bis maximal 65535
(16 Bits).
* Den Prescaler auf 8 setzen (Register TCCR1B). 16 wär einfacher, aber
diese Option gibt es hier nicht.
* An Stelle einer eigenen Variablen einfach das Zählregister des Timers
auf 0 setzen und am Ende auslesen (TCNT1L und TCNT1H, auf die
Reihenfolge achten).
* Das Ergebnis halbieren (Assembler-Befehl "lsr").
Rainer schrieb:> Kann der AVR überhaupt so schnell zählen? Theoretisch hat er 16 Takte um> die Variable zu inkrementieren. Ist das ausreichend?
Für das bloße Inkrementieren einer Variable wäre das ausreichend. Aber
für den kompletten Interrupt nie und nimmer. Allein Ein- und Aussprung
brauchen schon 11 Takte, und dazu kommt dann auch noch das Sichern und
Wiederherstellen der verwendeten Register.
Danke Euch allen,
damit habe ich auch die Antwort auf meine Frage – es geht nicht :(
Dann muss ich mir etwas anderes überlegen. Da der Timer1 später
anderweitig verwendet wird, wird es wohl nicht so einfach das Ganze zu
realisieren.
Trotzdem Vielen Dank für die Hilfe.
Gruß
Rainer
Rainer schrieb:> Danke Euch allen,>> damit habe ich auch die Antwort auf meine Frage – es geht nicht :(> Dann muss ich mir etwas anderes überlegen. Da der Timer1 später> anderweitig verwendet wird, wird es wohl nicht so einfach das Ganze zu> realisieren.
Langsam.
Niemand sagt, dass man einen Timer nur für eine Sache verwenden kann.
Der kann auch, bei geschicktem Vorgehen, dazu benutzt werden, mehrere
Sache zu behandeln.
Und oft genug beginnt dieses 'geschickte Vorgehen' damit, dass man sich
darüber im Klaren ist, dass dieses 'Wenn ... dann setze ich den Timer
auf 0' Käse ist. Dieses Timer bewusst starten/stoppen/0-setzen braucht
in Wirklichkeit meistens kein Mensch. Lass den Timer durchlaufen und
nimm die numerische Differenz zwischen den Zählerständen 2-er Ereignisse
und du hast genau das gleiche Ergebnis. Wenn du unsigned rechnest und
der Timer in der bewussten Zeit gar nicht bis 65535 zählen kann, dann
brauchst du einen Overflow arithmetisch noch nicht mal speziell
berücksichtigen. Du rechnest einfach immer Ende-Anfang und es kommt
durch die unsigned-Regeln IMMER das richtige Ergebnis raus.
Lässt du beispielsweise bei 16Mhz den Timer 1 durchlaufen (bei Prescaler
1), dann bewegt sich die Differnz zwischen der steigenden und der
fallenden Flanke eines Servosignals in der Größenordnung von 16000 bis
32000. Ob du dabei Taktgenau den Timerstand abgreifst, spielt praktisch
gesehen kaum eine Rolle, denn kein Servo kann tatsächlich seinen
Servoweg in 16000 Positionen reproduzierbar auflösen. D.h. man wird die
festgestellte Differenz der Zählerstände sowieso sofort verringern,
wodurch dann auch der eine oder andere verpasste Timertakt sich
rausmittelt.
Rainer schrieb:> damit habe ich auch die Antwort auf meine Frage – es geht nicht :(> Dann muss ich mir etwas anderes überlegen. Da der Timer1 später> anderweitig verwendet wird, wird es wohl nicht so einfach das Ganze zu> realisieren.
Nicht so leicht aufgeben. :-)
Ich wusste nicht, dass der Timer1 nicht zur Verfügung steht. Du kannst
trotzdem die Lösung verwenden, die ich oben skizziert habe. Der Zähler
des Timer0 läuft dann nur bis 255, das ist für dich das Low-Byte. Über
den Timer-Overflow-Interrupt kannst du quasi manuell das High-Byte
weiterzählen.
Natürlich passiert es dadurch, dass du den Zähler nicht
mikrosekundengenau stoppen kannst, wenn er grad dabei ist, das Hihg-Byte
zu erhöhen. Mit dieser kleinen Ungenauigkeit müsstest du leben.
Alternativ könntest du versuchen, die Interruptroutine in Assembler mit
ein paar Tricks innerhalb der 16 Takte zum Laufen zu kriegen. Die
restliche Rechenleistung des µC sinkt dann aber gewaltig ab.
Oder du misst die Zeit aktiv über Zählschleifen. Das geht natürlich nur,
wenn der Mikrocontroller während dieser Zeit keine anderen Aufgaben
erledigen muss.
Andere Frage: Steht denn der Timer2 auch zur Verfügung? Du könntest ihn
parallel zum Timer0 laufen lassen. Der Timer2 besitzt einen eigenen
Prescaler, du könntest ihn zum Beispiel mit 1/128 der Geschwindigkeit
des Timer0 laufen lassen. Aus beiden gezählten Werten kannst du dann
ohne Weiteres und ohne Umschaltfehler die tatsächliche Zeit ausrechnen.
Nochmals Danke,
werde versuchen das Ganze als erstes mit dem Timer1 zu realisieren, wie
Karl Heinz vorgeschlagen hat. Dann muss ich nur den Zwischenwert nach
einem Overflow speichern und diesen anschließend verrechnen. Und in der
ISR bei einer fallenden Flanke eine Fallunterscheidung durchführen ob
ein Overflow stattgefunden hat.
In etwa so:
ISR(INT0_...){
if(steigende Flanke){
berechne= 1;
start = TCNT1;
return;
}
if(fallende Flanke){
stop = TCNT1;
if(ueberlauf){
signal = zwischenwert + stop
zwischenwert = 0;
ueberlauf = 0;
}else{
signal = stop – start;
}
}
berechne = 0;
start = 0;
stop = 0;
}
ISR(OVF_..){
if(berechne){
ueberlaf++;
zwischenwert = (timer_max - start)
}
}
Bin gespannt ob das heute Abend so funktioniert :)
Gruß
Rainer
Rainer schrieb:> Nochmals Danke,>> werde versuchen das Ganze als erstes mit dem Timer1 zu realisieren, wie> Karl Heinz vorgeschlagen hat. Dann muss ich nur den Zwischenwert nach> einem Overflow speichern
Nö. Nicht nach einem Overflow.
Du lässt den Timer laufen und wenn dein externer Interrupt (für die
Signalflanke) kommt, dann liest du den Zahlenwert des Timers aus (oder
du benutzt den Input Capture, was dasselbe macht nur etwas genauer und
du hast nur 1 Input Capture Eingang)
Du brauchst dazu keinen Overflow. Bildlich gesprochen hast du eine
Armbanduhr, deren Sekundenzeiger ständig kreist (das ist der Timer).
Immer wenn du die Flanke siehst, schaust du auf die Uhr und siehst nach,
wo der Zeiger gerade steht. 2 dieser Informationen kannst du dann
verrechnen, wieviele Sekunden dazwischen lagen. Aber wie oft der
Sekundenzeiger dabei die 0 überquert hat (und 1 Minute weitergeschaltet
hat) interessiert dich überhaupt nicht, solange du weißt, dass maximal 1
Minute zwischen den Ereignissen lag.
Hallo Karl Heinz,
eventuell verstehe ich etwas falsch, aber was passiert wenn die
steigende Flanke zu einem Zeitpunkt kommt, kurz bevor der Timer1 den
Endwert 65535 erreicht. Da der Timer1 noch eine weitere Aufgabe hat,
möchte ich ihn bei einer steigenden Flanke nicht auf 0 setzen.
Wenn ich von 16000 bis 32000 Takten für ein Signal ausgehe.
Beispiel 1:
Der Timer1 arbeitet im Normalmodus und die steigende Flanke kommt bei
TCNT1
= 60535.
Somit habe ich als Startposition 60535.
Nach 65535 fängt der Timer1 bei 0 an.
Somit befindet sich mein Endwert (fallende Flanke) irgendwo zwischen
11000 und 27000.
Beispiel 2:
Ein anderes Mal könnte die steigende Flanke bei z.B. TCNT1 = 10
auftreten und mein Endwert würde sich irgendwo zwischen 16010 und 32010
befinden.
Die Verrechnung im Beispiel 1 und Beispiel 2 unterscheidet sich doch.
Oder stehe ich gerade auf dem Schlauch?
Gruß
Rainer
Rainer schrieb:> Die Verrechnung im Beispiel 1 und Beispiel 2 unterscheidet sich doch.
Nein, tut sie eben nicht, wenn du innerhalb von unsigned-16-Bit
rechnest.
11000 - 60535 = 16001
Rainer schrieb:> Hallo Karl Heinz,>> eventuell verstehe ich etwas falsch, aber was passiert wenn die> steigende Flanke zu einem Zeitpunkt kommt, kurz bevor der Timer1 den> Endwert 65535 erreicht. Da der Timer1 noch eine weitere Aufgabe hat,> möchte ich ihn bei einer steigenden Flanke nicht auf 0 setzen.
Musst du ja auch nicht.
Unsigned Rechnerei berücksichtiggt das für dich.
Zwischen 65554 und 1 liegen 3 Takte
65534 65535 0 1
| | | |
+------+-----+---+
1 2 3
und unsigned gerechnet ergibt
1 - 65534
genau diese 3.
Binär
000000000000001
- 111111111111110
------------------
1000000000000011
Das ist aber ein 17-Bit Ergebnis, d.h. die vorderste 1 fällt weg
(UNterlauf) und das 16-Bit Ergebnis lautet
000000000000011
also 3
Du rechnest einfach
unsigned int start;
unsigned int end;
unsigned int differenz = end - start;
und durch unsigned kommt IMMER das richtige raus: die Anzahl Werte
dazwischen. Selbst dann, wenn da ein Overflow im Spiel ist. Es ist
gerade dieses nicht berücksichtigen dieses Overflows, welches das
möglich macht.
Noch ein hinweis: Falls du mehr als einen kanal brauchst, versuch den
Empfänger auf "Summensignal" umzustellen, der Code wird unwesentlich
länger, und du hast auf einen Schlag alle 6 oder 12 oder 18 Kanäle...