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...
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.
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.
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.
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.
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 :-)
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.
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.
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.
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
staticuint8_tswTeilerT0=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...
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äß.
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.
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.
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.
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...
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.
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...