Forum: Compiler & IDEs Am Input Capture Pin (ICP) Frequenz messen


von Karl N. (karlnapf)


Angehängte Dateien:

Lesenswert?

Hallo zusammen!

Ich versuche über den Input Capture Pin eine Frequenz zu messen.
Der Code im Anhang gibt aber unterschiedliche Ergebnisse aus, wobei 
manchmal auch das richtige dabei ist.
Ich benutze einen Atmega16 mit 8MHz intern und Prescaler 256 im CTC 4 
Mode. Bei einer Frequenz von 50 Hz an ICP1 lasse ich den Timer bis 3125 
laufen, was 100ms entspricht. Somit findet maximal ein Compare Interrupt 
pro Messung statt.

Wo liegt mein Fehler?

von Karl H. (kbuchegg)


Lesenswert?

1
  if(ic_zp_B > ic_zp_A)    //kein Compare Interrupt zwischen beiden Werten

Ich denke nicht das das stimmt. Wenn du schon darauf aus bist,
festzustellen ob eine Overflow (alias Compare) stattgefunden
hat, warum nimmst du dann nicht einfach deine Compare Variable
dafür her?

Deine Berechnung (und auch die Anzeige updaten) solltest du nur
dann machen, wenn eine Messung komplett vollständig abgelaufen
ist. Setz dir doch einfach eine 2.te globale Variable, wenn
du den 2.ten Puls detektierst. In der Hauptschleife fragst
du ständig diese Variable ab und nur wenn die anzeigt, dass
neue Messwerte vorliegen, wird die Berechnung durchgeführt
und die Anzeige erneuert. Jetzt ist es so, dass deine
Berechnung manchmal einen alten B-Wert mit einem neuen A-Wert
verrechnet.

von Karl H. (kbuchegg)


Lesenswert?

Also ungefähr so:
1
...
2
volatile unsigned char UpdateDisplay;
3
4
SIGNAL(SIG_INPUT_CAPTURE1)
5
{
6
  i++;
7
  if (i == 1)    // 1.High Flanke
8
  {
9
    ic_zp_A = ICR1;
10
  }
11
  if (i == 2)    // 2.High Flanke
12
  {
13
    ic_zp_B = ICR1;
14
    i = 0;    
15
    compare = 0;
16
        // Messung ist fertig, Display kann neu geschrieben werden
17
    UpdateDisplay = 1;
18
  }
19
}
20
21
SIGNAL(SIG_OUTPUT_COMPARE1A)
22
{
23
  compare++;
24
}
25
26
int main(void)
27
{
28
  initLcd();
29
30
  UpdateDisplay = 0;
31
32
  sei();
33
  TCCR1B =  (1<<ICES1)  | (1<<CS12) | (1<<WGM12); // Input Capture Edge, PreScale 256, CTC 4 Modus
34
  TIMSK = (1<<TICIE1) | (1<<OCIE1A);  // Interrupts akivieren, Capture + Compare
35
  OCR1A = 3125;  // 100ms pro compare Interrupt, Test-Frequenz an ICP = 50Hz
36
  DDRD = 0x00;
37
38
39
  while(1)
40
  {
41
    if( UpdateDisplay == 1 )  // liegt eine neue Messung vor ?
42
    {
43
      Erg = ((compare*3125) - ic_zp_A + ic_zp_B);
44
    
45
      itoa(Erg, lcdCounterString, 10);
46
47
      clearLcd();
48
      setLcdCursorPos(LCD_LINE1);      //Ausgabe LCD
49
      writeStringToLcd(lcdCounterString);  
50
51
                             // Display ist wieder up to date
52
                             // warte auf die nächste Messung
53
      UpdateDisplay = 0;
54
    }
55
  }
56
}

Das hat dann auch den Vorteil, dass du keine Warteschleifen
mehr brauchst und die Hauptschleife nur dann durch die teure
Berechnung und den LCD Code geht, wenn es auch wirklich was
zu tun gibt.

von Karl H. (kbuchegg)


Lesenswert?

> mit 8MHz intern

sonderlich genau wirds damit aber so und so nicht werden.

von Mister mit Kanister (Gast)


Lesenswert?

Seh ich das richtig, Du misst (schreibt man das so, oder mit 'ß' ??) 
einfacah nur die Zeit zwischen 2 Flanken mit dem Compare Interrupt? Dann 
begreife ich die Timereinstellung für die 100ms nicht.

von johnny.m (Gast)


Lesenswert?

Ich verstehe nicht ganz, warum Du "compare" nach der zweiten Flanke 
löschst, noch bevor Du die Messung auswertest. Ich denke, Du benötigst 
den Wert für die Berechnung. Dann solltest Du ihn erst nach der 
Berechnung löschen. Ansonsten schließe ich mich Karl Heinz an: Warte 
zwei Flanken ab, setze ein Jobflag in der Capture-ISR und werte dieses 
Flag im Hauptprogramm aus.

BTW:
SIGNAL ist veraltet. Installiere Dir bei Gelegenheit mal eine aktuelle 
WINAVR-Version bzw. wenn Du das schon getan haben solltest, verwende ISR 
anstelle von SIGNAL.

von johnny.m (Gast)


Lesenswert?

@Mister mit Kanister:
Nein, er misst die Zeit mit dem *Capture*-Interrupt. Der 
Compare-Interrupt zählt nur 100ms-Blöcke. Das erleichtert hinterher die 
Umrechnung in Sekunden bzw. in Hz. Man kann aber auch die Overflows 
zählen. Im Ergebnis kommts aufs gleiche raus, man muss nur ein bisschen 
mehr mit krummen Zahlen rechnen...

von Karl N. (karlnapf)


Lesenswert?

> Ich hab die Änderungen jetzt mal übernommen. Der Wert stimmt
> jetzt. Das heißt wärend meiner delay funktion fürs Display
> kommen falsche Werte für ICR1 zu stande?

Stell dir einfach mal vor, dein ursprüngliches Programm steht
zur Zeit gerade in der Berechnung:

      Erg = ((compare*3125) - ic_zp_A + ic_zp_B);

Es rechnet munter dahin
   compare * 3125

und Zack, jetzt kommt ein Input Capture Interrupt. Dadurch
wird ic_zp_A auf einen neuen Wert gesetzt. Der Interrupt
ist fertig und es geht zurück zu der Berechnung. Die
rechnet jetzt weiter

   Vorhergehendes Ergebnis der Multiplikation
   minus ic_zp_A

Aber das ist schon der neue Wert für ic_zp_A! Eine neue
Messung hat begonnen.

Und weiter

   plus ic_zp_B

das ist aber der alte Wert von ic_zp_B, also der der vorhergehenden
Messung. Die Werte von ic_zp_A und ic_zp_B haben nichts miteinander
zu tun. Und trotzdem werden sie miteinander verrechnet. Da kann
nichts vernünftiges rauskommen.


Am besten das Display immer ohne delay funtion anschauen?
Wie meinst du das genau mit der "compare" Variablen. Muss ich überhaupt 
schauen ob ein Overflow stattgefunden hat?

> Am besten das Display immer ohne delay funtion anschauen?

Am besten ohne delay programmieren. Das Schema, das ich dir gezeigt
habe ist universell verwendbar. Da brauchts keinen delay.

> Muss ich überhaupt schauen ob ein Overflow stattgefunden hat?

Solange es unmöglich ist, dass 2 Overflows in einem Messzyklus
passieren, brauchst du es nicht berücksichtigen.

Übrigens: Johnny hat recht. Das mit dem Compare würde so
nicht 100% funktionieren. Selbes Problem gibt es übrigens
mit den beiden Timer Messwerten. Wenn der nächste 1-Flanke
Interrupt kommt, noch bevor die Hauptschleife die Berechnung
zu Ende gebracht hat, hast du wieder eine falsche Anzeige.

Man könnte das so umgehen:
1
SIGNAL(SIG_INPUT_CAPTURE1)
2
{
3
  if( UpdateDisplay == 1 )   // wenn die vorhergehende Messung
4
                             // noch nicht ausgewertet wurde
5
                             // machen wir nichts
6
    return;
7
8
  if (i == 0)    // 1.High Flanke
9
  {
10
    ic_zp_A = ICR1;
11
    compare = 0;
12
    i = 1;
13
  }
14
  else {        // 2.High Flanke
15
  {
16
    ic_zp_B = ICR1;
17
        // Messung ist fertig, Display kann neu geschrieben werden
18
    UpdateDisplay = 1;
19
    i = 0;
20
  }
21
}

Schau dir auch an, was ich mit dem i gemacht habe.
Das vermeidet unnötige Berechnungen.



von Mister mit Kanister (Gast)


Lesenswert?

Den Overflow brauchst Du ja wirklich nicht.

Ich würde einfach die Zeit zwischen 2 Captueres messen. Dann hast Du ja 
eine  Periode. Da bei Dir F_CPU >> F-mess wird das ganze sicher ziemlich 
genau.


Frequenz = F_CPU / Timerticks_zwischen_2_Captueres

Ich würde der Übersicht halber bei der Deklaration noch unsigned int 
verwenden.

von Karl N. (karlnapf)


Lesenswert?

Falls aber für den unwahrscheinlichen Fall der Timer bei der 1.Flanke 
kurz vor OCR1 ist und die zweit Flanke erst nach dem compare Ereignis 
auftritt, bekomm ich doch falsche Werte?
Das ist hier wahrscheinlich selten der Fall, aber wenn zusätzlich 
kleinere Frequenzen auftreten wird's problematisch, oder?

von Rahul, der Trollige (Gast)


Lesenswert?

>Falls aber für den unwahrscheinlichen Fall der Timer bei der 1.Flanke
>kurz vor OCR1 ist und die zweit Flanke erst nach dem compare Ereignis
>auftritt, bekomm ich doch falsche Werte?

1. ICR wird erst bei der nächsten Flanke verändert, solange hat man 
Zeit, das Register auszulesen.

2. bei einem ICP ohne die Begrenzung durch OCR1 hat man ja den Vorteil, 
dass der Zahlenraum aufgrund der 16Bit begrenzt ist. Dadurch kommt es 
bei einer Subtraktion automatisch zum richtigen Ergebnis, solange es nur 
einen Überlauf gab.
In deinem Fall muß zum zweiten Wert OCR1 (oder war es die Differenz zu 
0xFFFF?) hinzuaddiert werden, wenn er kleiner als der erste ist. Er 
befindet sich dann ja im"unsichtbaren" Intervall des Timers.

Es müsste OCR1 zu addieren sein...

von Karl N. (karlnapf)


Lesenswert?

Es scheint zu funktionieren!
Vielen Dank an alle!
Was kann ich tun um die Genauigkeit zu erhöhen, denn jetzt habe ich 2Hz 
Abweichung zu meinem Funktionsgenerator?

von Rahul, der Trollige (Gast)


Lesenswert?

>2Hz Abweichung

Bei welcher Frequenz?

Um Karl Heinz zu zitieren:
>> mit 8MHz intern
>sonderlich genau wirds damit aber so und so nicht werden.

Nimm einen externen Quarz. (ich weiß jetzt nicht, ob du dich 
diesbezüglich schon geäussert hast).

von Karl N. (karlnapf)


Lesenswert?

Die 2Hz Abweichung sind bei 50 Hz.
Ich muss Signale aufnehmen können (Drehzahlsensor) von (o,5 Umdr. pro 
Sec.) 360 Hz bis (3000 Umdr. pro Sec.) 36kHz.
Bei einem schnelleren Quarz läuf doch der Counter schneller hoch. Warum 
wird dadurch mein Ergebnis genauer?
Oder meinst du keinen schnelleren sondern einen externen da der genauer 
ist?

von Karl H. (kbuchegg)


Lesenswert?

Sagen wir mal so.
Wenn du mit einer Sanduhr einen 100 Meter Läufer stoppst,
wirst du auch jedesmal ein anderes Ergebnis kriegen.

Dein 8Mhz interner Takt macht alles Mögliche, nur
nicht möglichst exakt 8 Mhz. Und das tut er im Sommer
anders als im Winter (da andere Temperatur, Luftdruck,
Wasserstand etc.)

von Karl H. (kbuchegg)


Lesenswert?

> 3000 Umdr. pro Sec

Bist du dir da sicher?
Das sind 180.000,0 U/min
Nicht mal Turbinen drehen so hoch (AFAIK).

von Karl H. (kbuchegg)


Lesenswert?

Falls das noch nicht klar geworden ist:
Im AVR ist kein Quarz. Da sitzt ein normaler R-C
Schwingkreis. Der schwingt so lala, wie's ihm gerade
gefällt. Für Dinge bei denen es aufs Timing nicht
ankommt ist das kein Problem.

von johnny.m (Gast)


Lesenswert?

Du hast ja auch den Prescaler vom Timer auf 256 stehen. Du brauchst 
keinen schnelleren CPU-Takt, sondern einen schnelleren Timer-Takt. Und 
den gibts, wenn Du den Prescaler runtersetzt.

von Karl N. (karlnapf)


Lesenswert?

Pardon, natürlich 3000 Umd. pro min sind dann aber trotzdem 36kHz am 
Controller.
Also mit Prescaler = 256 (31,25kHz) wird der 16-bit-Timer in 2,098s 
hochgezählt.
Ohne Prescaler mit 8MHz in 8,195ms hochgezählt.
Das heißt die Skala ist feiner unterteilt und damit genauer.
Dann kann man den Zeitpunkt wenn eine High-Flanke kommt genauer 
bestimmen.

Bei einem 8-bit-Timer sind ist dann die Schrittdauer (bei 8MHz - 125ns 
pro Schritt) gleichgroß wie bei einem 16-bit-Timer? Also ist der 
16bit-Timer nicht genauer? Nur beim 8-bit-Timer hab ich event. mehr 
overflows?
Habt ihr mir ein paar Tipps wie ich für meine Anwendung die sinnvollste 
Einstellung wähle?


von johnny.m (Gast)


Lesenswert?

> Nur beim 8-bit-Timer hab ich event. mehr overflows?
Jau, beim 8-Bit-Timer haste bei gleicher Prescaler-Einstellung im selben 
Zeitraum 256-mal mehr Overflows... Bei Deiner aktuellen Einstellung mit 
Prescaler auf 256 wird der Timer alle 32 µs um eins erhöht. Die 32 µs 
sind dann gleichzeitig Deine maximale Auflösung, wenn Du immer eine 
Periode misst. Bei 50 Hz ist das eine Abweichung von 1,6 Promille 
(Hicks!). Welche Einstellung für Deine Anwendung die beste ist, kann man 
nur sagen, wenn man weiß, in welchem Bereich die zu messenden Frequenzen 
liegen sollen und welche Anforderungen Du generell an die Auflösung 
stellst. Dass Du aber mit den o.g. Einstellungen Abweichungen von 4 % 
hast, ist für mich nicht ganz nachvollziehbar, es sei denn, in Deinem 
Programm sind noch irgendwelche Schwachstellen drin...

von Karl H. (kbuchegg)


Lesenswert?

> Dass Du aber mit den o.g. Einstellungen Abweichungen von 4 %
> hast, ist für mich nicht ganz nachvollziehbar

Er benutzt den internen Taktgeber.
<4% bei einem RC-Oszillator ist doch nicht so schlecht.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kalibriert (d. h. bei Tamb = 25 °C und Vcc = 5 V) sollte der < 1 %
in der Abweichung liegen.

von johnny.m (Gast)


Lesenswert?

@Karl Heinz:
> Er benutzt den internen Taktgeber.
Upps, hatte ich jetzt übersehen...

von Karl N. (karlnapf)


Angehängte Dateien:

Lesenswert?

Hab jetzt einen externen 4MHz Quarz in Betrieb und umgestellt auf den 
Overflow Interrupt ohne Prescaler.
Das liefert mir ziemlich exakte Werte für hohe Frequenzen (5kHz, danach 
macht das Display nicht mehr mit), alles was aber kleiner als ca. 62 Hz 
ist, wird nicht mehr erkannt.
Ist da noch ein Fehler im Code, z.B. wird die overflow Variable richtig 
gezählt. Bei so niedrigen Frequenzen finden ja mehrere Überläufe statt.

von Karl H. (kbuchegg)


Lesenswert?

1
  zw_Erg = ((compare*65536) + ic_zp_B - ic_zp_A);  // Compare ausgelöst, ("Überlauf")
2
  Erg = 1/(zw_Erg*0.00000025);    //4MHz Quarz

zw_Erg wird beim ersten Overflow Überlaufen.
In einen unsigned int passen keine höheren Zahlen
als 65536. Mach da mal unsigned long draus.

Lass dir auch mal zw_Erg ausgeben.
Bei der Berechnung von Erg dividierst du
relativ grosse Zahlen durch sehr kleine Zahlen
in float -> nicht gut.

Rechne es lieber so:

  Erg = 4000000 / zw_Erg;

(Ich hab einfach nur den Doppelbruch aufgelöst).

von Karl H. (kbuchegg)


Lesenswert?

Karl heinz Buchegger wrote:
>
1
>   zw_Erg = ((compare*65536) + ic_zp_B - ic_zp_A);  // Compare ausgelöst,
2
>   Erg = 1/(zw_Erg*0.00000025);    //4MHz Quarz
3
>
>
> zw_Erg wird beim ersten Overflow Überlaufen.
> In einen unsigned int passen keine höheren Zahlen
> als 65536. Mach da mal unsigned long draus.

Da wollte ich ja noch was dazu schreiben. Bin
unterbrochen worden.

Damit unsigned long aber auch greift, muss klarerweise
auch die Multiplikation unsigned long sein:
1
  zw_Erg = ((compare*65536UL) + ic_zp_B - ic_zp_A);  // Compare ausgelöst, ("Überlauf")

  

von Karl N. (karlnapf)


Lesenswert?

Wenn ich zw_Erg bei niedrigen Frequenzen anzeigen lasse ist es negativ!?
Erg hingegen stimmt!? Warum den das?

Ab einer Freqeunz von 32kHz wird auch nichts sinnvolles mehr angezeigt. 
Da ist zw_Erg dann auf einem sehr kleinen Wert ~120, könnte es sein dass 
dann einfach nicht mehr geht?

von Karl H. (kbuchegg)


Lesenswert?

> Wenn ich zw_Erg bei niedrigen Frequenzen anzeigen lasse ist es negativ!?
> Erg hingegen stimmt!? Warum den das?

Weil du die falsche Ausgabefunktion benutzt. Eine unsigned
Zahl kann per Definition nicht negativ sein. Wenn du allerdings
das Bitmuster in eine Funktion stopfst, die von signed ausgeht
dann interpretiert diese eine sehr grosse Zahl als negativ, weil
das am weitesten links stehende Bit als Vorzeichenbit auffasst.
Ist es gesetzt, dann ist die Zahl negativ.

> Da ist zw_Erg dann auf einem sehr kleinen Wert ~120, könnte es sein
> dass dann einfach nicht mehr geht?

Nein. 4000000 dividiert durch ~120 ergibt immer noch was sinnvolles.
Da die Grenze die du nennst ungefähr 32kHz ist, wirst du wieder
dasselbe Problem haben. Beschäftige dich mal damimt, welche
Zahlenbereiche die einzelnen Datentypen abdecken können:

int            -32768  bis  32767
unsigned int    0 bis 65536
long           -2147483648 bis 2147483648
unsigned long   0 bis 4294967296

Wann immer du in einer Berechnung über diese Grenzen drüber
kommst, bist du in Schwierigkeiten. Auch bei Zwischenergebnissen,
auch dann wenn du die falsche Ausgabefunktion nimmst.

Und float ist oft keine gute Wahl. Besonders dann, wenn in
einer Berechnung viele Größenordnungen Unterschied zwischen
den beteiligten Zahlen herrschen.

von Karl N. (karlnapf)


Angehängte Dateien:

Lesenswert?

Hab deine Tipps umgesetzt. Unter anderem "ultoa" anstelle von "itoa", 
volatile vor "compare" ist wahrscheinlich auch besser und aus den int 
variablen hab ich unsigned long gemacht, siehe Anhang.

Wenn ich aber kein delay_ms() vor mein clearLcd() schreib kann ich das 
Display nicht ablesen. Muss das so sein, oder kann man das umgehen. 
Ansonsten stimmen die Werte. Vielen Dank für die Geduld, wenns wirklich 
läuft stell ich's dann in die Codesammlung.

von Karl H. (kbuchegg)


Lesenswert?

Gehs noch mal in Gedanken durch:

Wann musst du das Display löschen (wenns schon
unbedingt löschen sein muss).

Doch wohl nur dann, wenn ein neuer Wert ausgegeben
werden soll. Dann wäre es aber gut, wenn die Reihenfolge
wäre
* zuerst Display löschen
* dann neuen Wert hinschreiben

und nicht umgekehrt

Studier noch mal mein Posting vom 6.2 13:53
und schau dir dort die Reihenfolge an:

Zuerst wird der neue anzuzeigende Wert komplett (wenn
auch falsch) berechnet und als String zurecht gelegt.
Das Display zeigt in dieser Zeit immer noch den alten
Wert an.
Dann wird das Display gelöscht, der Cursor positioniert
und der neu errechnete Wert hingeschrieben. Auf diese
Weise ist die Zeit in der das Display nichts anzeigt
auf ein Minimum gedrückt.

Und da das ganze nur dann passiert, wenn (dank UpdateDisplay)
ein neuer Messwert vorliegt, flackert das Display auch nur
ganz kurz wenn ein neuer Wert auf das Display gezaubert wird.
Das geht aber so schnell, dass man es kaum sehen wird.

von Karl N. (karlnapf)


Lesenswert?

So jetzt passt's. Schwere Geburt. Vielen Dank!

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.