Forum: Mikrocontroller und Digitale Elektronik Input Capture ATmega16


von Bob (Gast)


Lesenswert?

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

von Rahul (Gast)


Lesenswert?

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?

von Bob (Gast)


Angehängte Dateien:

Lesenswert?

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

von Peter Dannegger (Gast)


Lesenswert?

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

von Bob (Gast)


Lesenswert?

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

von Hannes L. (hannes)


Lesenswert?

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.

...

von Bob (Gast)


Angehängte Dateien:

Lesenswert?

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.

von Peter Dannegger (Gast)


Lesenswert?

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

von Bob (Gast)


Lesenswert?

@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;

von Hannes L. (hannes)


Lesenswert?

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

...

von Bob (Gast)


Angehängte Dateien:

Lesenswert?

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

von Hannes L. (hannes)


Lesenswert?


von Bob (Gast)


Lesenswert?

@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?

von Hannes L. (hannes)


Lesenswert?

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

von Bob (Gast)


Lesenswert?

@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

von Hannes L. (hannes)


Lesenswert?

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.

...

von Bob (Gast)


Lesenswert?

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

von Hannes L. (hannes)


Lesenswert?

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.

...

von Werner J. (werner_j)


Lesenswert?

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.

von Bob (Gast)


Lesenswert?

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

von Hannes L. (hannes)


Lesenswert?

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.

...

von Irgwer (Gast)


Lesenswert?

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.

von Hannes L. (hannes)


Lesenswert?

Richtig, bei großen Vorteilern. Hatte ich nicht gemacht wegen Vorteiler
1:1.

...

von Werner J. (werner_j)


Lesenswert?

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