PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Programm zum Frequenzmessen



sebi87
01.02.2009, 14:18
Hallo AVR-Gemeinde,

ist es möglich mit einem Mega32 per Software eine Fequenz zwischen 0,1 MHz und 2 MHz zu messen.

Gibt es vielleicht sogar schon fertige Lösungen?


Grüßle
Sebastian

markusj
01.02.2009, 15:17
Stichwort: Timer, Counter - Siehe Datasheet.

mfG
Markus

wkrug
01.02.2009, 22:38
Das müsste möglich sein, wenn man die zu messende Frequenz als Taktquelle für einen Timer benutzt.
Ein weiterer Timer bestimmt dann noch die Torzeit der Messung.
Es gibt aber, soweit ich das im Hinterkopf habe, eine Grenze wie hoch der Timer extern im Bezug auf den Controllertakt sein darf.

sebi87
02.02.2009, 19:41
hat hierzu vielleicht irgendjemand ein Beispiel damit ich mich daran orientieren kann?
Hab so was noch nie gemacht.

Besserwessi
02.02.2009, 20:22
Für etwas niedrigere Frequenzen, bis etwa 300 kHz, geht die Freqwunzmessung sehr gut über die ICP funktion. Man maißt da nicht die Freqwunz direkt, sondern die Periodendauer. Unter Timer ist in Wiki Bereich ein Beispielprogramm in C, allerdings mehr für niedrige Freqwunzen unter 1 kHz. Für höhere Freqwunzen solle man die Dauer von mehreren Perioden messen.
Eine Frequenz über etwa 300 kHz müßte man das Signal runterteilen.

wkrug
02.02.2009, 21:27
@sebi87
Ein fertiges Programm hab ich leider nicht in petto.
Im Prinzip geht es darum einen Timer, vorzugsweise den Timer 1 mit der zu messenden Frequenz zu takten. Ich würde das ohne Vorteiler machen.
Im Overflow Interrupt des Timers 1 wird dann eine Variable hochgezählt, die dann auch Messungen über 65536 Hz zulässt und somit als High Word dient.

Ein anderer Timer z.B. dient als Torzeitquelle für z.B. 1sek. Der wird mit der prozessoreigenen Taktquelle gesteuert.

Nach einer Sekunde, also wenn der Torzeittimer zugeschlagen hat wird der Zähler + Überlaufzähler ausgelesen, in eine Variable geschoben und der Timer 1 für die nächste Messung wieder auf 0 gesetzt. Diesen Programmteil würde ich auch in einem Interrupt anlegen.

Man hat jetzt das Low und High Word der gemessenen Frequenz direkt in Hertz und kann das Ergebnis in der Hauptroutine des Programms weiter verarbeiten.


Für etwas niedrigere Frequenzen, bis etwa 300 kHz, geht die Freqwunzmessung sehr gut über die ICP funktion.
Das stimmt schon, hier geht es aber um 2 MHz und da wird es mit ICP schon sehr ungenau und schwierig.
Ausserdem wir ja so bei jedem Takt der Frequenz ein Interrupt ausgelöst und blockier so den Controller.

sebi87
03.02.2009, 14:35
Kann mir da jemand ne grobe Programmierstuktur geben?

sebi87
03.02.2009, 15:17
Wie muss ich das mit der Torzeit machen.
Mir ist klar wie das Funktionieren soll, kann es nur nicht programmierne...

Das habe ich mal Programmiert und der Zähler zählt auch die Überläufe am Timer0


volatile uint16_t overflow ;


/*-- Hauptprogramm ---------------------------------------------------------------------*/
int main(void)
{
char buffer[10] ;
count = 0 ;
overflow = 0 ; // Variable für Overflow

lcd_init() ; // LCD aktivieren

// Timer0 auf Externen Clock Source T0 mit Rising Edge setzen --> S.86
TCCR0 |= (1<<CS02) | (1<<CS01) | (1<<CS00) ;

// Overflow Interrupt Enable --> S.87
TIMSK |= (1<<TOIE0) ;

sei() ;

while(1)
{
utoa(overflow,buffer,10) ;
set_cursor(0,1) ;
lcd_string(buffer) ;
}

}

/*-- Timer0 Overflow Interrupt ---------------------------------------------------------*/
SIGNAL (SIG_OVERFLOW0)
{
overflow++ ;
}

Wenn mir jemand helfen könnte wäre ich sehr dankbar

Besserwessi
03.02.2009, 15:38
Für die Torzeit muß man noch eine genaue Verzögerung Programmieren:
Ersr den externen timer zurücksetzen und starten, dann die genau definierte Zeit (z.B. 1 Sekunde) warten und danach den Timer Stoppen.

Für eine Möglichst genaue Wartezeit, sollte man dafür einen 2 ten Timer nehmen, denn die man sollte das schon bis auf ein paar Zyklen genau hinkriegen. Es ist möglich das sogar wirklich zykengenau hinzukriegen, allerdings nicht ganz einfach wenn man gerade am Ende eine Überlauf Interrupt dazukriegt. Wenn man sich nicht um diesen eher seltenen Fall kümmern will, sollte man zum Zählen besser den 16 Bit Zähler nehmen, dann tritt der Fehler eher selten auf.

sebi87
03.02.2009, 15:40
Ich komm gerade irgendwie nicht weiter....
Wie bekomme ich den Timer dazu diese Genau definierte Zeit zu messen?

Besserwessi
03.02.2009, 16:04
Zum messen eine eternen Zit gibts die ISP Funktion. Wenn man aber den Weg mit dem Zählen wählt, wegen der hohen Frequezn, muß man eine definierte Zeit warten. Das Warten geht z.B. so:
Den Timer Starten, ggf. mit Startwert. In der ISR zu dem Timer dann ggf. noch einen Zähler runterzählen und dann wenn man 0 erreicht hat den anderen Timer anhalten. Für eine Wirklich extacte Zeit sollte das Hauptprogramm einfach ein den Idel mode gehen per Sleep-befehl.

Das abfangen einen extra Überlaufs beim anderen Timer ist etwas komplizierter: dazu schaltet man kurz vor dem Enden der Torzeit den Interrupt ab, und ließt dann neben dem Timer noch das Interrupt flag aus. In der kurzen Zeit am Ende sollte höchstens ein Überlauf vorkommen können.

wkrug
03.02.2009, 16:11
Nimm doch für die Taktmessung den Timer 1.
Mit deiner Konfiguration wird alle 256 Perioden des Signals ein Timer 0 Overflow Interrupt ausgelöst.
Da der Timer 1 ein 16 Bit Timer ist passiert das dort nur alle 65536 Perioden.

Ich hab nen anderen C-Compiler als Du, deshalb kann ich deinen Code nicht direkt bearbeiten.

Ich würd mal so an die Sache rangehen.

1. Du muss die Controller Taktfrequenz wissen, mit welcher Du arbeiten willst.
Sagen wir mal 8 MHz.
Der Timer 0 bekommt dann einen Prescaler von 8.
Somit wird alle 2048 Takte ein Timer0 Overflow Interrupt ausgelöst.
In diesem Interrupt wird nun eine 16Bit Int Variable hochgezählt.
Wenn diese Variable den Wert 3906 hat ( >3906 ) wir der TCNT0 mit 192 vorgeladen. TCNT0=192;
Weil die eine Sekunde nicht genau in den 2048 Takte passt ist das notwendig = (2048-0,25*2048)/8
Die Int Variable wird wieder auf 0 gesetzt.
Jetzt wird der Zähler TCNT1 und die dazugehörige Overflow Variable ausgelesen. Beide werden nun in Hilfsvariablen abgespeichert.
Dann wird TCNT1 und die zugehörige Overflow Variable auf 0 gesetzt.


void int tim0overflow(void)
{
ui_ov0count++
if(ui_ov0count>3906)
{
ui_counterlow=TCNT0;
TCNT0=192;
ui_counterhigh=ui_ov1count;
ui_ov1count=0;
ub_newflag=1;
};
};

2. Das Flag teilt der Hauptroutine mit, das neue Messwerte vorliegen und verarbeitet werden müssen.
Ist das in der Hauptroutine geschehen wird das Flag ub_newflag wieder auf 0 gesetzt.
Damit wäre dann das Torzeitproblem erschlagen.
Wie lange das Programm wirklich braucht, teste ich immer mit dem Simulator vom AVRSTUDIO aus.
Eventuell müssen da Anpassungen gemacht werden, da der Timer ja ach während des Programmablaufes weiter läuft.


void int tim1overflow(void)
{
ui_ov1count++
};


Die Timer 1 Overflow Routine ist erfreulich kurz - es wird nur die Hilfsvariable hochgezählt.



void main (void)
{......

while(1)
{
if(ub_newflag>0)
{li_frequenz=(ui_counterhigh*65536)+ui_counterlow;
/*Es sollte auch li_frequenz=(ui_counterhigh<<16)|ui_couterlow; gehen*/
displayout(li_frequenz);
ub_newflag=0;
}
}
}

Die beiden Hilfsvariablen werden dann in der Hauptroutine weiter verarbeitet in eine Long Int Variable gepackt und ausgegeben.

Du kannst natürlich auch mit anderen Quarzfrequenzen und Teilerfaktoren für den Timer0 arbeiten. Letztlich soll sich aber möglichst genau 1 Sekunde dabei rauskommen.

sebi87
03.02.2009, 16:27
Danke mal für die Antworten ich werde das probieren :-)

sebi87
03.02.2009, 16:46
Ok... ich hatte heute irgendwie schon zu lange Vorlesung oder ich bin einfach zu doof.

Kann mir jemand den 1. Schritt von wkrug nochmals erklären?

Ich kann das doch auch auf 10ms verkürzen und wie müsste das dann sein?
Und nachher einfach die Pulse mal 100 und dann hab ich die Frquenz oder?

wkrug
03.02.2009, 17:08
Ich kann das doch auch auf 10ms verkürzen und wie müsste das dann sein?
Und nachher einfach die Pulse mal 100 und dann hab ich die Frquenz oder?
Das geht natürlich auch.
Allerdings kriegst Du dabei eine maximale Auflösung von 100Hz.
Du hast nicht gepostet welche Auflösung Du haben willst.
Ich bin da einfach mal von 1Hz ausgegangen.
Und das geht mit dieser Methode halt nur mit Torzeiten von 1 Sekunde.

Ein Kompromiss wäre eine Messdauer von 100ms, das ergäbe dann eine maximale Auflösung von 10Hz.
Dein Display kannst Du ohnehin nicht viel öfter als 3...4x pro Sekunde refreschen, sonst wirds nervig.

sebi87
03.02.2009, 17:35
kann mal bitte noch jemand über den Code schaune, es geht nicht :-(


/*-- Hauptprogramm ---------------------------------------------------------------------*/
int main(void)
{
char buffer[20] ;


count = 0 ;
overflow1 = 0 ; overflow0 = 0 ; // Variable für Overflow

lcd_init() ; // LCD aktivieren

//Timer0 auf Prescaler 8 stellen --> S.86
TCCR0 |= (1<<CS01) ;

// Overflow Interrupt Enable --> S.87
TIMSK |= (1<<TOIE0) ;

// Timer1 auf Externen Clock Source T1 mit Rising Edge setzen --> S.115
TCCR1B |= (1<<CS12) | (1<<CS11) | (1<<CS10) ;

// Overflow Interrupt Enable --> S.117
TIMSK |= (1<<TOIE1) ;

sei() ;

while(1)
{
utoa(overflow1,buffer,10) ;
set_cursor(0,1) ;
lcd_string(buffer) ;
set_cursor(0,2) ;

if(newflag > 0)
{
frequenz=(high*65536)+low;
lcd_data(frequenz) ;
newflag=0;
}
}

}

/*-- Timer0 Overflow Interrupt ---------------------------------------------------------*/
SIGNAL (SIG_OVERFLOW0)
{
overflow0 ++ ;
if(overflow0 > 3906)
{
low = TCNT0 ;
TCNT0 = 192 ;
high = overflow1 ;
overflow1 = 0 ;
newflag = 1 ;
}
}


/*-- Timer1 Overflow Interrupt ---------------------------------------------------------*/
SIGNAL (SIG_OVERFLOW1)
{
overflow1 ++ ;
}

Besserwessi
03.02.2009, 18:25
Einge der variablen müßten noch als volative gekennzeichent werden. Wenigstens overflow0, overflow1, low und high.

Für die weiteren Messungen ist auch das zurücksetzen der Timer noch nicht ganz in Ordnung. overflow0 und timer 1 müssen auch wieder bei 0 starten. Als ein kleines Detail sollte man auch den Prescaler wieder zurücksetzen. Es kann auch immer noch zu ein paar glitches kommen durch einen eher seltenen Überlauf von timer1 gerade wenn die Messzeit zu ende geht. Wenigstens einen Teil davon könnte man abfangen: Wenn der Timer1 einen kleinen Wert hat und das Overflow Bit von timer1 gesetzt ist, fehlt da wohl noch eine Overflow interrupt.

sebi87
03.02.2009, 18:41
Die variablen sind alle volative;

Ich gebs heute auf es läuft nicht....

wkrug
03.02.2009, 19:50
if(overflow0 > 3906)
{
low = TCNT0 ;
TCNT0 = 192 ;
high = overflow1 ;
overflow1 = 0 ;
newflag = 1 ;
}
low = TCNT0 ist falsch.
Da muß low = TCNT1 hin.
Danach sollte man TCNT1 gleich wieder auf 0 setzen
TCNT1=0;
Der "SEI" Befehl soll einen Overflow Interrupt des Zählers vor der Abfrage ermöglichen.
Mit dem "CLI" Befehl wird diese Möglichkeit wieder verhindert.
Wenn der Controller die Interrupt Routine verlässt sollte SEI automatisch ausgeführt werden.
Man könnte auch das Timer 1 Overflow Flag abfragen und entsprechend darauf reagieren.
Das sind aber erstmal Future Optionen. Erstmal muß das Ding laufen.


if(overflow0 > 3906)
{
#asm("sei");
low = TCNT1 ;
#asm("cli");
TCNT0 = 192 ;
TCNT1=0;
high = overflow1 ;
overflow1 = 0 ;
newflag = 1 ;
}

Hast Du deinen Code schon mal im Simulator vom AVR Studio laufen lassen ? Ist wirklich hilfreich!

sebi87
03.02.2009, 20:24
Hmm... sehr ich das richtig das soll einmal das zählregister im Low sein (TCNT0) und dann der obere Teil (TCNT1) oder?

Wenn ja müsste es TCNT1L und TCNT1H sein...

Besserwessi
03.02.2009, 20:50
Bei den meisten 16 Bit registern stellt GCC auch schon den direkten zugriff als 16 Bit wert zu verfügung.
Also geht entweder TCNT1 oder TCNT1L und TCNT1H seperat. Wobei der direkte 16 Bit-wert praktischer ist.


Wenn man es richtig machen will sollte man erst den Timer 1 Stoppen, und dann duch SEI/CLI eventuell noch ausstehenden Interrupt einfügen. Sonst hat man kaum einen wirklich definierten Stopzeitpunkt und kann trotzdem noch einen Überlauf genau auf der Grenze kriegen.

Was definitiv noch fehlt, ist es overflow0 zurückzusetzen. Auch verstehe ich das "TCNT0 = 192" nicht. So viel Unterschied sollte in der initialisierung nicht zustandekommen.

sebi87
03.02.2009, 20:59
Danke, dann hab nicht nur ich das nicht verstanden.

Jetzt hab ich mal eine fast brauchbare Anzeige. Ist nur nicht stabiel, weiß auch noch nicht warum...

overflow0 müsste ich doch dann nullsetzen wenn der Wert ausgegeben ist, oder?

sebi87
03.02.2009, 21:03
Das war ein sehr guter Tipp danke Besserwessi.
Wenn ich jetzt overflow0 Null setzte bekomme ich eine schöne Ausgabe zwischen den messzyklen. Ob sie stimmt muss ich morgen mal prüfen.

Mit dem "TCNT0 = 192" wird die Messung genauer, weiß aber noch nicht warum, bitte um Erklärung.

Hier nochmal der Quelltext so wie es geht:

volatile uint8_t count ;
volatile uint16_t overflow1 ;
volatile uint16_t overflow0 ;
volatile uint16_t low ;
volatile uint16_t high ;
volatile uint8_t newflag ;
uint32_t frequenz ;


/*-- Hauptprogramm ---------------------------------------------------------------------*/
int main(void)
{
char buffer[20] ;


count = 0 ;
overflow1 = 0 ; overflow0 = 0 ; // Variable für Overflow

lcd_init() ; // LCD aktivieren

//Timer0 auf Prescaler 8 stellen --> S.86
TCCR0 |= (1<<CS01) ;

// Overflow Interrupt Enable --> S.87
TIMSK |= (1<<TOIE0) ;

// Timer1 auf Externen Clock Source T1 mit Rising Edge setzen --> S.115
TCCR1B |= (1<<CS12) | (1<<CS11) | (1<<CS10) ;

// Overflow Interrupt Enable --> S.117
TIMSK |= (1<<TOIE1) ;

sei() ;

while(1)
{
if(newflag > 0)
{
set_cursor(0,1) ;
frequenz = (high*65536) + low;
snprintf(buffer, 20, "%u Hz", frequenz) ;
lcd_string(buffer) ;
newflag=0;
high = 0 ;
low = 0 ;
}
}

}

/*-- Timer0 Overflow Interrupt ---------------------------------------------------------*/
SIGNAL (SIG_OVERFLOW0)
{
overflow0 ++ ;
if(overflow0 > 3906)
{
sei() ;
low = TCNT1 ;
cli() ;
TCNT0 = 192 ;
TCNT1 = 0 ;
high = overflow1 ;
overflow1 = 0 ;
newflag = 1 ;
overflow0 = 0 ;
}
}


/*-- Timer1 Overflow Interrupt ---------------------------------------------------------*/
SIGNAL (SIG_OVERFLOW1)
{
overflow1 ++ ;
}

Besserwessi
03.02.2009, 21:46
Das mit dem TCNT0 = 192 verkürzt die Zeit der Späternen Messungen etwas. Solange man das für alle Messungen gleich hat stört es nicht weiter.


Man sollte den Timer1 vor dem SEi() in der ISR stopen, sonst hat man dadurch nicht viel gewollen. Den Timer solle man erst wieder Starten nachdem man den Wert wieder auf Null gesetzt hat.
Wenn man den Prescaler von timer0 nicht zurücksetzt sollte man sicher sein, das der Code zwischen stop der einen Messung und Start der nächsten immer genau gleich lange braucht. Duch die eventuell nachgehohlte ISR sollte das aber fast unmöglich sein. Also sollte man wenigstens für volle genauigkeit den Prescaler zurücksetzen Wie das geht müßte man im Datenbraltt nachlesen.

Bleibt noch der eher seltene Fall, das man kurz vor dem entscheidenen Aufruf von overflow0 gerade einen overflow1 oder anderen Interrupt hat. Dies läßt sich aber vermeiden, wenn auch nicht ganz elegant:

In der ISR(overflow0) (Nr. 3905) wird erstmal der overflow1 abgeschaltet und dann ein eventuell noch ausstehender overflow1 erlaubt.
Danach bleiben interrupts erlaubt und es wird per Sleep in den Idel Mode gegangen. Aus dem Idel mode wacht der Controller auf, um den overflow0 (Nr. 3906) abzuarbeiten und man ist dabei sicher eine genau definierte Verzögerung vom Timer zu haben. Erst hier wird das Ergebins ausgelesen und die Timer wider gestartet, etwa so wie in jetzingen Version.

Das ganze ist dann aber schon einen ziehmlich verschalchtelte version. Das wäre einer der ganz wenigen Fälle wo man in einer ISR den Sleep- befehl nutzen darf und dann auch noch die gleiche ISR noch einmal auftreten kann. Normalerweise sollte man ja vermeiden das eine ISR druch eine von gleichen Typ unterbrochen wird.

Man solle auf alle Fälle sehen das man genug Stack hat, denn da wird einiges gebraucht.

wkrug
04.02.2009, 08:35
Auch verstehe ich das "TCNT0 = 192" nicht. So viel Unterschied sollte in der initialisierung nicht zustandekommen
1 sek bei 1Mhz ( 8MHz /8 ) braucht 1 Mio Zählerstand.
1Mio / 256 = 3906,25 Überläufe.
,25 Überläufe kriegt man, wenn man den Timer noch 256*0,25=64 Zählerstände hochzählen lässt.
256-64=192 Also muss der Timer für den ersten Durchlauf mit 192 vorgeladen werden - so kommt das zustande.