Hallo Leute, ich verwende einen ATmega 16 mit CodeVision C Compiler. Was ich vorhabe, ist mit Hilfe des Input Capture die Zeit zwischen zwei Ereignissen zu bestimmen. Hierzu betreibe ich den Timer in Normal Mode und zur Zeit mit 10MHz und 1024 Vorteiler, da ich die Ereignisse mit Hilfe eines Tasters zuführe. Dementsprechend sehen meine beiden Control-register aus: TCCR1A=0x00 und TCCR1B=0xC5; Nun habe ich mal zum Testen mir das TIFR Register bei jedem ICP-Interrupt ausgeben lassen. Was mich hier schon stutzig macht, ist die Tatsache, dass das TOV1-Flag zu eins wird bei einem Timer-Overflow obwohl doch im Datenblatt steht: "...,TOV1 can be cleared by writing a logic one to its bit location." Also genau invertiert, eigentlich. Dieses TOV1 Bit interessiert mich insofern, als dass das System feststellen soll, dass ein Timer-Überlauf stattgefunden hat und der Timer erst zurückgesetzt wird und erst beim nächsten Tastendruck wieder die Differenzzeit bestimmt werden soll. Aufgrund diesen Erkenntnissen habe ich folgenden Quellcode produziert für den Interrupt Input Capture (siehe Anhang): Das Programm läuft nun wie folgt ab: 1. Drückt man den Taster ohne dass ein Overflow stattfindet kommt zumindest ein Wert im Terminal an. Dieser Wert ist aber offensichtlich falsch. 2. Wartet man bis ein Overflow stattfindet (also 6.71s) und drückt dann den Taster (auch mehrmals) tut sich nichts mehr. Meine Frage nun, was mache ich falsch bzw. wer kennt sich in diesem Bereich aus. Es haben doch bestimmt schon viele von euch mit dem Input-Capture gearbeitet. Das dürfte doch nicht zu schwer sein die Zeit zwischen wei Ereignissen zu bestimmen. Für eure Hilfe bedanke ich mich schon im voraus. Gruß Bob
Es gibt so einige Bits beim AVR, die durch Reinschreiben einer 1 gelöscht werden (Siehe auch Fueses). Wo ist denn der Anhang? Welche Funktion willst du mit dem Programm erfüllen?
Sorry, Anhang vergessen, Danke für den Hinweis Das Programm soll später eine 1-Wire Verbindung monitoren. Der Datentransfer findet hierbei jedoch nur jede Minute für mehrere Sekunden statt. Deshalb möchte ich auch den Timer-Overflow registieren. Gruß Bob
Du hast das Konzept von Interrupts nicht verstanden. Interrupts verwendet man, um auf etwas schnell zu reagieren. Das bedingt aber auch, daß der Interrupt selber schnell sein muß, d.h. keine ellenlange Rechenzeit vernichten darf. Deshalb führen Delays und schnarchlahme printfs() innerhalb von Interrupts das Ganze ad absurdum. Der 2. Punkt ist, daß nicht entprellte Tasten keine 2 Flanken erzeugen sondern mehrere. Peter
Lieber Peter, ich verstehe natürlich deine Argumentation. Aber um diese ICP Funktion erstmal grundsätzlich in Betrieb zu nehmen ist es doch sinnvoll Taster und LEDs zu benutzen, damit man erkennt wo das Programm klemmt. Wenn ich ja Anfang der ISR die #asm("cli") ausführe entprelle ich die Taste doch softwaremäßig oder sehe ich das falsch. Gut die Ausgabe per Printf könnte man natürlich in einen anderen Timer verfrachten. Falls dir dennoch was einfällt, was mir weiterhelfen würde, wäre ich sehr dankbar. Gruß Bob
Mit Tastern veralbert sich ICP, da Taster prellen. Wenn du zum Test Taster einsetzen willst, dann musst du diese mit weiterer Elektronik zuverlässig entprellen. Ein RC-Glied reicht dazu meist nicht. ...
Also nun habe ich den Taster und die LED weggelassen. Einen ICP Puls erzeuge ich jetzt mit dem Timer 0 so ca. jede Sekunde (hier habe ich auch die printf asugabe reingepackt). Im Terminal landet nun ständig der Wert 121. Wenn der Timer1 aber mit 9,766 kHz läuft, sollte ja eigentlich ein Wert in der Region von 9766 auftauchen.
Ja, mit den Interruptbits legt Atmel jeden rein. Also die Hardware setzt es um es zu setzen, die Software setzt es um es zu löschen:
1 | if( TIFR & 1<<TOV1 ){ // wenn gesetzt |
2 | TIFR = 1<<TOV1; // dann löschen |
3 | // usw.
|
4 | }
|
Nur zum Test kann man auch mit delay im Interrupt entprellen, man darf nur nicht vergessen, danach auch die Preller zu löschen:
1 | delay_ms(200); |
2 | TIFR = 1<<ICF1; // löschen !!! |
Und das cli+sei ist überflüssig, das ist automatisch der Fall in Interrupts. Sonst würden sich manche Interrupts (UART, TWI usw.) ja ständig selbst unterbrechen und nie ausführen. Peter
@Peter mit if(TIFR & 0x04) mache ich das allerselbe wie du Löschen des TOV1 Bit wäre: TIFR=TIFR & 0xFB; oder wie du es machen willst, wäre es TIFR=0 << TOV1;
Int-Flags löscht man aber durch schreiben einer "1" und nicht durch schreiben einer "0". Schau mal ins Datenblatt in die Erklärung zu TIFR... Ausgaberoutinen würde ich grundsätzlich nicht in der ISR machen. Dazu setze ich mir ein Flag, damit das Hauptprogramm den "Job" erledigt. ISRs müssen so kurz wie möglich sein, es sei denn, es gibt nur eine einzige ISR (Timer), in der das gesamte Programm läuft... ...
Sers Leute, so ich kann jetzt mein Problem etwas konkretisieren. Was ich sehr verwirrend fand, ist das was Peter schon angesprochen hatte. Wenn das TOV Bit gesetzt (=1) ist, kann man dieses Löschen indem man es mit 1 beschreibt. Klingt komisch, ist aber so. Ich habe mein Programm aufs wesentlche reduziert, um zu sehen wo der Fehler steckt. Hierzu generierte ich nun einen Interrupt, der ca. alle 20ms einen kurzen High-Pegel auf einen Pin legt, der mit dem ICP verbunden ist. Der ISR des ICP sieht nun wie folgt aus (siehe Anhang). Das Programm läuft und beim Ausgeben von tdiff ans Terminal erhalte ich Werte, die in ihrer Differenz immer 804 ergeben. Dies macht auch Sinn, da der Timer1 mit ca. 39khz läuft (20,479ms * 39,062kHz= 800). Soweit so gut. Möchte ich aber anstatt immer eine Differenz zu bilden, bei jedem Aufruf der ISR den Zähler (TCNT1) auf 0 zurücksetzen (siehe auskommentierten Quelltext), so erhalte ich Werte von 59 und 100 für tdiff. Also hat sich genau hier ein Fehler eingeschlichen, jedoch weiß ich nicht woran das liegt. Wie immer bin ich sehr dankbar für konstruktive Antworten. Ich hoffe ihr könnt mir helfen, da ich das Problem doch jetzt schon sehr konkretisiert habe. Gruß Bob
@Hannes: Ich verstehe ja, dass du uns nahelegen möchtest den Timer schön im Normal-Mode durchlaufen zu lassen. Jedoch möchte ich den Zählerstand TCNT1 bei jedem Aufruf der ISR, die durch den ICP getriggert wird, zurücksetzen. Somit sollte doch in ÌCR1 der Absolutwert seit dem letzten Aufruf stehen! Gerade dies funktioniert bei mir nicht. Der ICR1 Wert ist bei den folgenden Aufrufen immer viel zu klein. Wer hat ne Ahnung?
Dazu musst du nur in der ICP-ISR den Timer auf 0 setzen. Allerdings ist der Zeitversatz ICP-Erreignis (Flanke am Pin) - ICP-ISR nicht immer gleich (Interruptresponsetime oder Int noch blockiert, da andere ISR aktiv), so dass dein Ergebnis mit einem Jitter behaftet ist, also ungenau ist. Was ist denn daran so schlimm oder kompliziert, vom aktuell eingelesenen Wert den zuvor eingelesenen Wert zu subtrahieren? Einfacher geht es doch fast garnicht. Und das Ergebnis hat auf diese Art keinrerlei Jitter. Mal ganz abgesehen davon, dass dir weiterhin beide OCR-Interrupts und (bedingt) der OVF-Interrupt zur Verfügung steht und weitere Software-ICP-Messungen mit den externen Interrupts möglich sind. Aber mach', was du für richtig hältst... Viel Erfolg... ...HanneS...
@Hannes: Danke erstmal dass du den Dialog hier annimmst. <<Dazu musst du nur in der ICP-ISR den Timer auf 0 setzen. Das mache ich ja mit TCNT1=0; Es ist aber so, dass wenn ich ihn (TCNT1) nicht zurücksetze und speicher den ICP Wert ab bei jedem Aufruf und gebe diesen anschließend aus, so sind die Differenzwerte schon O.K. (sagen wir mal bei 800). Setze ich aber den Zähler zurück TCNT1=0; so sind meine Werte nur noch bei ca. 100 <Was ist denn daran so schlimm oder kompliziert, vom aktuell <eingelesenen Wert den zuvor eingelesenen Wert zu subtrahieren? <Einfacher geht es doch fast garnicht. Und das Ergebnis hat auf diese <Art keinrerlei Jitter. Hast ja vollkommen recht. In meiner Anwendung bietet es sich aber an, dass ich den Zählerstand immer rücksetze und falls "längere" Zeit kein Impuls kommt, läuft der Zähler über und ich kann den Overflow registrieren-> sozusagen eine Reset-Bedingung für meine Anwendung. Dies wäre nach deiner Vorgehensweise schwieriger zu implementieren. Der Jitter ist in meiner Anwendung unkritisch. Nun habe ich noch was ganz interessantes durchgeführt: Ich lasse einfach mal den Timer2 in Normal laufen. Bei jedem ISR Aufruf durch ICP lese ich den Zählerstand TCNT2 aus und resete diesen anschließend. Erfreulicherweise funktioniert dies einwandfrei. Meine brennende Frage ist weiterhin: Wieso funktioniert das nicht mit TCNT1 (mal davon abgesehen, dass du das für nicht sinnvoll hälst). Gruß Bob
Das "Timeout" kann man auch bei freilaufendem Timer1 realisieren. In der ICP-ISR setzt man zusätzlich ein OCR-Register auf den ICP-Wert. Der OCR-Interrupt signalisiert dann, dass eine ganze "Timer-Runde" lang kein ICP aufgetreten ist. Also: - In der ICP-ISR OCRA auf den Wert von ICP setzen, - In der OCRA-ISR auf den Timeout reagieren. fertig... Ich verstehe allerdings nicht, warum du in der ISR etwas an TIMSK veränderst? Das ist im Normalfall nicht nötig. Falls doch, dann ist vor jedem Aktivieren eines Bits in TIMSK das zugehörige Bit in TIFR zu löschen (eine Eins reinschreiben!, siehe Datenblatt), ansonsten tritt sofort wieder ein Interrupt auf. Denn als Quelle für Interrupts werden Maske und Flags AND-vernüpft, sind also (Bit in) TIMSK und (Bit in) TIFR gleichzeitig gesetzt, dann gibt es den Sprung zur ISR. Das Setzen der Bits in den Flags (TIFR) übernimmt die Hardware (Timer) bei dem dafür vorgesehenem Ereignis (Pegelwechsel am ICP-Pin, Gleichstand mit OCR, Überlauf), das Löschen übernimmt die Hardware (Controllersteuerung) beim Sprung über den Int-Vektor. ...
Ich wüsste nicht wo ich das TIMSK Register verändere ??? Ich frage nur das TIFR REgister ab und schau ob ein Overflow stattgefunden hat. In dem Punkt bin ich jetzt etwas verdutzt. Gruß Bob
Sorry, verguckt, es war TIFR. Aber da hat man auch nur dann was dran zu suchen, wenn man ein Bit in TIMSK setzen möchte und die bisher aufgetretenen Ereignisse ignorieren möchte. Dann entschuldige bitte, dass ich kein C kann (nur ASM) und dass ich wegen deinem Programm nicht das Datenblatt vorgekramt habe. Im Kopf habe ich das Mega16-Datenblatt auch nicht, da ich momentan kein Mega16-Projekt in Arbeit habe, sorry... Allerdings hielt ich: if(TIFR & 0x04) { TIFR=TIFR & 0xFB; TCNT1=0; } nicht für eine reine Abfrage, sondern für eine Manipulation. Aber wie gesagt, ich kann kein C. Allerdings vermute ich, dass du das Gegenteil von dem erreichst, was du erreichen möchtest. Falls du damit 0x04 löschen willst, dann löscht du alle anderen Bits, nur nicht 0x04. Den die Bits in TIFR löscht man durch Setzen. Ist jedenfalls bei allen anderen AVRs so, wird beim Mega16 sicher nicht anders sein. Steht sicherlich auch im Datenblatt. Sehen wir es mal so: Bei mir funktioniert ICP auf einem AT90S8515 bestens. Damit wird ein tastgradmoduliertes serielles Signal (PWM?) decodiert, dessen Informationen weitere Schalthandlungen auslösen. Der Timer1 wird darin für ICP, OCR1A und OCR1B genutzt. TIFR manipuliere ich nur während der Init-Routine direkt vor dem Zugriff auf TIMSK. Es ist sicherlich auch nicht optimal programmiert, aber es tut seine Arbeit. Ich will dir meinen Stil nicht aufdrängen, mach, was du für richtig hältst. ...
Hallo, 'ne Anregung zur Overflow-Erkennung bei freilaufendem Zähler und Differenzbildung wie von HanneS vorgeschlagen: - Tritt zwischen zwei Capture-Events kein Timer OVF ein, ist das Ergebniss gültig. - Tritt ein Timer OVF ein, ist das Erbnis gültig, solange Timerwert_neu kleiner als Timerwert_alt ist bzw. das Carry-Bit bei der Differenzbildung gesetzt wird. - Treten 2 oder mehr Timer OVFs auf, ist das Ergebnis ungültig. Falls man also die Compare-Einheit für etwas anderes braucht, lässt sich das Problem mit einem Flag für den Timerüberlauf und dem Carry Flag erschlagen. Nachteil dabei ist, ob das Ergebnis gültig ist erfährt man erst nach dem Capture-Event oder einem 2fachen Timerüberlauf. Ciao, Werner By the way, Danke für den Tip, auf die Idee eine der Compare Units dafür zu verwenden bin ich nicht gekommen. Wenn die eh nix zu tun hat, dann kann ich mir das hantieren mit den Flags sparen.
@Hannes: Dieses Problem mit Bits in TIFR löschen habe ich nach etwas rumprobieren hingekriegt. Ich frage ob das Bit (2.Bit -> 0x04) gesetzt ist, also TIFR & 0x04, falls es gesetzt ist, muss ich es löschen indem ich ne eins drauf schreibe, also TIFR=TIFR | 0x04. Es hat mich sehr verdutzt, dass man zu Löschen des Bits eine Eins drauf schreiben muss?! Aber wie gesagt, das habe ich hinbekommen.
Hallo Werner... Bob ist so sehr auf ICP fixiert, dass er die OCRs eh nicht weiter beachtet. Beim Löschen des Timerstandes sind diese ja sowiso nicht mehr nutzbar. Man kann die ganzen Features eben nur dann parallel nutzen, wenn sichergestellt ist, dass Niemand den Timer manipuliert. Und selbst der OVF des freilaufenden Timer1 kann noch sinnvoll verwendet werden, z.B. zum Generieren eines (langsamen) Zeitnormals (hochzählen einer globalen Variable), welches für weitere Software-Timer zum realisieren langer Wartezeiten genutzt werden kann. Dabei sollte man aber nicht in einer Schleife warten, bis der (Software-)Timerstand erreicht ist, sondern zur Mainloop zurückspringen, solange der "Job" noch nicht angearbeitet werden darf. ...
Zur Sicherheit sollte man in der ICP-ISR den OCR auf ICP - 1 setzen. Andernfalls kann es bei größerem Prescaler zu einer falschen Überlauferkennung kommen. Falls OCR auf den aktuellen Wert von TCNT gesetzt wird, wird beim nächsten Clock-Cycle bereits Überlauf erkannt.
Richtig, bei großen Vorteilern. Hatte ich nicht gemacht wegen Vorteiler 1:1. ...
Hallo Bob, ohne Deine Quelltexte wirklich zu kennen (ich kann wie HanneS kein C), schreibst Du, das Du den Capture-Event jetzt nicht mehr mit 'nem Taster, sondern mit Timer0 erzeugst. Vermutung: Du benutzt den Timer0 OVF Interrupt dafür. Damit hast Du folgendes Scenario: Timer0 läuft über: - Sprung in die Timer0 OVF Routine - Erzeugung des ICP Events => Werte von Timer1 werden im Capture Register gespeichert (Hardware) - Ausgabe - Rückkehr aus Timer0 OVF Routine - Sprung in die ICP Routine - Löschen von Timer1 - Rücksprung aus ICP Routine Sprich Du machst alles nur noch schlimmer, weil Durch den 2. Interrupt noch mehr Zeit vergeht bis Timer1 auf Null gesetzt wird. Das Du damit statt 800 nur noch 100 misst verwundert mich nicht sonderlich. Das Printf zwischen Capture-Event und Timer löschen lässt grüßen. Imo bekommst Du das Problem nicht in den Griff, wenn Du Timer1 zwischendurch löschst. Du kannst Timer1 nur löschen, wenn Du KEINEN anderen Interrupt verwendest und so sicherstellen kannst, daß Du relativ konstante Zeiten zwischen Capture-Event und Timer löschen hast. Sobald Du einen 2. Interrupt verwendest, oder Programmteile hast, die nicht unterbrochen werden können oder dürfen, geht das in die Hose, da Du nie weist, war zwischendurch ein anderer Interrupt aktiv oder nicht. Den Timer frei laufen zu lassen und die differenzen zu bilden ist imo der einzige saubere Weg. Ciao, Werner
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.