Forum: Mikrocontroller und Digitale Elektronik Frage zu: Input Capture zum abtasten von Frequenzen(Zeit)


von Johannes L. (johannes_l37)


Angehängte Dateien:

Lesenswert?

Guten Abend,
ich will mittels Input Capture, eine eingehende Frequenz abtasten, und 
messen wie lange sie auf 'HIGH' liegt.
Die Frequenz erzeugte ich mit einem Funktionsgenerator und überprüfe sie 
mit einem Oszilloskop.
Theoretich sollte ich als Ergebnis '80' haben (entspricht nach 
Umrechnunng 10ms)

allerdings bekomme ich solche Werte:
122
99
111
188
168
157
243
184
141
191
160
209
170
193
216
170
131
214
223
211
188
203
227
176
217
225
___
Ich denke das ich da etwas gewaltig falch mache in meinem Programm Code, 
denn vom programmieren hab ich noch nicht alzuviel Ahnung.
btw: ich benutze einen ATmega168, programmiere mit einer AVRISP mkII und 
nem int. RC Osc. mit 8MHz

von Karl H. (kbuchegg)


Lesenswert?

Hautpproblem dürfte sein, dass deine Interrupts zu lange gesperrt sind.

Vorschlag:
1
  while (1)
2
  {
3
  if(fertig==TRUE)//bereit zum senden
4
    {cli();
5
    char output[80];
6
7
8
    unsigned char DIFFERENZ;
9
    DIFFERENZ=dif(StartTime, EndTime);//EndTime - StartTime
10
    int len ;
11
    DIFFERENZ = (overflow*65536) + DIFFERENZ;
12
    sprintf(output ,"%d",DIFFERENZ);
13
            len = strlen(output);
14
15
          for (uint8_t i=0; i<len; i++)
16
              {
17
                  uart_putc ( output[i] );
18
              }
19
        uart_putc ( '\r' );
20
        uart_putc ( '\n' );
21
    overflow=0;
22
    fertig=0;
23
    _delay_ms(500);//Damit die Werte langsamer kommen
24
25
                              // ***********************************
26
    TIFR0 |= ( 1 << ICF1 );   // hier her und dafür aus der ISR raus
27
                              // ***********************************
28
29
    sei();
30
    }
31
  }

das sorgt dafür, dass nach dem sei nicht sofort noch irgendwelche 
pending Interrupt Requests feuern. Aber im Grunde ist der ganze Aufbau 
mit Berechnung, Ausgabe und delay in einem cli()/sei() Block schon Käse.

von Johannes L. (johannes_l37)


Lesenswert?

Karl Heinz Buchegger schrieb:

1
>     TIFR0 |= ( 1 << ICF1 );
2
>

Das ist TIFR1, (mein Fehler habs auch in der ISR korrigiert)

> das sorgt dafür, dass nach dem sei nicht sofort noch irgendwelche
> pending Interrupt Requests feuern. Aber im Grunde ist der ganze Aufbau
> mit Berechnung, Ausgabe und delay in einem cli()/sei() Block schon Käse.

Käse? Ich hab nicht viel Ahnung, aber als ich das ganze geschrieben 
hatte, dachte ich mir, cli(); sperrt die Interrupts, also zählt auch der 
Zähler nicht weiter. Dann in ruhe die Werte auswerten, starten und 
warten bis wieder eine Messung erfolgt ist.

Und das Delay muss nicht sein, ich kann es jederzeit rausnehmen, es 
ändert jedoch nichts an den kommenden Werten.

Die Werte sehen wie folgt aus, nachdem ich deinen Vorschlag intigriert 
habe:

113
184
87
107
116
94
28
166
96
102
82
122
89
78
92
149

von Ulrich (Gast)


Lesenswert?

Bis jetzt wird das Ergebnis auf 8 Bit abgeschnitten, als unsigned Char. 
Da kann man sich das Zählen der Überläufe erstmal sparen. So einfach wie 
hier gedacht geht das mit den overflows ohnehin nicht. Bis 2^16 
Zählschritten geht es auch erstmal ohne die extra overflows.


Mit dem cli() sperrt man nur den Aufruf der ISR - das Flag wird weiter 
gesetzt.

Der Befehl
TIFR0 |= ( 1 << ICF1 );

ist hinten und vorne Murks:
wie schon geschreiben ist es das Register TIFR1.  Beim AVR braucht man 
die meisten Interrupt flags auch nicht von Hand in der ISR löschen, das 
macht die Hardware schon beim Aufruf der ISR. Zum Löschen muss man nur 
eine 1 in das zu löschende Flag schreiben - der gezeigte Befehl schreibt 
überall eine 1 rein, wo schon eine 1 stand, und löscht damit alle 
anstehenden Interrupts. Das ist also eine sehr missverständliche 
Schreibweise.


Wenn überhaupt dann ein
TIFR1 = ( 1 << ICF1 );
im Hauptprogramm, da wo Karl Heinz Buchegger es vorgeschlagen hat. Wobei 
es da nicht so schlimm ist alle Interrupt flags zu löschen.

von Johannes L. (johannes_l37)


Lesenswert?

Ulrich schrieb:
>[...]
Das ist eine gute Erklärung, so habe ich mir das inzwischen auch 
gedacht.
Wobei: Ich übernehme ja den Start, und den Endwert zur rechten Zeit 
(Quasi beim betreten der Capt. ISR). Jetz habe ich die IRS etwas 
abgeändert:
1
ISR(TIMER1_CAPT_vect)
2
{
3
4
if(ErsteFlanke)
5
    {
6
    StartTime = ICR1 ;
7
    ErsteFlanke = FALSE ;
8
    TCCR1B &= ~(1<<ICES1);
9
    }
10
  else
11
    {
12
    EndTime = ICR1 ;
13
    ErsteFlanke = TRUE;
14
    fertig=TRUE;
15
    TCCR1B |= (1<<ICES1);
16
    cli();//HIER
17
    }
18
19
TIFR1 |= ( 1 << ICF1 );
20
}
Wie man hier sieht speere ich den Interrupts hiermit nach jeder 
vollständigen Messung, d.h. ich muss sie im Hauptprogramm erst wieder 
starten, bevor weiter Interrupts erfolgen können, und meine 
abgespeicherten Werte,  StartTime und EndTime, ändern sich nicht mehr, 
da nun 100%tig nicht mehr in die entsprechende ISR gesprungen werden 
kann, da ich mit Differenzen rechne ist es theoretich egal wenn der 
Zähler weiterzählt, da wir aber nichts risskieren wollen:
1
TCNT1H = 0x00;//Zähl register für Timer1
2
TCNT1L = 0x00;
Dies setze ich im Hauptprogramm vor das sei();

> Wenn überhaupt dann ein
> TIFR1 = ( 1 << ICF1 );
> im Hauptprogramm, da wo Karl Heinz Buchegger es vorgeschlagen hat. Wobei
> es da nicht so schlimm ist alle Interrupt flags zu löschen.
Das hatte ich dann nach dem Post von Karl Heinz Buchegger (kbuchegg), 
schon gemacht, nachdem ich alles was ich gerade erklärt habe angewandt 
habe

Werte:
7
241
185
223
252
227
4
16
20
237
4
252
218
2
74
46
241
248
10
21
203
57


Für mich leider immernoch keine ordentlichen Wert :/

von Karl H. (kbuchegg)


Lesenswert?

Ulrich schrieb:

> Mit dem cli() sperrt man nur den Aufruf der ISR - das Flag wird weiter
> gesetzt.

Genau das ist meiner Ansicht nach das Hauptproblem.

Johannes
Das ist deswegen Murks, weil du die Interrupts sperren kannst solange du 
willst, die Interrupt auslösenden Ereignisse treten ja weiter auf und 
werden vom µC auch registriert, der dann eben später (nach Freigabe der 
Interrupts) darauf reagiert.

Eine Analogie:
Was du machst ist: Du willst die Laufzeit von Skiläufern stoppen, 
schaust aber die halbe Zeit nicht hin, ob gerade ein Läufer startet 
(INterrupts gesperrt). Wenn du dann hinschaust (Interrupts freigibst) 
kommst du dann drauf, dass ein Läufer gestartet ist (das entsprechende 
Interrupt Flag ist bereits gesetzt) und du drückst noch schnell auf den 
Startknopf deiner Stoppuhr (die ISR wird aufgerufen).

Dass diese Zeiten nix aussagen, dürfte klar sein.

Daher: entweder du lässt diesen Läufer fahren und stoppst dann erst 
wieder beim nächsten, dessen Start du sicher zweifelsfrei gesehen hast 
(Das Interrupt Flag löschen) oder aber wegschauen ist mehr oder weniger 
verboten (Interrupts dürfen nicht so lange gesperrt bleiben) oder eine 
Kombination aus beidem.

>
> Der Befehl
> TIFR0 |= ( 1 << ICF1 );
>
> ist hinten und vorne Murks:

Ich hab ehrlich gesagt nicht kontrolliert, welches Flag er da wie löscht 
und habs einfach aus der ISR von ihm übernommen. Aber scheinbar muss man 
wirklich jede Kleinigkeit mit dem Datenblatt in der Hand überprüfen. 
Danke für den Catch.

von Johannes L. (johannes_l37)


Angehängte Dateien:

Lesenswert?

So, im Anhang nocheinmal die Datei (die etwas 'ausgebesserte')
Ich habe zum einen eure Vorschläge Berücksichtigt, keine cli/sei mehr, 
kein Delay mehr.

Zu der sache mit TIFR0, entschuldige ich mich nochmal, aber da ich 
selber mit dem Datenblatt nochmal drüber gegangen bin, ist es mir ja 
aufgefallen (siehe Post weiter oben.)
Die Sache mit Zählregister zurücksetzen haeb ich nun auch rausgenommen, 
da ich ja eh keine Interruptspeere mehr habe.
Nun da ich keine Interrupts mehr Sperre,

Edit:
Neue Werte:
249
13
213
187
154
195
168
170
72
149
117
179
194
169
182
187
184
186
170
188
163
203
165
167
197
200
189
196
228
180
__
Sehen nun schoneinmal etwas konstanter aus aber immernoch nicht richtig.

Edit 2: und etwas später:

152
147
86
112
154
162
176
183
134
140
104
127
116
69
119

von Karl H. (kbuchegg)


Lesenswert?

Johannes L. schrieb:
> So, im Anhang nocheinmal die Datei (die etwas 'ausgebesserte')
> Ich habe zum einen eure Vorschläge Berücksichtigt, keine cli/sei mehr,

Überhaupt keine ist ein Problem.

Du musst die Interrupts hier

    unsigned char DIFFERENZ;
    DIFFERENZ=dif(StartTime, EndTime);//EndTime - StartTime
    int len ;
    DIFFERENZ = (overflow*65536) + DIFFERENZ;

sperren, damit du sicher gehen kannst dass EndTime und StartTime aus 
derselben Messung stammen und nicht zwischendurch ein ISR AUfruf dir die 
Werte unter dem Hintern verändert (Später gleich mehr, warum man es 
trotzdem nicht braucht)

Ausserdem solltest du dein flag fertig auch innerhalb der ISR 
weiterbenutzen. Mittels fertig meldet die ISR an die Hauptschleife, dass 
sie Werte hat. Und umgekehrt kann auch die Hauptschleife auf diesem Weg 
wieder an die ISR signalisieren: OK, ich hab mir die Werte geholt, du 
kannst wieder neu messen


   if( fertig ) {

      Werte holen

      Umrechnen

      Ausgeben

      Alles klar soweit, fertig auf 0 setzen
   }



ISR( .... )
{
  if( fertig )      // die Hauptschleife hat noch kein OK für eine
    return;         // neue Messung gegeben

  if( steigende Flanke )

   ...

  else if( fallende Flanke ) {
    ...

    fertig = 1;    // eine neue Messung ist fertig
  }
}


In dem Fall darfst du dann sogar ohne Interrupt Sperre auf die Messwerte 
zugreifen, weil sichergestellt ist, dass sie von der ISR nicht verändert 
werden. Warum? Weil die Hauptschleife noch keine Freigabe dafür gegeben 
hat.

Und zum Feststellen welche Flanke: Die bessere Lösung ist es, einfach am 
Pin nachzusehen, ob der 1 oder 0 ist, anstatt sich auf deine Zählung zu 
verlassen. Dir braucht nur 1 Flanke als nicht erkannt flöten zu gehen 
und du ordnest die Dinge falsch zu. PLötzlich misst du nicht mehr den 
Puls, sondern die Pause zwischen den Pulsen.

Schön langsam nähern wir uns dem Verständnis des Problems und seiner 
Lösung :-)

von Karl H. (kbuchegg)


Lesenswert?

ZU deinem Programm:

Du darfst nicht alles umdrehen. Die Helfer hier versuchen aus deinem 
vorhandenen Programm etwas zu machen. Die Hilfestellung ist daran 
angelehnt wie dein Programm aussieht. Wenn du es umdrehst, dann greift 
die Hilfestellung nicht mehr.

    overflow=0;
    fertig=0;
    //TIFR1 |= ( 1 << ICF1 );//Muss nicht mehr, weill kein cli/sei mehr
    }


No. Das wäre immer noch notwendig. Dafür ist es in der ISR sinnlos. Denn 
da löscht sowieso die Hardware das Flag.

von Karl H. (kbuchegg)


Lesenswert?

Ach und noch was.
Ulrich hat es ja schon angesprochen.


Rechne dir aus, ob bei deinen Zeiten eine Timer-Differenz größer als 
65535 rauskommt. Wenn ja, stell den Vorteiler höher.
Und dann wirf die Overflow-Behandlung erstmal raus. Die ist nämlich 
falsch.

von Johannes L. (johannes_l37)


Lesenswert?

Karl Heinz Buchegger schrieb:
>     unsigned char DIFFERENZ;
>     DIFFERENZ=dif(StartTime, EndTime);//EndTime - StartTime
>     int len ;
>     DIFFERENZ = (overflow*65536) + DIFFERENZ;
>
> sperren, damit du sicher gehen kannst dass EndTime und StartTime aus
> derselben Messung stammen und nicht zwischendurch ein ISR AUfruf dir die
> Werte unter dem Hintern verändert.
Deswegen wollte ich anfangs ja die Interrupts beim verlassen der Inpt. 
ISR sperren, denn auch wenn ich die Interrupts direckt vor diesem 
Abschnitt speere, kann es ja sein das ich direckt nach dem übernehmen 
der Starttime (eines anderen Wertes) in die Auswertung kann.


>    if( fertig ) {
>
>       Werte holen
>
>       Umrechnen
>
>       Ausgeben
>
>       Alles klar soweit, fertig auf 0 setzen
>    }
>
>
>
> ISR( .... )
> {
>   if( fertig )      // die Hauptschleife hat noch kein OK für eine
>     return;         // neue Messung gegeben
>
>   if( steigende Flanke )
>
>    ...
>
>   else if( fallende Flanke ) {
>     ...
>
>     fertig = 1;    // eine neue Messung ist fertig
>   }
> }
>
>
> In dem Fall darfst du dann sogar ohne Interrupt Sperre auf die Messwerte
> zugreifen, weil sichergestellt ist, dass sie von der ISR nicht verändert
> werden. Warum? Weil die Hauptschleife noch keine Freigabe dafür gegeben
> hat.
>
> Schön langsam nähern wir uns dem Verständnis des Problems und seiner
> Lösung :-)
Ah, das hört sich interessant an,
habe nund das
1
if(fertig)
2
return;
in die Capt. ISR eingefügt, und mir die Werte erneut angeschaut die nun 
rauskammen.

126
102
104
120
58
61
107
98
143
135
112
41
39
45
35
63
46
35
75
68
20
116

____________
Das TIFR1 |= (1<<ICF1); //Sitzt noch /wieder im Hauptrogramm

>Und zum Feststellen welche Flanke: Die bessere Lösung ist es, einfach am
>Pin nachzusehen, ob der 1 oder 0 ist, anstatt sich auf deine Zählung zu
>verlassen. Dir braucht nur 1 Flanke als nicht erkannt flöten zu gehen
>und du ordnest die Dinge falsch zu. PLötzlich misst du nicht mehr den
>Puls, sondern die Pause zwischen den Pulsen.
1
if(PINC &(1<<PINC2)) //ist PINB PINB0 = 1?//wenn ich mich jetz nicht vertu,
2
    {
3
    //????
4
    }
5
else if(!(PINC &(1<<PINC2))) 
6
    {
7
    //???
8
    }
Das verstehe ich nicht genau, ich soll in der ISR(?) überprüfen ob der 
entsprechende PIN, schon auf Low/High ist.
da verstehe ich den entsprechenden Sinn nicht, denn ich springe ja nur 
in die ISR, wenn der PIN den entsprechenden WERT hat, das erste mal 
springe ich in die Capt. ISR, sobald der Pin High ist, speicher den Wert 
und gehe da wieder raus, das zweite mal springe ich in die Capt. ISR 
sobald der Pin auf LOW liegt. Das es das zweite mal ist, springe ich 
dann else hinein, speicher den Endwert, und ab da kann dan nichts mehr 
in Start/endwert geschrieben werden, denn durch das if(fertig){return;}.
Kommt er erst wieder in der ISR weiter, sobald ich 'fertig' wieder auf 0 
setze.

Wieso soll ich nun irgendwo überprüfen ob der Pin auf High/low ist? 
*leicht verwirrt.*

von Karl H. (kbuchegg)


Lesenswert?

Johannes L. schrieb:

> Das verstehe ich nicht genau, ich soll in der ISR(?) überprüfen ob der
> entsprechende PIN, schon auf Low/High ist.

Da hab ich mich vertan.
Sorry für die Konfusion. Mein Fehler. Deine Variable ersteFlanke steht 
ja sowieso immer richtig, weil sie von der ISR selber gestellt wird.
Ich hab das falsch gelesen.

von Johannes L. (johannes_l37)


Lesenswert?

Aus mehr oder minder Testzwecken habe ich die Frequenz auf 500mHz 
gestellt, was einer Highzeit von 1.00S entspricht, meine Werte sehen 
immernoch nur nach Müll aus
152
81
204
150
110
173
4
und ich meine, bei einer Sekunde Zeit, zwischen Messung 1 und Messung 
zwei, sollte er sich ja wohl nicht zwischen den verschiedenen Perioden 
vertuen.

>Deine Variable ersteFlanke steht
>ja sowieso immer richtig, weil sie von der ISR selber gestellt wird.
>Ich hab das falsch gelesen.
Uff, war ganz schön verwirrt xD

von Karl H. (kbuchegg)


Lesenswert?

unsigned char DIFFERENZ;

    DIFFERENZ=dif(StartTime, EndTime);//EndTime - StartTime
    int len ;
    DIFFERENZ = (overflow*65536) + DIFFERENZ;


unsigned char für die DIFFERENZ. Und dann noch im Fall des Falles 65536 
für einen Overflow dazu?

    int DIFFERENZ = EndTime - StartTime;

und die Overflows lass erst mal weg. So einfach wie das da gemacht ist, 
geht das nicht.

von Stefan E. (sternst)


Lesenswert?

Das du ständig unsinnige Werte bekommst, liegt auch daran, dass du die 
Differenz auf 8 Bit begrenzt, das Ergebnis da aber nie rein passt. Denn 
schon deine Annahme über die Ausgangslage ist falsch:

Johannes L. schrieb:
> Theoretich sollte ich als Ergebnis '80' haben (entspricht nach
> Umrechnunng 10ms)

Nein, 80 wären 10 µs (Micro, nicht Milli).

Johannes L. schrieb:
> Aus mehr oder minder Testzwecken habe ich die Frequenz auf 500mHz
> gestellt, was einer Highzeit von 1.00S entspricht, meine Werte sehen
> immernoch nur nach Müll aus

Und das ist nun vollends Unsinn.

von Johannes L. (johannes_l37)


Lesenswert?

Karl Heinz Buchegger schrieb:
> unsigned char DIFFERENZ;
>
>     DIFFERENZ=dif(StartTime, EndTime);//EndTime - StartTime
>     int len ;
>     DIFFERENZ = (overflow*65536) + DIFFERENZ;
>
>
> unsigned char für die DIFFERENZ. Und dann noch im Fall des Falles 65536
> für einen Overflow dazu?
>
>     int DIFFERENZ = EndTime - StartTime;
>
> und die Overflows lass erst mal weg. So einfach wie das da gemacht ist,
> geht das nicht.
1
    int DIFFERENZ;
2
    DIFFERENZ=dif(StartTime, EndTime);
3
    DIFFERENZ=DIFFERENZ*125000/1033;//Beginn der Umrechnung.

Es Funktioniert, ich bin mir noch nicht ganz genau sicher, warum es 
gerade deswegen so große ungenauigkeiten gab O.o aber die neuen 
Messwerte:

(Hab das Programm ein wenig abgeändert, siehe Anhang, nun wird schon 
schön umgerechnet, Korrektur faktor... etc)

Werte:
9922ns
9922ns
9922ns
9922ns
9922ns
10us
10us
10us und das bei  ziemlich exakt 100us, find ich gut! (Frequenz 50kHz)
bei 500kHz wirds wieder brei aber egal! bei 5kHz bekomme ich Minuswerte:
-30879ns
-30879ns
-31000ns
-30879ns
-31000ns
-31121ns
__
Sieht doch glatt nach nem Overflow aus und das verlangt ja wohl nach nem 
Presc.

Danke dir :*

von Johannes L. (johannes_l37)


Angehängte Dateien:

Lesenswert?

So und hier noch das Funktionierende Programm, (Presc. is da abern och 
nicht mit einberechnet. Ich denke da ansowas wie.
if DIFFERENZ<=0 (Minuswerte)
Dann Presc. einstellen.
und dann mal schaun wie ich es mache das der Presc. auch wieder 
abschaltbar ist, aber ich denk mir da bestimmt noch was aus :-)

von Ulrich (Gast)


Lesenswert?

So ganz richtig funktionieren kann das immer noch nicht. Die variablen 
DIFFERENZ ist nur als Int deklariert. Da gibt es mit Zahlen wie 125000 
garantiert einen überlauf. Und der Vergleich mit 1000000 ist auch nicht 
sinnvoll.

Am Ende der ISR ist auch immer noch der unnötige und falsche Versuch das 
Interrupt Flag zu löschen.  Da hier nur ein Interrupt genutzt wird 
passiert nichts - aber wenn man sich das später noch mal ansieht und 
ggf. Code kopiert, kann man so herrlich versteckte Fehler kopieren.

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.