Hallo zusammen,
habe einen Atmega64 über möchte über input capture den zeitlichen
Verlauf einer Frequenz bestimmen.
Ich möchte im Abstand von 1ms eine Frequenz von etwa 20kHz messen.
Soweit so gut klapp alles, ich benutze timer0 für den 1ms Takt:
1
TCCR0=(1<<CS02);//Prescaler 64
2
OCR0=230;//Vergleiswert setzten
3
TIMSK|=(1<<OCIE0);
Der Systemtakt ist 14.7 MHz, das klappt
für input-capture benutze ich den 16bit timer1
wie gesagt das klappt solange die Frequenz etwa 15kHz nicht
überschreitet,
dazu kommt, dass ich noch ein paar weitere Größen während des kHz Takes
aufnehmen möchte, dann klappt es auch mit 10kHz nicht mehr.
Ich konnte feststellen, dass die Zeitdifferenzen die ich messe richtig
sind, es werden lediglich einige Takte vergessen. Heißt für mich, wenn
das ISR des timer0 läuft, kann das ISR des timer1 nicht aufgerufen
werden.
In der Vector-Tabelle hat der timer1 aber eine kleinere Adresse als
timer0, daher dachte ich, dass innerhalb des timer0 der timer1
aufgerufen werden würde (wie eine Stack Abarbeitung) das scheint nicht
so zu sein???
Ich muss zugeben, mir ist nicht ganz klar, wie das funktioniert? Hoffe
mir kann das jemand erklären? Was muss ich tun, damit meine Pulse nicht
vergessen werden??
merci
Schorsch
Georg T. schrieb:> Ich konnte feststellen, dass die Zeitdifferenzen die ich messe richtig> sind, es werden lediglich einige Takte vergessen. Heißt für mich, wenn> das ISR des timer0 läuft, kann das ISR des timer1 nicht aufgerufen> werden.
Heißt für mich:
deine ISR machen viel zu viel
> daher dachte ich, dass innerhalb des timer0 der timer1 aufgerufen werden würde
Nö.
Defaultmässig sind die Interrupts gesperrt, während eine ISR
abgearbeitet wird.
Georg T. schrieb:> daher dachte ich, dass innerhalb des timer0 der timer1> aufgerufen werden würde (wie eine Stack Abarbeitung) das scheint nicht> so zu sein???
so ist es auch nicht. Es gibt keine Prio bei den Atmels. Es kann immer
nur eine ISR laufen ( außer man manipuliert das ISR flag).
Die Reihenfolge, ist nur wichtig wenn 2 ISR gleichzeitig gestartet
werden sollen, dann gibt sie die Abarbeitung vor.
Georg T. schrieb:> Ich muss zugeben, mir ist nicht ganz klar, wie das funktioniert?
Du mußt Dich entscheiden, wie Du messen möchtest.
Wenn Du mit konstanter Torzeit von 1ms messen willst, brauchst Du den
ICP1 nicht. Nimm einen Zählereingang (ohne Interrupt!) und sieh nach
1ms, wie weit er sich der Zählerstand verändert hat. Als Ergebnisse
erhält Du die Werte 19, 20 odere 21. Wie gesagt, die Torzeit ist
konstant, die Ergebnisse bescheiden.
Wenn Du die genaue Zeit (Periodendauer) des Eingangssignals messen
möchtest, dann verwende ICP1, um den genauen Zeitpunkt des Impulses zu
erhalten. Gleichzeitig zähle per Interrupt die Anzahl der Perioden in
1ms und werte entsprechend aus. Für diese 1ms kannst Du 20 Impulse
abwarten, was ca. 1ms Meßdauer entspricht. Diese 1ms ist aber nur ein
grober Wert und keine konstante Torzeit. Die Messzeiten schwanken von
0,95ms - 1,05ms, aber die Ergebnisse sind sehr genau.
Wie gesagt, nur eine Möglichkeit von beiden geht, nicht beide
gleichzeitig und vermischt. Es ensteht auch keine störende Überlappung
von Interrupts.
Ich denke, das hast du missverstanden.
Er will die zeitliche Entwicklung der Frequenz festhalten. Mit 1ms
Abtastung.
d.h. der eine Timer stellt laufend die Frequenz fest. Über den anderen
Timer ist ein Mechanismus implementiert, der die gerade anliegenden
Frequenz alle 1ms 'aufzeichnet' zb in ein Array wegspeichert. Im Moment
seh ich noch keinen Grund, warum das nicht gehen sollte. Die Zeiten sind
allesamt noch so, dass genügend Taktzyklen bereit stehen um das alles
unter einen Hut zu bringen. Sofern natürlich die ISR nicht mit
Berechnungen vollgestopft sind.
Karl Heinz schrieb:> Ich denke, das hast du missverstanden.>> Er will die zeitliche Entwicklung der Frequenz festhalten. Mit 1ms> Abtastung.
Glaube mir, ich habe es sehrwohl verstanden :-)
Georg muß sich aber entscheiden, ob er das genaue 1ms Raster einhalten
möchte, mit entsprechend bescheidenen Ergebnissen, oder ob er die
Frequenz des Eingangssignals genau bestimmen will.
Da beide Signale asynchron zueinander sind, geht nicht Beides
gleichzeitig.
Ich glaubs zwar nicht, aber in dubio
(Denn deine Vorgehensweise setzt vorraus, dass man das Ergebnis schon
kennt. Er hat nicht 20kHz, sondern von xxx bis 20kHz. Daher weiß er im
Vorfeld nicht, wieviele Pulse am Eingangspin 1ms ergeben).
Dass er einen kleinen Jitter bekommt, ist richtig aber unvermeidlich.
Der spielt sich aber im µs bereich ab. WIe relevant der für die 1ms ist
(und auch nicht kummuliert), muss der TO entscheiden. Auf jeden Fall
aber deuten seine beschriebenen Probleme in eine ganz andere Richtung.
Karl Heinz schrieb:> Denn deine Vorgehensweise setzt vorraus, dass man das Ergebnis schon> kennt. Er hat nicht 20kHz,
Er hat ca. 20kHz, und mein Vorschlag, 20 Impulse zu vermessen, war die
einfache Variante :-)
Die Verfeinerung besteht dann darin, jeweils nach 1ms eine Auswertung
mit den bis dahin eingetroffenen Impulsen vorzunehmen. Dabei werden für
die Anzahl der Impulse sowie die Gesamtzeit für diese Impulse jeweils
die Differenzwerte zur letzten Auswertung gebildet und die aktuellen
Zählerstände für die nächste Auswertung gespeichert.
Das klingt etwas kompliziert, ist aber problemlos umsetzbar.
Zum Jitter: Die Auslösung ist durch den µC-Takt begrenzt; sie liegt im
Bereich von ca. 70ns oder 1/14,7 MHz.
Der 2. Timer mit 1 ms macht nicht allzu viel Sinn, höchstens für die
anderen Messungen und da ist dann eine ISR nicht unbedingt der beste
Weg.
Die Frequenz kann man halt so nicht zu jedem Zeitpunkt messen, sondern
halt immer nur zum Ende einer Periode. Bzw. für ein Zeitfenster von z.B.
20 Perioden. Das gibt eine Zeitraster von etwa 50 µs - aufzeichnen
könnte man also etwa die Frequenz nach jeweils etwa 1 ms und die
dazugehörige Zeit. Ein genaues 1 ms Raster geht nicht so direkt - dafür
könnte man z.B. die Frequenz interpolieren um einen Wert für den
gewünschten Zeitpunkt zu bekommen. Es hängt auch davon ab wie genau die
Frequenz sein soll, und wie stark die sich ändert.
Als Alternative würde es auch schon reichen die Zeiten für jeweils 20
Perioden aufzuzeichnen. Die Gesamtzeit kann man sich dann einfach
aufaddieren.
Auch wenn die ISR erst mit Verzögerung aufgerufen wird sollten sich
keine kleinen Fehler ergeben. Der Wert im ICP Register ist unabhängig
von der Verzögerung beim Aufruf. Entweder man hat noch das richtigen
Wert, oder man verliert ganze Perioden und kriegt dann ganz falsche
Werte für z.B. die doppelte Periodenlänge.
m.n. schrieb:> Georg muß sich aber entscheiden, ob er das genaue 1ms Raster einhalten> möchte, mit entsprechend bescheidenen Ergebnissen, oder ob er die> Frequenz des Eingangssignals genau bestimmen will.
huch, da hab ich doch meinen Namen gelesen :-)
also, ich denke mit einer Verschiebung des kHz Takes um ein paar µs kann
ich leben, im Moment zeichne ich alle Pulse innerhalb eines Fenster auf
und messe die Zeitdifferenz von einem Zeitpunkt im ersten Fenster zu dem
gleichen Zeitpunkt im zweiten Fenster, das zusammengewurschtelt mit der
Anzahl der Pulse und der Taktfrquenz ergibt die echte Frequenz.
Ich könnte mir ebenfalls vorstellen, innerhalb des 1ms Fensters ein
Unterfenster zu definieren was dann natürlich Auflösung kostet, falls
diese zu knapp sein sollte, könnte man immernoch ein laufendes Mittel
über die fester n und n+1 und ... machen
Zur Karl-Heinz:
> Im Moment seh ich noch keinen Grund, warum das nicht gehen sollte.
Ich glaube, Du möchtest ein bisschen Code sehen stimmts?? Ich wird mal
versuchen, den Code aufs Nötigste zu reduzieren:
Hier die ISR des timer0
1
ISR(TIMER0_COMP_vect){// timer loop to acquire data, usually called once a ms
2
[...]
3
if(timer_program==4){
4
firstEdge=1;
5
uint16_tssi=getSSI(1);
6
uint16_tdin=getDIN();
7
uint16_tadc0=ADC_Read(0);
8
uint16_tadc1=ADC_Read(1);
9
uint16_tadc2=ADC_Read(2);
10
uint16_tadc3=ADC_Read(3);
11
//Only write the variables out, if the first trigger-condition is fullfilled, or it was fullfilled before
12
if(state==1||ssi>=threshold){
13
state=1;
14
uint8_tnVars=8;
15
//Save these variables to external memory
16
pBank_1[counter*nVars+0]=ssi;
17
pBank_1[counter*nVars+1]=din;
18
pBank_1[counter*nVars+2]=adc0;
19
pBank_1[counter*nVars+3]=adc1;
20
pBank_1[counter*nVars+4]=adc2;
21
pBank_1[counter*nVars+5]=adc3;
22
23
uint16_tintervals=0;
24
uint16_tfreq=0;
25
//On counter == 0 the frequency acquireing starts, counter == 1 is the first value, counter == 2 is the second value, so frequency can be calculated from the 2nd
Ulrich schrieb:> Auch wenn die ISR erst mit Verzögerung aufgerufen wird sollten sich> keine kleinen Fehler ergeben. Der Wert im ICP Register ist unabhängig> von der Verzögerung beim Aufruf. Entweder man hat noch das richtigen> Wert, oder man verliert ganze Perioden und kriegt dann ganz falsche> Werte für z.B. die doppelte Periodenlänge.
Und genau das verstehe ich nicht,
wird die andere ISR dann mit Verzögerung aufgerufen, oder gar nicht???
Schorsch
Georg T. schrieb:> Ich kann Zeit sparen indem ich das Auslesen der ADC und der SPI> Komponenten weglasse aber es reicht immer noch nicht
Das du da jetzt ein paar Capture Interrupts verpasst, wundert mich nicht
mehr.
Also, wirklich, beim Bit,
diese erste ISR ist das Paradebeispiel dafür wie man es nicht macht.
Reduziere die auf die absolut notwendigen Operationen. Streiche jeden
Befehl, den Du in main auslagern kannst, ohne das es prinzipiell ein
Problem gibt.
In einer anderen Welt werden Programmierer wegen sowas zum
Filterreinigen verurteilt. Aber lebenslänglich.
Georg T. schrieb:> ich hab ja schon fast damit gerechnet, dann sag mir mal bitte welchen> der Befehle ich nach main() auslagern soll
Fast alles.
Deine Timer0 ISR kann von mir aus noch den(die) letzten Capture wert(e)
sichern, aber der Rest wandert alles in die main.
die ISR setzt gerade noch ein Flag, welches dann in der Hauptschleife
dafür sorgt, dass die restliche Abarbeitung angestossen wird.
Und dann hoffen wir mal, dass das alles in 1ms überhaupt realisierbar
ist.
Georg T. schrieb:> huch, da hab ich doch meinen Namen gelesen :-)
Wenn das Kind schon einen Namen hat ... :-)
Sag doch besser einmal, was Du überhaupt vorhast. Welche Daten sollen
wie schnell erfaßt und verarbeitet werden. Im Prinzip kannst Du eine
Menge in eine 1ms Interrupt-Routine packen, wenn der ganze 'Kram' in der
Zeit auch verarbeitet werden kann.
Dabei ist es wichtig, die zeitkritischen Dinge zu erledigen, und dann
mit sei() global alle Interrupts wieder zuzulassen. Irgendetwas nach
main() zu verlagern, kann auch sehr ungünstig sein.
Im obigen Code sieht man in der ISR von T0 adc_read()-Aufrufe, von denen
man nicht weiß, wie schnell diese ausgeführt werden. Ferner finden dort
Berechungen von Indizes in der Form [counter * nVars + Konstante] statt,
wobei nicht klar ist ob es uint8_t, uint32_t oder float-Varibalen sind.
Geschickter wäre es, den Index einmal zu berechnen und dann nur noch zu
inkrementieren. Oder man nimmt gleich einen Zeiger (pointer).
Der sehr gewagte Aufruf von
uart_puts("DONE\r\n");
läßt hoffen, dass hier mit 115200Bd, oder besser noch mit TX-fifo
gearbeitet wird.
Was ich tun will..
Ich will mehrere Messgrößen analog und digital regelmäßig aufzeichnen.
Das hat soweit so gut auch immer funktioniert. Jetzt kam halt nur die
Forderung ebenfalls Frequenzen zu messen.
Bis auf die Frequenz ist das alles nicht wirklich zeitkritisch, was man
auch schon daran sieht, dass die 1ms nicht wirklich 1ms ist. Früher,
bevor ich den externen Speicher verwendet hatte, war das alles viel
schwieriger, weil der UART so langsam ist. Der Speicher wird hier
innerhalb von 1-2 Sekunden gefüllt und hat dann etwa 5 Sekunden zeit
ausgelesen zu werden. Das klappt sicher mit 115200Bd :-)
irgendetwas nach main zu verlagern....
so dachte ich bisher auch, vor allem tut es nicht unbedingt der
Lesbarkeit der Programmes gut. Aber ich bin auch davon ausgegangen, dass
die ISR wie ein Stack ineinander abgearbeitet werden.
Dieser Satz im Datasheet hat mich wohl verwirrt
> The interrupts have priority in accordance with their> Interrupt Vector position. The lower the Interrupt Vector address, the> higher the priority.
Nachwievor ist auch nicht die Frage beantwortet, ob und wann es zu
Verzögerungen oder Auslassungen von ISRs kommen kann.
Variablen Typen...
Ich verwende 16bit unsigned int (siehe obiges Beispiel)
das uart_puts....
... wird nur am Ende aufgerufen, da ist der Timer schon aus
ich werde mal morgen versuchen, ein Start-Flag in die ISR zu packen und
dann alles nach main zu verschieben
ein Frage habe ich aber doch noch (in dem Sinne meine ISRs zu
"entmüllen")
gibt es eine elegantere Methode um verschiedene Funktionalitäten in eine
ISR zu legen als
1
ISR(){
2
if(prog==1){
3
[...]
4
}elseif(prog==2){
5
[...]
6
}elseif[...]
7
8
}
würde es Sinn machen die ISRs mit funtion pointern umzubiegen?? macht
das Program auch nicht unbedingt lesbarer....
Danke im Voraus
Schorsch
Es kommt bei der ISR nicht so sehr auf die Zahl der C Befehle, sondern
auf die Laufzeit an. Problematisch ist hier das lesen des ADCs - dafür
braucht der AVR eine kleine Ewigkeit, so in der Größenordung 100 µs für
jeden Wert. Dagegen ist die UART mit 115 kBaud noch vergleichsweise
schnell.
Auch das schreiben in den externen Speicher könnte zu lange dauern.
Da sind auch noch ein paar Divisionen (%) drin, auch die sollte man
vermeiden, weil sie zu langsam werden. Einen Zyklischen Zähler für den
Ringpuffer macht man besser per runterzählen bis 0 und dann neu starten
bei der Länge-1. Der C-Code wird länger läuft aber viel schneller.
Bei geschätzten 400-500 µs Laufzeit für die ISR wird man nicht nur ein
ICP Envent verpassen, sondern eher so um die 10 und dann kann es ggf.
zufällig wieder etwa hinkommen mit der Zeit. Das es unter 15 kHz da noch
funktionierden soll kann ich mir aber nur schwer vorstellen. Da kann es
sogar schon fast passieren das µC nicht mal in der 1 ms fertig wird,
oder danach nicht mehr genug Zeit für 2 ICP Messungen hat.
Als minimale Änderung wäre ggf. das Freigeben von Interrupts in der ISR
eine Quick und Dirty Möglichkeit, aber halt nicht gerade schön und schon
etwas Fehleranfällig.
Sinnvoller wäre es vermutlich die Aufgabe auf mehr kleine ISRs zu
verteilen (z.B. ADC und ggf. einen anderen Timer für den externen
Speicher, ggf. auch SPI oder so), so dass jeder der Interrupts relativ
schnell fertig ist. Das Auslagern in Main und Abfrage eines Flags geht
auch - nur braucht man um das Flag zu setzen gar keine ISR mehr, das
macht die HW auch so schon.
Ulrich schrieb:> Als minimale Änderung wäre ggf. das Freigeben von Interrupts in der ISR> eine Quick und Dirty Möglichkeit, aber halt nicht gerade schön und schon> etwas Fehleranfällig.
Diese Lösung muß nicht unbedingt 'schmutzig' sein und kann vielleicht
zügig zu einem positiven Ergebnis führen. Allerdings ändert sich dadurch
nichts an den generellen Kritikpunkten des gezeigten
Programmausschnittes.
Also, probiere aus, gleich beim Eintritt in die ISR zunächst die Befehle
auszuführen, die nicht durch andere Interrupts gestört werden dürfen.
Anschließend wird die Interrupt-Freigabe nur für T0 gesperrt und mit
sei() werden neue Interrupts anderer Quellen zugelassen. Damit
verhindert man, dass die T0-ISR rekursiv aufgerufen werden kann.
Am Ende der ISR muß aber der T0-Interrupt wieder freigegeben werden.
Vielleicht kommst Du damit auf die erhofften 20kHz.