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