Hallo,
Hab die Suchfunktion schon benutzt, konnt aber bisher nichts passendes
zu meinem Problem finden.
mich quält folgendes:
Ich muss eine Frequenz am AVR messen. Zu einem zufälligen Zeitpunkt
kommt ein Frequenzburst mit einer Frequenz von ca. 500kHz und ca. 50µs
Dauer an dem AVR an. Das Problem ist zusätzlich noch, dass die Dauer
nicht genau 50µs ist, das kann variieren. Das Signal liegt als TTL Pegel
vor.
Ich betreibe einen Atmega8 mit 4 MHz. Programmiern tu ich mit GCC.
Jetzt meine Frage, wie kann man dieses Signal am genauesten/exaktesten
messen?
Ich stelle mir das so vor: Kommt ein Flankenwechsel an z.B. bei INT0
wird ein Interrupt ausgelöst. Der Interrupt Startet den Timer und zählt
ein Signalwechsel. Pro Periode wird dann ein Interrupt ausgelöst und der
Zähler um 1 erhöht. Dann muss der AVR erkennen wenn kein Signal mehr
anliegt und den Timer stoppen und den Zählerstand und Timerstand in die
Frequenz umrechnen.
Hat jemand eine bessere Idee oder ein paar Vorschläge? Ich will nicht um
ein fertiges Programm betteln, ein guter Lösungsansatz oder Bestätigung
wäre schon sehr hilfreich.
Besten Dank!
Hallo,
sehr genau wird das nicht. Wenn Du, sagen wir mal mindestens zehn
Impulse hast (20µs), dann miss die Dauer dieses Bursts, also zehn
Impulse und lasse in dieser Zeit einen Timer mit vollem Takt mitlaufen.
Der Rest ist Mathematik. Für das nötige 1/x wäre eine MathLIB sinnvoll.
Sehr genau wird das aber auch nicht. Genauer wird es, wenn Du mehr
Perioden hast und den AVR- und damit den Timertakt so hoch wie möglich
kriegst. AVRs machen z.Z. max. 20MHz.
Viel Spaß
Lothar
Was genau willst Du denn messen ? Die Dauer des Paketes ? eine einzelne
Periode ? jede einzelne Periode ?
Wie hoch muß die Auflösung sein ?
Wie auch immer, besorge Dir schon einmal einen Quarz mit 16MHz.
Hi,
also wegen der Genauigkeit:
Wenn ich sagen wir mal günstig angenommen 20 Impulse einfange, dann wäre
meine Messungenauigkeit +/- ein Zählerstand im Timer, vorausgesetzt das
Timing stimmt und der Code ist effektiv.
Ein Zählschritt @ 4 MHz dauert ca. 0,25µs. Bei also angenommen 40µs
Signal mit 20 Impulsen wäre das dann ca. 0,625% Messungenauikgeit. Also
bei meinem Signal würde er im schlimmsten Fall 500kHz +/- 3kHz anzeigen.
Das würde mir an Genauigkeit reichen.
Macht es überhaupt Sinn den Flankenwechsel per Interrupt auszuwerten?
Ich bräuchte einerseits ein Interrupt der meinen Timer Startet und dann
ein Interrupt für das Hochzählen. Mein Signal kann ja aber nur ein ext.
Interrupt auslösen.
Mit einer höheren Zählfrequenz wirds genauer. Ein externer 16er-Teiler
mit einem Takt von bis zu 160 MHz, 74AC161 o.ä. könnte an einem 20
MHz-AVR funktionieren. Start und Stop dieses Zählers ( Clock enable oder
externes UND-Gatter) werden von der ersten Pulspaketflanke und der
Flanke des 20. Impulses abgeleitet. Anschließend hat der AVR Zeit, die 4
Bit des externen Zählers und seiner internen folgenden Zählstufen in die
Frequenz umzurechnen.
Die beste Lösung wäre mit ICP1 von Timer 1 zu erreichen. Nach dem 1.
Interrupt bleibt man in der ISR und arbeitet weitere Flanken ab, bis die
erwartete Pulsdauer überschritten wird (timeout). (Dickere CPUs würden
T1-Werte nebenbei einfach per DMA ablegen.)
Um den 1. Interrupt nicht zu verpassen, kann man den AVR schlafen legen
und durch diesen Interrupt aufwecken, sodaß die Reaktionszeit minimal
wird.
Nimm aber bitte 16MHz :-)
Also das mit dem CPU Takt das werde ich mir zu Herzen nehmen. Einen
externen Zähler denke ich brauche ich nicht. Trotzdem Danke für den Tip,
Christoph.
Ich hab mal hier ein Lösungsansatz der mir INT0 Interrupt funktionieren
sollte. Hab das ganze nur mal rudimentär zusammengeschustert.
@ Michael: Taktfrequenz wird noch erhöhrt. Das mit dem ICP1 gefällt mir
ganz gut, dann könnte ich denke ich den Timer0 zum zählen, und den
Timer1 als Timout verwenden, dann könnte ich automatisch die maximale
Länge des Burstes erfassen und mein Ergebnis so genau wie möglich
halten. Was meinst Du aber mit schlafen legen? Brauch ich doch nicht?!?
Hier erstmal die 1. Version:
#includes_und_defines_usw
ISR(INT0_vect) // Flankenwechsel an INT0
{
if (TCCR1B != 0x01) // Falls Timer nicht gestartet dann starten
TCCR1B = 0x01; // starte Timer 1 (clk = F_CPU)
freqcounter++; // eine Flanke hochzählen
}
int main(void)
{
uint16_t freqcounter=0;
uint16_t frequenz;
sei(); // Interrupts Enable
MCUCR |= (1 << INT0) | (1 << ISC01) | (1 << ISC00);
// Interrupt Freigeben für INT0, rising edge
while(1)
{
if (freqcounter == 20)
{
TCCR1B = 0x00; // stoppe Timer
MCUCR &= ~(1 << INT0); // Interrupt INT0 Disable, damit
Zählerstand nicht mehr erhöht wird
frequenz = (uint16_t)(20/( (long)TCNT1/(long)F_CPU) );
// berechne Frequenz in kHz
}
// Ausgabe Frequenz auf z.B. Display
}
return 0;
}
Ich hab mal der Einfachheit wegen ein Frequenzgenerator angeschlossen.
Bei 100kHz funktioniert es ohne Probleme mit der MEssung, doch bereits
ab 150kHz stimmt der Wert nicht mehr. Kann es sein, dass die ISR zu
viele Taktzyklen belegt und somit der nächste Flankenwechsel/Interrupt
zu "früh" eintrifft, bevor die alte ISR abgearbeitet ist?
Schön, daß Du Dein Programm selbst getestet hast; fertige Lösungen
wolltest Du ja nicht haben, und auf die würde es hinauslaufen, wenn
jemand die Funktion Deiner Zeilen bestätigen soll.
C-Programmierung geht hier nicht, es muß ASM sein ! Dabei ist es
geschickt, möglichst alle Variablen in Registern zu halten und nur Daten
zu erfassen. Die Auswertung kommt dann später, wenn alle Impulse erkannt
sind.
Und wenn man ganz schnell auf INTs reagieren muß, kann es nützlich sein,
die notwendigen Register zu retten, dann den Prozessor schlafen zu
legen, und beim Aufwachen per INT gleich mit der Verarbeitung
einzusetzen, ohne PUSHs oder POPs.
Hi Michael,
das mit fertiger Lösung ist in der Tat so. Bevor ich mir ne fertige
Lösung anschaue versuch ich selbst darauf zu kommen. Ich denke das macht
einen guten Programmierer aus. Wenn man sich intensiv mit den Problemen
auseinandersetzt bekommt man mit der Zeit das richtige "Gespür".
Abtippen kann jeder und das Programm funktioniert wohlmöglich auch, aber
lernen tut man da nur begrenzt.
Es war schon so gemeint dass ich gerne Hilfe annehme. Wenn dabei mein
Programm komplett umgestrickt wird dann hab ich da auch nichts dagegen.
Nur den 1. Ansatz den wollte ich halt selbst machen.
Wenn jemand eine Idee oder Verbesserung dazu hat gerne her damit. Bin
darüber auch sehr dankbar. Mit ASM hab ich mich noch nicht
auseinandergesetzt. Ich denke das müsste doch auch mit effizientem
C-Code zu machen sein!?!?
Mittlerweile hab ich eine 2. Version "entwickelt":
Timer0 wird mit dem externen Signal getaktet. Timer1 fängt an die CPU
Takte zu zählen. Wenn Timer0 auf z.B. 50 steht wird der Timer1 - Stand
anhand von den 50 Impulsen in die Frequenz umgerechnet.
Der Vorteil ist, dass der Zähler automatisch hochzählt und ich mir die
ISR sparen kann.
Funktioniert bis 400kHz gut, darüber hinaus versagt die Abfrage ob schon
50 Impulse eingegangen sind.
Mal eine prinzipielle Frage:
Wie ist die theoretisch messbare Frequenz wenn man DIREKT auf ein
Eingangspin am Atmel geht?
Habe mal was von halber CPU Frequenz gelesen...
>Ich denke das macht einen guten Programmierer aus.
Ein guter Programmierer versteht auch bzw. kann analysieren und
bewerten, was andere Leute ihm "vorsetzen".
>Wenn man sich intensiv mit den Problemen>auseinandersetzt bekommt man mit der Zeit das richtige "Gespür".
... oder manverrent sich in etwas völlig sinnfreies...
>Habe mal was von halber CPU Frequenz gelesen...
Das dürfte auch per ICP noch nicht gehen.
Zumindest kann per OC die halbe Prozessorfrequenz ausgegeben werden.
Ich hab ja geschrieben dass ich mir gerne helfen lasse. Ich wollte nur
nicht Anfangs hergehen und sagen: "Leute, ich will ne Frequenz messen,
gebt mal bitte alles an Programmen und Vorschlägen was Ihr habt"
Wie gesagt ich lass mein Programm gern verbessern oder greife auch auf
ein anderes Konzept zurück.
wie siehts denn mit deinen messleitungen aus? der schwachsinn kann auch
leicht durch fremdeinflüsse kommen...
Prescaler richtig eingestellt?
Höhere Frequenz zum messen benutzen.
ASM programmieren.
Es kann auch sein, wo ich gerade im Datasheet lese: welchen timer
benutzt du? beim timer 1 kann das problem darin bestehen, das er 16-bit
hat - der µC braucht zum lesen und ins register schreiben 2x so lang als
beim 8-bit timer... vllt. geht es sich ja mit den zeiten so aus, das du
den 8-bit timer verwendest...
vllt. hat ich geholfen...
jÜrgen
wie ich nochmals nachgelesen habe, das du bei deinem zweiten versuch
timer0 mit einem externen clock ansprichst, hab ich mal im DS
nachgelesen und gesehen, das hier max. f/2,5 empfohlen sind... hast du
das eingehalten?
Als Timeout empfiehlt sich auch der Watchdog, dann muss kein
zusätzlicher Timer verbraten werden. Ist bei dem ankommenden Burst
sichergestellt dass das Potential am Anfang und Ende gleich sind? Oder
ist das z.B. am Anfang Low und am Ende High, dann solltest du die
Flanken zählen, den Abstand zwischen den Flanken messen und in einer
Variable kumulieren, dann kannst du die Burstdauer, die Anzahl Impulse
und die Frequenz des Bursts ausrechnen.
Also ich habe mein Atmega8L @ 8 MHz laufen. Die Messleitungen dürften,
soweit ich das beurteilen kann, sauber sein.
Bei dem Singal ist am Anfang LOW und am Ende auch LOW.
Die eingehende Frequenz ist mal ca. 1 MHz zum Testen. Der Prescaler ist
1, läuft also mit vollem CPU Takt für max. Genauigkeit.
Timer 0 Zählt meine Impulse, Timer 1 misst die Zeit die vergeht. Nach 50
Impulsen sollte die Messung stoppen.
Hab hier mal meine Funktion angehängt. Das Problem ist, bei höheren
Frequenzen stoppt der Timer nicht nach 50 Impulsen, sondern erst nach
z.B. 63 Impulsen die Messung. Ich kann kaum glauben dass die IF Abfrage
und das Stoppen des Timers so lange Dauert dass mein Signal << F_CPU
noch über 10 Perioden lang anliegt.
void frequenzmessung(void)
{
uint16_t frequenz;
TCNT0 = 0;
TCNT1 = 0; // Timerstände resetten
// External clock source on T0 pin. Clock on rising edge.
TCCR0 |= (1 <<CS02) | (1 <<CS01) | (1 <<CS00);
// Starte Timer1. Full CPU Clock.
TCCR1B |= (1 << CS10);
while(1)
{
if (TCNT0 > 49)
{
// Stoppe Timer0
TCCR0 &= ~(1 <<CS02) | ~(1 <<CS01) | ~(1 <<CS00);
// Stoppe Timer1
TCCR1B &= ~(1 << CS10);
frequenz = (uint16_t)((TCNT0*8000)/TCNT1); // berechne Frequenz in kHz
// Ausgabe Frequenz, Timerstände zur Kontrolle auf z.B. Display
break;
}
}
}
Das ist jetzt ein gutes Beispiel dafür, daß eine Taktfrequenz auch zu
hoch sein kann. Wenn einige Blödmänner hier 16MHz vorgeschlagen haben,
ist das völlig daneben. Du brauchst einen Takt von genau 6,34920MHz
(50*8/63) !!!
So erhält man dann einen Zählerstand von exakt 50.
Man sieht, daß genaue Meßanforderungen auch genaue µP-Taktfrequenzen
erfordern, so krumm diese auch sein mögen.
ja, der Takt des Timers kann so gewählt werden, den µC selber würde ich
dennoch mit 8 od. 16MHz betreiben, damit die Auswertung schnell genug
abgearbeitet werden kann...
Also 8MHz scheint mir auch als bestmögliche Frequenz.
Hat vielleicht jemand eine Verbesserung zu meinem Programm? Oder soll
ich auf ein ganz anderes Konzept zurückgreifen?
Soll ich lieber in der ISR zählen, oder den Zähler mit externem Takt
laufen lassen, oder die Capture Funktion benutzen. Mit was wurden die
besten Ergebnisse erziehlt?
Ich habe mal die Zeilen:
TCCR0 |= (1 <<CS02) | (1 <<CS01) | (1 <<CS00);
TCCR1B |= (1 << CS10);
durch
TCCR0 |= 0x07;
TCCR1B |= 0x0001;
ersetzt (und beim Rücksetzen entsprechend auch). Scheint gewaltig Zeit
zu sparen. Jetzt Zählt er bis ca. 1 MHz
ABER: Die Ausgabe ist manchmal 1015 kHz, manchmal 1025 kHz, manchmal
1021 kHz oder irgendwo dazwischen. Also ein genaues Ergebnis schickt er
nicht raus, dabei ist meine angelegte Frequenz ziemlich stabil.
Je kleiner die zu messende Frequenz ist, desto ungenauer wird das
Ergebnis.