Forum: Mikrocontroller und Digitale Elektronik Zeitaufgelöste Frequenzmessung - Interuptprioritätsproblem?


von Georg T. (microschorsch)


Lesenswert?

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
1
TCCR1A = 0;
2
TCCR1B = 0;
3
TCCR1B |= (1 << ICES1); //triggering on rising edge
4
TCCR1B |= (1 << ICNC1); //Activate noise canceling
5
TCCR1B |= (1 << CS10); //No prescaler
6
TIMSK |= (1 << TICIE1); //Enable Input Capture Interrupt

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

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Peter II (Gast)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

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.

von Quarzolomäus (Gast)


Lesenswert?

>Glaube mir,

Glaub's bloß nicht, Karl Heinz.

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von m.n. (Gast)


Lesenswert?

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.

von Ulrich (Gast)


Lesenswert?

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.

von Georg T. (microschorsch)


Lesenswert?

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_t ssi = getSSI(1);
6
    uint16_t din = getDIN();
7
    uint16_t adc0 = ADC_Read(0);
8
    uint16_t adc1 = ADC_Read(1);
9
    uint16_t adc2 = ADC_Read(2);
10
    uint16_t adc3 = 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_t nVars = 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_t intervals = 0;
24
      uint16_t freq = 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
26
      if (counter > 1) {
27
      //Calculate and write out the frequency
28
      if (edgeTriggerTime[counter%INTERVALCOUNTER] >= edgeTriggerTime[(counter-1)%INTERVALCOUNTER]) {
29
        intervals = edgeTriggerTime[counter%INTERVALCOUNTER] - edgeTriggerTime[(counter-1)%INTERVALCOUNTER];
30
      } else {
31
        intervals = 0xffff - edgeTriggerTime[(counter-1)%INTERVALCOUNTER] + edgeTriggerTime[counter%INTERVALCOUNTER] +1;
32
      }
33
      freq = F_CPU*edges[(counter-1)%INTERVALCOUNTER]/intervals;
34
      }
35
      p_Bank_1[counter*nVars + 6] = freq;
36
      p_Bank_1[counter*nVars + 7] = edges[(counter-1)%INTERVALCOUNTER]; //For Debugging only
37
      counter++;
38
      if (counter > 1000/acq_freq * acq_time || (counter > 50 && ssi < threshold) ) { // End of acquisition
39
        //disable ms-timer
40
        TCCR0 = 0x00;
41
        //disable input-capture timer/interrupts
42
        //Disable the counter
43
        TCCR1B = 0;
44
        TIMSK &= ~(1 << TICIE1);
45
        TCCR1A = 0;
46
    
47
        write_out_from_mem(counter*nVars, nVars);
48
        uart_puts("DONE\r\n");  
49
        timer_program = 0;
50
        state = 0;
51
        counter = 0;
52
        firstEdge = 2;
53
      }
54
    }
55
  }
56
  } else [...]
57
}
..irgendwo hab ich eine } zuviel... sorry

Hier die ISR des timer1
1
ISR( TIMER1_CAPT_vect) { //timer loop to measure frequency at inout capture channel
2
  uint8_t thisIndex = (counter)%INTERVALCOUNTER;
3
  if (firstEdge < 2) {
4
    if (firstEdge == 1) {
5
    firstEdge = 0;
6
    edges[thisIndex] = 0;
7
  }
8
  if (edges[thisIndex] == 1) { //This is the second edge after the 1ms timer occured
9
    edgeTriggerTime[thisIndex] = TCNT1;
10
  }
11
  edges[thisIndex]++;
12
}
13
14
}

Ich kann Zeit sparen indem ich das Auslesen der ADC und der SPI 
Komponenten weglasse aber es reicht immer noch nicht

von Georg T. (microschorsch)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Quarzolomäus (Gast)


Lesenswert?

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.

von Georg T. (microschorsch)


Lesenswert?

ich hab ja schon fast damit gerechnet, dann sag mir mal bitte welchen 
der Befehle ich nach main() auslagern soll

Schorsch

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Georg T. (microschorsch)


Lesenswert?

in etwa so?

1
timer_ISR() {
2
  tuWas = 1;
3
  Counter++;
4
}
5
6
int(main) {
7
  tuWas = 0;
8
  for(;;) {
9
    if (tuWas ==1) {
10
      tuWas = 0;
11
      if (Counter < 1000)
12
        acquire(Counter);
13
      else
14
        write_out();
15
    }
16
  }
17
}

Schorsch

: Bearbeitet durch User
von m.n. (Gast)


Lesenswert?

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.

von Georg T. (microschorsch)


Lesenswert?

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
} else if (prog==2) {
5
[...]
6
} else if [...]
7
8
}

würde es Sinn machen die ISRs mit funtion pointern umzubiegen?? macht 
das Program auch nicht unbedingt lesbarer....

Danke im Voraus

Schorsch

von Ulrich (Gast)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

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.

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.