Hallo! Bin gerade auf diese Seite gestoßen und hoffe ihr könnt mir helfen. Ich habe ein Signal welches drei untersciedliche Impulse enthält. Der erste Impuls ist 10µs lang, der zweite 20µs und der dritte 50µs. Diese drei Impulse werden alle 100ms wiederholt. Nun möchte ich dieses Signal mit einem Mikrocontroller auswerten. Dazu benutze ich den Atmega48 in der Programmierspreache C. Als Idee ist mir in den Kopf gekommen, einen Flankenzähler mit dem externen Interuppt zu machen. Bei einer steigenden Flanke wird dann eine Variable hochgezählt. Nur ist mir nicht ganz klar, wie ich die länge der Impulse bestimmen kann, damit ich auch weiß welches Signal gerade ausgewertet wird. Für ein paar Tips wäre ich echt dankbar. Grüße Heiko
Imho klassischer Fall für Input Capture. Schau Dir mal die Funktionsweise der Input Capture Unit des Timers 1 an. Damit kannst Du sehr präzise Impulslängen messen.
@Oskar-Rahul: Ich hoffe, Du bist nicht so fies, dass Du mir den einen klitzekleinen Erfolg missgönnst... ;-)
>Ich hoffe, Du bist nicht so fies, dass Du mir den einen klitzekleinen >Erfolg missgönnst... ;-) Ist ja auch unfair: Ich hatte eine "Sitzung"... >Erfolg missgönnst... ;-) Weisst du, was Voodoo ist? (Tut's schon weh? ;-)
Danke für die schnellen Antworten. Das mit dem Input Capture hört sich gut an, hab ich mir gerade mal im GCC-Tutorial hier auf der Seite angeschaut. Dazu hab ich aber noch ne Frage zur genauen Funktionsweise. Da steht > Nun kann je nach Konfiguration entweder ein Signalwechsel von 0 nach 1 > (steigende Flanke) oder von 1 nach 0 (fallende Flanke) erkannt werden und > der zu diesem Zeitpunkt aktuelle Zählerstand in ein spezielles Register > abgelegt werden. Wann fängt denn der Timer an zu zählen? Sagen wir mal ich stell den Signalwechsel von 0 nach 1 ein. Die Flanke kommt und der Timer fängt an zu zählen. Geht der Pegel wieder auf O dann stoppt der Timer. So, oder zählt er nach der Initialisierung los und stoppt wenn die Flanke kommt?
Der Timer läuft bei dem Verfahren kontinuierlich im Hintergrund. Die Input Capture Unit speichert lediglich bei Auftreten einer entsprechenden Flanke den aktuellen Zählerstand. Bei der nächsten Flanke bildest Du dann die Differenz der beiden erfassten Werte und zack haste Deine Signaldauer...
>Wann fängt denn der Timer an zu zählen?
Den starten man irgendwann vorher.
ICP kopiert den aktuellen Wert des Timers ins ICP-Register. Von dort aus
kann man ihn lesen; er wird beim nächsten Auftreten der Flanke wieder
überschrieben.
Hat man eine steigende Flanke programmiert, schaltet man die
Flankenrichtung in der ICP-ISR um, da man ja die Pulsdauer und nicht die
Periodendauer messen will.
Wenn man dann die alte "Uhrzeit" von der neuen abzieht, erhält man die
Impulslänge (da es sich um einen begrenzten Zahlenraum handelt, braucht
man sich auch nicht um negative Zahlen kümmern: die gibt es nämlich
nicht...).
Ablauf:
Timer starten
ICP-Flanke auf steigend programmieren.
Interrupt freigeben
warten, dass Flag gesetzt ist
flag löschen
Pulslänge auswerten...
weiter warten
Interrupt-Routine:
Ist steigende Flanke eingestellt?
ja: ICPR in "Startzeit" speichern
Flanke auf "fallend" stellen
nein:
ICPR in "Stopzeit" speichern
Flanke auf "steigend" stellen
"Pulslänge" = "Stopzeit" - "Startzeit"
Flag setzen.
Versteh ich nicht ganz. Also der Timer wird initialisiert und fängt an zu zählen. Bei einem Wert von 50 kommt die erste Flanke. Die 50 wird gespeichert und in eine Variable geschrieben. Der erste Impuls sagen wir dauert 10 Zählwerte, dann sind wir bei 60. Die nächste Flanke kommt bei einem Zählwert von 200 und die 200 wird wieder gespeichert. Aber wie komm ich da auf die 10 für meine Impulslänge? -- ---- | | | | | | | | ----- --------------- --------------- 0 50 60 200
Ergänzung: Tritt zwischen zwei Flanken ein Timer-Überlauf auf, muss der natürlich entsprechend berücksichtigt werden, sonst kommt Müll raus.
Hab den Tread von Rahul jetzt erst gelesen und nun auch kapiert. Supi! Vielen Dank euch beiden!
Da Du nach der ersten pos. Flanke die Flanke auf neg. umgestellt hast, wird nach der 50 zunächst mal die 60 gespeichert. Und 60-50 = 10... Klar? Danach wird wieder auf pos. Flanke umgestellt und die 200 kommt als nächster (Start-)Wert rein.
>Tritt zwischen zwei Flanken ein Timer-Überlauf auf, muss der natürlich >entsprechend berücksichtigt werden, sonst kommt Müll raus. Nö, tut es nicht. (Zumindest nicht bei dieser Pulslänge...)
Binäre Berechnung: Stopzeit = 0001 Startzeit = 1100 Somit ist Startzeit > Stopzeit Stopzeit - Startzeit: 0001 - 1100 ------ 10101 Da hier nur mit 4 Bitz gerechnet wird, streicht man die erste Stelle (5.Bit/Übertrag) noch, und hat das richtige Ergebnis... (Die führende 1 kann man auch als Vorzeichen ansehen...) http://de.wikipedia.org/wiki/Dualsystem#Schriftliche_Subtraktion
@wie-auch-immer-Rahul:
> Probleme gibt es erst, wenn es zu einem weiteren Überlauf kommt...
...oder wenn der zweite Wert nach einem Überlauf größer oder gleich dem
ersten (vor dem Überlauf) ist, also z.B. Startwert: 10000d, dann
Überlauf, dann Stopwert: 12000d -> Differenz: 2000, müsste aber
eigentlich ein bisschen mehr sein...
>müsste aber eigentlich ein bisschen mehr sein
Dann hat der Programmentwickler Mist gebaut.
So, hab den Code soweit fertig. Scheint aber noch nicht zu funktionieren. Hab bestimmt wieder die Hälfte vergessen;-) Vielleicht könnt ihr ja mal drüber schauen, hänge ihn mal an. Ach ja, nicht über die Auswertung wundern, die war jetzt nur zum testen. Danke
> if (TCCR1B & (1<<UDRE0)) Was hat UDRE0 denn mit dem TCCR1B zu tun? Du musst schon das ICES1-Bit abfragen. Ich will jetzt auch nicht nachsehen, ob das UDRE0 zufällig an der selben Stelle steht wie das ICES1... > Startwert = ICR1L; > Startwert += (ICR1H<<8); Für sowas sind in der Lib 16-Bit-Register definiert. Also besser
1 | Startwert = ICR1; |
Dann brauchste Dir keine Gedanken über die (wichtige) Reihenfolge beim Auslesen zu machen (die bei Dir aber stimmt...).
>TCCR1B & (1<<UDRE0)) Interessanter Aspekt, den Timer direkt mit dem USART zu verbinden... > Stopwert = ICR1L; > Stopwert += (ICR1H<<8); Geht einfacher: Stopwert = ICR1;
Versuch doch mal folgendes: ICP des Timers wie vorher beschrieben initialisieren auf steigende Flanke wenn der Interrupt kommt Timerwert löschen und auf fallende Flanke setzen wenn der nächste Int ausgelöst wird hast du ziemlich genau die Impulslänge in Ticks keine Start und Stopprechnungen nötig ob der Timer überläuft oder nicht ist egal, da nur der Wert während des High-Pegels gemessen wird MW
Oh oh, ich spüre schon wieder diese Schmerzen... BTW: Möglicherweise gibts Probleme, weil die Variable "Pulsdauer", auf die in der ISR und im Programm zugegriffen wird, nicht "volatile" ist. Besser
1 | volatile unsigned int Pulsdauer; |
>ob der Timer überläuft oder nicht ist egal, da nur der Wert während des >High-Pegels gemessen wird Angenommen der Controller hat eine Taktfrequenz von 1MHz und der Impuls ist etwa 14.0000µs lang. Man will die exakte Imulslänge wissen (so wie Johnny oben). Ist da der Überlauf wirklich irrelevant? Eher nicht. >wenn der Interrupt kommt Timerwert löschen und auf fallende Flanke >setzen Das mag bei dieser Anwendung vielleicht gehen, benutz den gleichen Timer dann aber noch zur Erzeugung einer PWM. Da hast du schöne EMV-Probleme, wenn du eine induktive Last betreibst...
@Michael:
> ...ziemlich genau die Impulslänge in Ticks
Aber eben nur "ziemlich" genau, nämlich abzüglich der Zeit zwischen
Auftreten des Interrupts (setzen des Flag) und Beendigung des
Löschvorgangs im Timer-Register (und das sind allein zwei Zugriffe). Da
gehen einige "Ticks" flöten, was grad bei Zeiten im µs-Bereich durchaus
erheblich sein kann!
@ Rahul, die Zeiten stehen fest, 10, 20 und 50 µs, ok? @ Jonny, die Zeiten sind aber konstant und können zu der Berechnung addiert werden. MW
@Michael:
> die Zeiten sind aber konstant und können zu der Berechnung addiert werden.
Jau, wenn man als fortgeschrittener (und Assembler-erfahrener)
Programmierer sich die Mühe macht, rauszuklamüsern, was der Compiler
beim Einsprung in die ISR alles macht oder wenn man direkt in Assembler
programmiert. Außerdem enthält Dein obiges Posting diese Information
nicht und dient deshalb höchstens zur Verwirrung eines Anfängers. Also
entweder die ganze Wahrheit oder gar nichts... Und Heiko programmiert
weder in Assembler noch scheint er ein erfahrener Programmierer zu sein.
>aber konstant und können zu der Berechnung >addiert werden. Finde das mal raus, ohne den Assemblercode durchzukämmen! Warum soll der Controller nicht ein wenig rechnen? Wie oben schon erwähnt: Kommt es zu einem Überlauf, wird dieser auf jeden Fall (unbewusst) mit reingerechnet. Alles andere ist doch "Gebastel". Der 8051 hat(te) eine schöne Timer-Funktion: Den Gate-Mode. Da wurde der Timer noch inkrmentiert, wenn ein Pin auf einem bestimmten Pegel lag - wunderbar um Pulslängen zu messen. Da die Pulslänge ja feststehen, kann man also wunderbar "meine" Methode anwenden. Wenn es um Pulslängen geht, die grösser als ein kompletter Timerumfang sind, muß man dessen Messbereich durch ein oder mehr Bytes erweitern. So kann man auch aus einem 8Bit-Timer einem 32Bit-Timer machen... >...oder wenn der zweite Wert nach einem Überlauf größer oder gleich dem >ersten (vor dem Überlauf) ist, also z.B. Startwert: 10000d, dann >Überlauf, dann Stopwert: 12000d -> Differenz: 2000, müsste aber >eigentlich ein bisschen mehr sein... >die Zeiten stehen fest, 10, 20 und 50 µs, ok? Sagt das Johnny, nicht mir!
Danke, hab ich nun geändert. Nur irgendwie scheint das Programm gar nicht erst in die Interrupt Routine zu springen. Lass mir dort zum Test eine Variable hochzählen und schaue mir diese dann später im Watch_Fenster an.(Debugger JTAGICE mkII) Doch nichts tut sich. Hab das Programm noch mal angehängt.
@Rahul:
> Sagt das Johnny, nicht mir!
Hab's gelesen und entschuldige mich in aller Form dafür, dass ich ein
Beispiel eingebracht habe für einen Fall, bei dem es Probleme geben
könnte, auch wenn dieser Fall im konkreten Anwendungsfall nie
auftritt... :-)
Hallo nochmal, hab nun das ganze Wochenende damit zugebracht, den Fehler zu suchen. Leider ohne Erfolg. Wo kann der Fehler liegen? Hab ich irgenetwas falsch initialisiert, da der Input Capture Interrupt ja anscheinend gar nicht erst ausgeführt wird wenn ich eine Signal an PB0 anlege? Ein Tip wäre super!
Kann es sein, dasss deine Signalquelle einen Open Collector-Ausgang ist? Dann musst du noch den Pull Up Widerstand an dem Portpin aktivieren. MW
> Kann es sein, dasss deine Signalquelle einen Open Collector-Ausgang ist?
Ist es nicht.
Hab den Pullup am Portpin trotzden mal aktiviert. Klappt aber auch
nicht.
Woher weißt Du denn jetzt, dass die ISR nicht ausgeführt wird? Ich sehe in Deinem Code nirgends eine Ausgabe. Außerdem solltest Du wirklich Variablen, auf die in ISR und Programm zugegriffen wird, volatile deklarieren. Ich hab das oben nicht aus Spaß gesagt...
> Woher weißt Du denn jetzt, dass die ISR nicht ausgeführt wird? Ich sehe > in Deinem Code nirgends eine Ausgabe. Doch doch, in der ISR wird die Variable C hochgezählt. Und die ist auch nach mehrmaligem anlegen einer Flanke immer 0. Egal ob volatile oder nicht.
Meine Frage bezog sich darauf, woher Du weißt, dass die Variable sich nicht ändert (dass sie da ist und inkrementiert wird, hab ich gesehen; so blind bin ich auch nicht...). Sie wird nirgends weiter bearbeitet oder ausgegeben!
Siehe oben: > Lass mir dort zum Test eine Variable hochzählen und schaue mir diese dann > später im Watch_Fenster an.(Debugger JTAGICE mkII)
Aha, das hatte ich jetzt beim "Wieder-Einlesen" übersehen. Hatte extra drübergeschaut, ob irgendwo das Wörtchen JTAG auftaucht...
Ich glaub ich hab den Fehler. Ich hab den Timer zwar initialisiert(Taktfrequenz, Interrupts), doch vergessen einem Mode einzustellen. Welcher Mode für den Timer eignet sich am besten bei einem Input Capture? CTC?
Nein, Du brauchst keinen Waveform-Generation-Mode für das was Du vorhast. Der Timer muss nur laufen, und das sollte er nach Deiner Initialisierung tun. Die Waveform-Generation-Modes brauchste nur dann, wenn Du ein Signal erzeugen willst.
> ...Du brauchst keinen Waveform-Generation-Mode...
OK, um keine Verwirrung zu stiften: Du kannst den
Waveform-Generation-Mode "0" (Normal Operation) einstellen. Der ist aber
defaultmäßig aktiv und ist für meine Begriffe eigentlich kein
Waveform-Generation-Mode...
Normalerweise müsste die Capture Unit mit jeder Timer-Betriebsart bis auf Waveform-Generation-Modes 8, 10, 12 und 14 (also die, bei denen ICR1 eine andere Funktion hat) funktionieren. Um sicherzustellen, dass da nix passiert, könntest Du mal in Deinem Code die "|=" bei den Zugriffen auf TCCR1A und B durch "=" ersetzen. Die Bits haben zwar alle lt. Datenblatt den Reset-Wert 0, aber man weiß ja nie. Und andere Bits in den Registern brauchste eh nicht.
Aber wo kann dann der Fehler liegen? An meinem Signal? Der Controller wird mit 4,5 Volt betrieben. Das Signal ist mitlerweile schon nur noch eine Flanke(4,2V) von einem Labornetzteil.
> Um sicherzustellen, dass da nix passiert, könntest Du mal in Deinem Code die >
"|=" bei den Zugriffen auf TCCR1A und B durch "=" ersetzen.
Hat sich nichts geändert.
Ach ja, Bits im Register TCCR1A hab ich in meinem Code gar nicht
gesetzt. Da müsste doch dann defaultmäßig die "normal port operation"
eingestellt sein. Muss ich da irgenwas anderes einstellen?
Bei "Flanken" von Labornetzteilen wäre ich sehr vorsichtig. Hast Du es mal mit einem anderen Controller probiert? Dass Du das Signal auch tatsächlich an PORTB.0 angeschlossen hast, setze ich mal voraus (Obwohl: man weiß ja nie...). Ich steh momentan auch ziemlich auf dem Schlauch und weiß keine anderen Lösungsvorschläge mehr. Aber hinterher isses wahrscheinlich wieder irgendwas triviales, und man hat nur den Wald vor lauter Bäumen nicht gesehen. Ich schau jedenfalls, wenn ich zwischen der Arbeit mal Zeit habe auf jeden Fall noch mal drüber...
Werd ich gleich mal mit nem Signal vom anderen Controller versuchen. Wie du schon sagst, wird wohl irgendein dummer Fehler sein, den man einfach übersehen hat. Erst einmal vielen Dank für deine Mühe! Und wäre total super, wenn du noch mal drüberschauen könntest, falls du Zeit hast. Danke und Gruß Heiko
Schau dir mal die Interrupt-Tabelle an, der 48 und der 88 haben die gleichen Einsprungadressen (so wie ich es überschlagen habe), nur der 168 hat eine andere Tabelle. Evtl. liegt da der Fehler. MW
Nachtrag: auch 48 und 88 haben andere Tabellen. Nur in dem Bereich, in dem du arbeitest sind sie gleich. MW
>aber wo finde ich diese Tabellen?
Ohne den Beitrag genau gelesen zu haben:
Im Datenblatt!
>Schau dir mal die Interrupt-Tabelle an, der 48 und der 88 haben die >gleichen Einsprungadressen (so wie ich es überschlagen habe), nur der >168 hat eine andere Tabelle. Evtl. liegt da der Fehler. Interrupt-Tabellen? Und C? Sollten nicht weiter interessieren, wenn man im makefile den richtigen Controller angegeben hat. Um die kümmert sich in der Compiler.
Hab das Programm mal bei mir durch den Simulator gejagt und da funktioniert es tadellos. Der Interrupt wird schön sauber ausgeführt, auch in der Langversion der ISR klappts.
>>Hatte ich schon überprüft. Im Build-Fenster steht: "Device: Atmega88"
Ich denk du arbeitest mit dem 48?
MW
Das mit Mega48 / 88 war mir auch grad aufgefallen. Sollte aber in dem Fall keine Schwierigkeiten machen. Solange da kein Mega168 steht, unterscheiden die sich afaik nicht.
Vielleicht versucht der Compiler den Stack da anzulegen, wo der 48 gar kein Ram hat (256 zu 512 Byte). MW
@Michael: Das ist natürlich nicht von der Hand zu weisen. ATMega48: 512 Bytes RAM, ATMega88: 1KByte RAM. @Heiko: Also, einmal für ATMega48 kompilieren bitte...
> Hab das Programm mal bei mir durch den Simulator gejagt und da > funktioniert es tadellos. Der Interrupt wird schön sauber ausgeführt, > auch in der Langversion der ISR klappts. Daher könnte man annehmen, der Pin PB0 ist kaputt. Versucht ne LED über den Pin zu schalten. Geht nicht. Tonne auf, Controller rein. Neuen aufs Board und siehe da, es funktioniert. Was ein dummer ärgerlicher Fehler. Nur da erst mal drauf zu kommen, da alle anderen Pins einwandfrei funktionierten. Na ja, allen vielen Dank die sich mit dem Problem auseinandergesetzt haben! Versuche nächste mal solche Fehler(was mir bestimmt nicht noch mal passiert) direkt auszuschließen. Gruß und besten Dank!
@ Jonny, hast natürlich recht. Hab auf die schnelle die EEPROM Daten gelesen. MW
@Michael: Die genauen Zahlen sind ja eh egal. Der Unterschied (den ich beim ersten Datenblattcheck übersehen hatte) macht die Musik... Gruß joHnny
Hallo, ich bin es nochmal. Soweit funktioniert das ganze. Ich lese nun zwei Impulse von einer Signalquelle ein. Die Signalquelle erzeugt ein Rechtecksignal mit einer Periodendauer von 30µs. Also ein Highpegel hat eine Länge von 15µs. Das Capture Interrupt habe ich so programmiert, wie oben schon Besprochen. Das ganze läuft nun folgendermaßen ab: - Positive Flanke kommt - Interrupt_Routine wird ausgeführt - Wenn auf positive Flanke eingestellt, Startwert = ICR1 und auf negative Flanke gestellt - Zusätzlich wird in der Routine noch die Variable c hochgezählt - Routine wird beendet. - Negative Flanke kommt - Interrupt_Routine wird ausgeführt - Wenn auf negative Flanke eingestellt, Stopwert = ICR1 und auf positive Flanke gestellt und Impulslänge durch Stopwert - Startwert berechnet - Zusätzlich wird in der Routine noch die Variable c hochgezählt - Routine wird beendet. Das ganze ist zum Test erst einmal auf 4 Durchläufe begrenzt. Daher müssten genau 2 Impulse gemessen und den zugehörigen Variablen zugeordnet werden. Die Zuordnung geschieht durch den Wert der Variable c. Was mich ein wenig wundert ist, das ich für gleiche Impulslängen unterschiedliche Werte herausbekomme. Und nicht nur leicht unterschiedliche, die Werte der Variablen unterscheiden um ca. 1000. Hab den aktuellen Code nochmal angehangen. Gruß Heiko
>Die Signalquelle erzeugt ein Rechtecksignal mit einer Periodendauer von >30µs.
Mit welcher Frequenz arbeitet dein Controller?
Kannst du u.U. auch nur einen einzelnen "Ping"* senden?
Ich vermute, dass deine ISR einfach zu lang ist, und du deswegen die
fallende Flanke eines der darauffolgenden Impulse misst.
*"Jagd auf Roter Oktober"...
Deine Funktion ICP_Auswertung ist nicht sonderlich geschickt angelegt. Da sie ständig wieder ausgeführt wird, kann da alles Mögliche schief gehen. Das Beste wäre, die Funktion nur dann einmal auszuführen, wenn sich tatsächlich was geändert hat (z.B. indem Du ein Software-Flag in der ISR setzt und nur dann, wenn dieses gesetzt ist, die Funktion aufrufst). Zugriffe auf 16-Bit-Variablen, die in ISRs verändert werden können, sind generell kritisch. Man sollte in solchen Fällen besser vor dem Zugriff im Hauptprogramm die Interrupt-Bearbeitung deaktivieren (mit cli()) und erst nach Beendigung des Vorganges mit sei() wieder freigeben.
BTW: Mit was für ner Taktfrequenz läuft Dein µC eigentlich? Kann zwar sein, dass das oben schon mal erwähnt wurde, ich finde es jetzt aber nicht...
Der Controller läuft mit 14,74500 MHz. Nen einzelnen Ping kann ich nicht senden.
Also, 15µs / 67ns = 224, d.h. zwischen zwei Flanken sollten 224 Zyklen passen. Das sollte eigentlich für das bisschen Programm in der ISR und die Auswertung dicke reichen. Daran dürfte es also nicht liegen...
Hab es jetzt gerade hinbekommen, nur einen einzelnen Rechteckimpuls von 10µs Länge zu erzeugen. Trotzdem wird die ISR 4mal aufgerufen. Das dürfte doch eigentlich auch schon nicht sein.
Ich hab noch mal ins Datenblatt geschaut. Hier der Auszug: Measurement of an external signal’s duty cycle requires that the trigger edge is changed after each capture. Changing the edge sensing must be done as early as possible after the ICR1 Register has been read. After a change of the edge, the Input Capture Flag (ICF1) must be cleared by software (writing a logical one to the I/O bit location). For measuring frequency only, the clearing of the ICF1 Flag is not required (if an interrupt handler is used). Also, wenn du per Hand die Flanke wechselst das entsprechende Bit (ICF1) löschen. Das sollte helfen. MW
Aah ja, da hatte ich auch nicht mehr dran gedacht! Die Input Capture Unit detektiert ein Umschalten der Flankendetektierung selber als Flanke und setzt das Flag sofort wieder...
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.