Forum: Mikrocontroller und Digitale Elektronik Exakte Zeit warten für Frequenzmessung?


von AVRli (Gast)


Lesenswert?

Hallo Leser,

ich möchte gerne einen kleinen Frequenzzähler programmieren der 
folgenden Ablauf hat. Die Flanken sollen über T3 eines ATmega128 gezählt 
werden...

- interne/externe HW Zähler auf 0
- 250ms warten
- Frequenz aus Zählerstände errechnen

Das ganze funktioniert in soweit das ich nun konstante Ergebnisse 
erhalte, allerdings mit einer Differenz die ich nicht erklären kann, 
weil sie nicht proportional zur Frequenz ist.

Statt  1 000 000 Hz werden  1 002 536 Hz,
statt 10 000 000 Hz werden 10 025 184 Hz,
statt 20 000 000 Hz werden 20 033 968 Hz angezeigt.

Ich vermute nun das das "Tor" wo der T3 zählt nicht exakt für die 
vorgesehene Messzeit von 250ms öffnet. Wie macht man so etwas?

_delay_ms(250) verwende ich nun, da meine Lösung über den Timer0, der 
alle 10ms aufgerufen wird und bis 25 eine Variable hoch zählt, stark 
schwankende Ergebnisse erzielt.

Wie kann man möglichst genau das Zeitfenster von 250ms erreichen?

Es sind noch weitere Timer aktiv, einer für HW PWM ein weiterer für Zeit 
unkritischer Aktionen sowie Tastenabfragen, ein UART läuft auch noch...

Vielen Dank für jeden Tip,
schöne Grüße AVRli...

: Verschoben durch Moderator
von B e r n d W. (smiley46)


Lesenswert?

Läuft der AVR überhaupt mit einem Quarz? Der interne RC-Oszillator ist 
bei weitem zu ungenau.

von Rolf M. (rmagnus)


Lesenswert?

Nunja, _delay_ms() wird durch Interrupts unterbrochen, d.h. jeder 
Interrupt verlängert das Delay um seine Laufzeit. Vielleicht hat es 
damit zu tun. Du kannst ja mal zum Testen während der Messung die 
Interrupts sperren.

von Ingo (Gast)


Lesenswert?

Ich würde eher in einem Timerinterrupt nutzen im mir eine Torzeit zu 
erzeugen. Hast du sonst noch interrupts die irgendwie dazwischen funken? 
Wenn man es genau machen will, deaktiviert man alle störquellen und 
misst dann ganz in Ruhe.

von Karl H. (kbuchegg)


Lesenswert?

AVRli schrieb:

> Ich vermute nun das das "Tor" wo der T3 zählt nicht exakt für die
> vorgesehene Messzeit von 250ms öffnet. Wie macht man so etwas?

Mit einem Timer.


> _delay_ms(250) verwende ich nun,

du treibst den Teufel mit dem Belzebub aus.

> da meine Lösung über den Timer0, der
> alle 10ms aufgerufen wird und bis 25 eine Variable hoch zählt, stark
> schwankende Ergebnisse erzielt.

Da hast du dann irgendwas falsch gemacht.
Wenn es um exakte Timings geht, dann führt kein Weg an Quarz und Timer 
vorbei. delay_ms ist dagegen eine Sanduhr, bei der der Sand nass 
geworden ist.

> Wie kann man möglichst genau das Zeitfenster von 250ms erreichen?

Zurück an den Start. Alles noch mal auf 'Timer als Torsteuerung' 
umbauen.

> Es sind noch weitere Timer aktiv, einer für HW PWM ein weiterer für Zeit
> unkritischer Aktionen sowie Tastenabfragen, ein UART läuft auch noch...

Und hoffentlich alle so programmiert, dass sie keine Zeit vertrödeln und 
nur ganz kurz die Sachen erledigen, die es zu erledigen gilt.

> Vielen Dank für jeden Tip,

Baus auf Timer zurück und wenn du nicht klar kommst poste dein Programm. 
Alles andere ist Kaffeesatzleserei und bringt dich nicht weiter.


Und PS:
Wie meistens ist der Terminus "warten" in µC-Programmen eine gefährlichs 
Sache.

Denn der Ablauf sollte nicht so sein

> - interne/externe HW Zähler auf 0
> - 250ms warten
> - Frequenz aus Zählerstände errechnen

Du hast einen Timer laufen. Der ruft regelmässig eine ISR auf. Nach 
jeweils 10 Aufrufen ist deine Torzeit um.
D.h. deine Strategie muss so aussehen:

In der ISR

  ist es der 10.te Aufruf?
  wenn ja
     die aufgelaufenen Zählwerte sichern
     alles auf 0 zurückstellen

d.h. das 'Warten' wird in der ISR erledigt, in dem die ISR mitzählt um 
den wievielten Aufruf es sich handelt und nach dem jeweils 10.ten Aufruf 
(denn dann sind 250ms um) die gemessenen Werte aus den Zählern sichert 
und sie der Frequenzberechnung zuführt.
Auf die Art hast du die Sicherheit, dass sich das Tor auch wirklich 
immer vom Timer-Stand 0 bis zum Timer-Stand 0 10 ISR Aufrufe später 
öffnet. Es kann höchstens eine kleine Verzögerung geben, wenn gerade 
eine andere ISR läuft wenn eigentlich die Frequenz-Timer-ISR laufen 
sollte, da du die aber kurz gehalten hast, bewegt sich der Zeitfehler da 
im kleinen µs-Bereich. Also weit weg von deinen 250ms. Die 250ms sind 
dann so exakt, wie es im µC nur möglich ist, auf jeden Fall um 
Größenordnungen exakter als mit einem _delay_ms

'Warten' in der µC-Programmierung ist meistens eher ein
Ich stell mir den Wecker so ein, dass er alle halbe Stunde klingelt. Um 
6 Stunden abzumessen, zähle ich einfach wie oft er geklingelt hat. Nach 
12 mal klingeln sind 6 Stunden um. Das gibt mir die Freiheit, dass ich 
in der Zwischenzeit tun und lassen kann was ich will und nicht ständig 
den Sekundenzeiger beobachten muss.
Das muss (im Prinzip) deine Denkweise sein, wenn du irgendwelche 
Zeitsachen mit einem µC machst. Es läuft so gut wie immer auf ein 
derartiges oder ein ähnliches Schema raus.


Am besten vergisst du fürs erste lieber wieder, dass es eine Funktion 
_delay_ms gibt. Man kann sie manchmal brauchen, aber es ist nur ganz 
ganz selten eine gute Lösung. Vor allen Dingen dann, wenn es um längere 
Zeiten (ich sach mal: alles über 500µs aufwärts) geht. Die Berechtigung 
für _delay_ms leitet sich IMHO hauptsächlich daher, weil jeder Neuling 
irgendwo mal anfangen muss und man ihn nicht gleich ins kalte Wasser 
stossen kann, so dass die Wellen (wie man die Dinge richtig macht) über 
ihm zusammenschlagen. Daher werden die ersten Lauflichter mit _delay_ms 
gemacht, auch wenn das aus der Sicht von 2 Monaten später ziemlicher 
Schwachsinn ist. Aber: an irgendeiner Ecke muss man anfangen zu 
programmieren und man kann nicht alles gleichzeitig lernen.

von Peter D. (peda)


Lesenswert?

Eine Torzeit für einene externen Zähler erzeugt man mit einen 
Timer/Counter1 Output Compare Match A Output (OC1A). Damit hat die 
Interruptlatenz keinen Einfluß.

Allerding hat die Torzeitmethode einen großen Nachteil, sie wird bei 
kleinen Frequenzen sehr ungenau.

Daher ist die übliche Methode, die Zeit für n Perioden der 
Eingangsfrequenz zu messen und daraus den Wert zu berechnen.
Speziell dazu gibt es den Timer/Counter1 Input Capture Input (ICP1). 
Damit ist der Timestamp unabhängig von der Interruptlatenz.
Die Frequenz muß aber so klein sein, daß man innerhalb einer Periode den 
Timestamp auslesen kann. Bei hohen Frequenzen schaltet man daher einen 
festen Vorteiler zu, z.B. 1:256. Der Vorteiler beeinflußt nicht die 
Genauigkeit.

von M. N. (Gast)


Lesenswert?

AVRli schrieb:
> ich möchte gerne einen kleinen Frequenzzähler programmieren der
> folgenden Ablauf hat. Die Flanken sollen über T3 eines ATmega128 gezählt
> werden...

Ohne jetzt ins Datenblatt zu sehen und zu hinterfragen, ob es sinnvoll 
ist, es so zu machen:
zähle Deine Impulse mit T3 und erzeuge das genaue Timing der 250ms über 
T1. T1 erzeugt nach Ablauf der Zeit einen Ausgangsimpuls (output-compare 
Funktion). Mit diesem wird der input-capture von T3 aktiviert.

Suche nach "reziproker Frequenzzähler", wenn Du eine 'vernünftige' 
Lösung brauchst :-)

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


Lesenswert?

B e r n d W. schrieb:
> Läuft der AVR überhaupt mit einem Quarz? Der interne RC-Oszillator ist
> bei weitem zu ungenau.

Er hat doch gar keine Angaben über die gewünschte Genauigkeit gemacht.

von Udo S. (urschmitt)


Lesenswert?

Jörg Wunsch schrieb:
> Er hat doch gar keine Angaben über die gewünschte Genauigkeit gemacht.

Nein aber gefragt:

AVRli schrieb:
> Das ganze funktioniert in soweit das ich nun konstante Ergebnisse
> erhalte, allerdings mit einer Differenz die ich nicht erklären kann,
> weil sie nicht proportional zur Frequenz ist.
>
> Statt  1 000 000 Hz werden  1 002 536 Hz,
> statt 10 000 000 Hz werden 10 025 184 Hz,
> statt 20 000 000 Hz werden 20 033 968 Hz angezeigt.

Er hat also einen Fehler von 0,2 bis 0,3 Prozent, die durchaus am 
internen Osz. hängen könnte. Wahrscheinlicher aber an seiner delay 
Geschichte.

Richtig ist wie Peter schon geschrieben hat

Peter Dannegger schrieb:
> Eine Torzeit für einene externen Zähler erzeugt man mit einen
> Timer/Counter1 Output Compare Match A Output (OC1A). Damit hat die
> Interruptlatenz keinen Einfluß.

Ich würde zunächst versuchen über eine kurze Torzeit Impulse zu zählen 
und falls es zu wenig Impulse sind umschalten auf Periodendauermessung.

Der TO kann ja erst mal beides einzeln programmieren und es dann 
entweder über Schalter oder automatisch umschaltbar machen.

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


Lesenswert?

Udo Schmitt schrieb:
> Er hat also einen Fehler von 0,2 bis 0,3 Prozent, die durchaus am
> internen Osz. hängen könnte.

Das ist natürlich richtig, und die Zahlen implizieren, dass er eine
Genauigkeit von besser als 1E-3 erwartet (hatte ich vorher nicht
gesehen/nicht drüber nachgedacht).  Andererseits wäre 1E-6 als
Genauigkeit für einen einfachen Quarz auch unrealisitisch.

Für den Rest wurden ja genügend Hinweise gegeben.

von Wolfgang (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Andererseits wäre 1E-6 als
> Genauigkeit für einen einfachen Quarz auch unrealisitisch.

Und wer weiß, wie genau die zum Vergleich verwendete Referenz ist.

Wenn man die Grundübungen zur Zeitbasis hinter sich hat, kann man auch 
gucken, wie andere einen Frequenzzähler bauen, z.B.
Beitrag "Frequenzzähler 1Hz - 40MHz"
http://www.mikrocontroller.net/articles/Frequenzz%C3%A4hlermodul

von AVRli (Gast)


Lesenswert?

Vielen vielen Dank für Eure ausführlichen Antworten!
Eine Grippe hat mich lang gesteckt, deshalb konnte ich nicht früher 
antworten.

Takt ist von einem Quarz mit 14,7456 MHz

Ich habe auf Timer umgebaut doch leider habe ich nun wieder das Problem 
das das Anzeigeergebnis nun spring wie verrückt und nicht um eine Stelle 
sondern +- >100  :-( Zähler (also Flanken) +/- 1-3 denke ich wäre ok

Irgendwas mach ich dann wohl noch falsch... :-(

Nun habe ich mir mal ein PIN H/L schalten lassen so lange der T0 läuft. 
Die Zeit ist nicht konstant. von 249,964ms - 249,981ms

fINT.bReadGo ist volatile...

Die ISR sieht wie folgt aus:
1
ISR(TIMER0_OVF_vect) {
2
  static uint8_t swTeilerT0 = 25;
3
4
  TCNT0 = 255 - 143;
5
  swTeilerT0--;
6
  if (swTeilerT0 == 0) {    // 250ms
7
    swTeilerT0 = 25;
8
  
9
    TCCR0 &=~ (7 << CS00);    // Timer0 CLK OFF
10
    TIMSK &=~ (1 << TOIE0);    // Timer0 OVF INT OFF
11
    CLR_DEBUG;
12
  
13
    TCCR3B &=~ (7 << CS30);    // Timer3 CLK OFF
14
    ETIMSK &=~ (1 << TOIE3);  // Timer3 OVF INT ON
15
16
    fMAIN.bUpdDispQrg = 1;
17
    fINT.bReadGo = 1;
18
  }
19
}

Im Mainprogramm erfolgt der Start mit...
1
...
2
if (fINT.bReadGo) {
3
  fINT.bReadGo = 0;
4
  SET_DEBUG;
5
  TCNT0 = 255 - 143;
6
  TCCR0 |= (7 << CS00);    // Timer0 CLK = CLKcpu / 1024
7
  TIMSK |= (1 << TOIE0);    // Timer0 OVF INT ON
8
}
9
...

Es wird auch nicht besser, wenn man die anderen ISR deaktiviert. 
IRgendwie komisch...

Die Genauigkeit sollte doch schon so zu erreichen sein das die letzte 
Stelle +/- einen Zähler ausmacht wenn man die gleiche Zeit misst oder?

Ich meine damit nicht die Genauigkeit zu Referenz X sondern die der 
aufeinander fallenden Messungen.

Das Ergbnis an sich wird durch Erwärmung usw. wandern aber doch nicht so 
hin und her hüppeln von Messung zu Messung...?

Ist die Timer Sache an sich OK soweit?

Gruß AVRli...

von M. N. (Gast)


Lesenswert?

AVRli schrieb:
> Ist die Timer Sache an sich OK soweit?

Natürlich nicht!
Wie oben wiederholt ausgeführt wurde, ist T0 ungeeignet und Dein 
Meßverfahren nicht zeitgemäß.

von Ulrich (Gast)


Lesenswert?

Mit dem Timer hat man ggf. das Problem, das gerade eine andere ISR (z.B. 
Timer3.Ovr) aktiv ist. Das kann dann für den Timer0 Interrupt eine 
Verzögerung von vielleicht 100 Zyklen geben, allerdings nur eher selten. 
In kleinerer Form gibt es Abweichungen um ein paar Zyklen durch 
unterschiedlich lange Befehle die unterbrochen werden.  Um die beiden 
Fehler zu umgehen, könnte man z.B. nach 249 ms alle anderen Interrupts, 
incl. dem Timer3_ovr ausstellen und dann noch mal 1 ms warten. In der 
Zeit den µC per Sleep in den Standby versetzen - dann ist die 
Antwortzeit für den Interrupt genau definiert.

Auch beim Starten von Timer0 kann ggf. noch was falsch laufen: der 
Prescaler muss vor dem Start noch zurückgetzt werden, sonst sind da ggf. 
noch bis zu 1023 Zyken drin.

Ein anderes Problem gibt es ggf. mit timer3_Überläufen: Nach dem Stoppen 
von Timer3 kann immer noch ein Interrupt anliegen, der auch noch gezählt 
werden muss. Also solle das Ausschalten (der Kommentar ist auch noch 
falsch) des Interrupts per
 ETIMSK &=~ (1 << TOIE3);  // Timer3 OVF INT ON
erst ins Hauptprogramm.

von Viktor N. (Gast)


Lesenswert?

Fuer ein genaues Timing sollte man ein CPLD nehmen und die Zaehler in 
Hardware implementieren.

von W.S. (Gast)


Lesenswert?

Viktor N. schrieb:
> Fuer ein genaues Timing sollte man ein CPLD nehmen..

Ähem..nö. Ein CPLD ist erforderlich, wenn man eine höhere Auflösung und 
höhere Meßfrequenzen habe will.

Das, was es hier in diesem Thread zu lesen gibt, ist mal wieder ne 
typische Atmel-Fan-Schlaubergerei.

AVRli schrieb:
> ich möchte gerne einen kleinen Frequenzzähler programmieren der
> folgenden Ablauf hat. Die Flanken sollen über T3 eines ATmega128 gezählt
> werden...

"Die Flanken.." eben. Schon mal der allererste Fehler. Mit einem 
normalen Mikrocontroller ohne asynchronen Vorteiler kann man keine 
Flanken zählen, weil alle Signale, die an den Portpins ankommen, auf den 
systeminternen Takt synchronisiert werden. Das ist ein klassisches 
Sampling - also erstmal ein bissel Abtast-Theorie sich anlesen. Was soll 
man auch mit einem Frequenzzähler, der grundsätzlich nicht zwischen 
echten Signalen und Alias (Aliassen, Alii..?) unterscheiden kann?

AVRli schrieb:
> - interne/externe HW Zähler auf 0
> - 250ms warten
> - Frequenz aus Zählerstände errechnen

Im Prinzip ist das ja nicht wirklich falsch, aber es bedarf einer 
kleinen Ergänzung: Erforderlich sind 2 Zähler, einer für das zu messende 
Signal und ein zweiter für das Referenzsignal - und (GANZ WICHTIG) beide 
müssen gleichzeitig oder zumindest mit konstantem Versatz gestartet und 
gestoppt werden - entweder synchron zum Referenzsignal (das ergibt dann 
den klassischen Zähler) oder synchron zum Eingangssignal (das ergibt 
dann den sogen. Reziprokzähler). Dein Ablauf sähe dann etwa so aus:
1. beide Zähler rücksetzen
2. Meßtor öffnen (entweder synchron zum Reftakt oder synchron zum 
Eingangssignal)
3. so ungefähr 250 ms lang warten (kann auch länger oder kürzer sein)
4. Meßtor wieder schließen (synchron wie bei Pkt.2)
5. beide Zähler auslesen und ins Verhältnis setzen

W.S.

von Ulrich (Gast)


Lesenswert?

So unmöglich ist die Idee nicht den µC zu nutzen um die Torzeit 
bereitzustellen. Der Timer (in der SW hier Timer0)im µC ist dabei der 
eine der Zähler, nämlich der für den Ref. Takt. Es gibt nur in der Regel 
einen besseren Weg.

Wenn man die Wahl hat zwischen einem Reziprokzähler und einem 
klassischen Zähler mit fester Torzeit, dann macht der klassische Zähler 
nur Sinn, wenn die Signalfrequenz höher als die Taktfrequenz des µC ist 
- sonst gibt der Reziprokzähler in der Regel die besseren Ergebnisse.

Eine höhere Frequenz als die halbe Taktfrequenz kann zumindest der 
Mega128 nicht direkt zählen und bräuchte entsprechend einen externen 
Zähler und eine Torschaltung dazu. Die Steuerung des externen Tors kann 
man dann über einen PWM Pin des µC machen, was die Sache mit dem Timing 
um einiges vereinfachen würde. Wenn es sein muss, könnte man die Tor 
Zeit auch per Timer und Interrupt erzeugen, das ist aber nicht so 
einfach - das eigentliche freischalten des Zählers ist dabei nicht das 
Problem - das ist auch auch in Software auf den Zyklus genau möglich, 
nur halt nicht so einfach.

Das klassische Zählen mit dem AVR macht eigentlich nur Sinn, wenn es 
darum geht ohne extra HW eine Frequenz zwischen etwa 1/50 und 1/3 der 
Taktfrequenz zu erfassen. Drunter ist der Reziprokzähler möglich und 
besser, drüber geht nicht richtig ohne extra HW.

von AVRli (Gast)


Lesenswert?

Ulrich schrieb:
> Auch beim Starten von Timer0 kann ggf. noch was falsch laufen: der
> Prescaler muss vor dem Start noch zurückgetzt werden, sonst sind da ggf.
> noch bis zu 1023 Zyken drin.

Bevor ich nun weiter suche warum es springt, würde mich interessieren 
wie man das anstellt?

Reicht ein abschalten der CLK und erneutes einschalten nicht?

Gruß AVRli...

von Ulrich (Gast)


Lesenswert?

Wie es jetzt genau beim Mega128 hab ich nicht nachgesehen, aber bei 
vielen AVRs (z.B. Mega88, Mega32) reicht es nicht den Takt anzuhalten um 
den Prescaler zurückzusetzen. Dafür ist dann ein extra Bit in einem 
weiteren Register vorgesehen.

von AVRli (Gast)


Lesenswert?

Ulrich schrieb:
> Dafür ist dann ein extra Bit in einem
> weiteren Register vorgesehen.

Das war's! Vielen Dank, nun läuft die Sache Rund mit Timer ohne 
_delay_ms und es sind 250ms :-D

Prima!

Grüße AVRli...

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.