Hallo AVR-Experten,
ich habe mich gestern und heute mit einen Problem rumgeärgert und immer
noch keine Lösung dazu gefunden.
Ich habe mir einen Frequenzmesser geschrieben bei dem ich die Frequenz
von über 100 Herz über den Port PD5/PCINT21 einlese.
Das Programm arbeit größtenteils korrekt, doch es gibt Situation indem
nur ein Viertel der Frequenz ausgegeben wird.
Die ISR werden wie beim Oszi zu sehen aber immer abgearbeitet.
Die CPU läuft mit 20.000.000 Hz. Es ist ein Atmega328.
Ich habe echt viel probiert Timereinstellungen andere Berechnung aber
bisher ohne Erfolg. Vllt hab ich auch etwas übersehen und finde es nicht
mehr weil ich blind geworden bin.
Danke für eure Hilfe!
Hallo,
ich denke Du solltest auch erklären nach welcher Methode dein
Frequenzzähler funktionieren soll.
Welches Problem besteht genau ? Das wird nicht beschrieben !
So etwas ist unsinnig, da ja gerade jetzt keine globale
Interruptfreigabe vorhanden ist.
1
/* Save global interrupt flag */
2
sreg=SREG;
3
/* Disable interrupts */
4
cli();
5
volatileuint16_ttimer1_low_val=TCNT1;
6
SREG=sreg;
Über den Einsatz von
1
volatile
musst du nochmal nachdenken. Da sind bestimmt einige zu viel.
Danke für die Anmerkung ich habe alles versucht wie gesagt. Da ich mit
meinen Latein am Ende war. Sicherlich ist es nicht nötig, aber ich habe
es vorsichthalber mit reingetan.
Der Frequenzzähler arbeitet wie folgt:
Es gibt einen 32-Bit Timer der aus zwei 16 Bit Timern besteht:
timer1_low_val entspricht dem TCNT1 Register
timer1_high_val wird bei OVF des Timer1 hochgezählt
bei negativer Flanke wird der neue Timer-Wert gespeichert der davor als
t0 und t1 gespeichert
bei Aufruf von getFreqValue(uint8_t id) werden t0 und t1 verglichen und
dabei die Frequenz berechnet.
Das Problem ist, dass die Frequenzzähler Aussetzer hat, und machmal nur
ein Viertel der tatsächlichen Frequenz ausgibt und das sind ziehmlich
genau ein Viertel. Ich verstehe aber nicht warum...
Danke für deine Mühe
das ist falsch.
Wenn du unsigned rechnest (was grundsätzlich richtig ist), dann
errechnet sich die diff in allen Fällen zu
1
udiff0=t0-t1;
das Ergebnis ist auch dann richtig, wenn es einen Overflow gibt.
Die Sache mit dem sei und cli ist schon angesprochen worden. Das hat in
einer ISR nichts verloren. Um das Interrupt Flag brauchst du dich in
einer ISR nicht kümmern. Während eine ISR läuft, sind Interrupts sowieso
abgeschaltet und werden beim Beenden der ISR Funktion automatisch wieder
aktiviert.
>> das Ergebnis ist auch dann richtig, wenn es einen Overflow gibt.
(Und tu dir einen Gefallen und nenn die Dinge nicht irgendwas0 und
irgendwas1. Da muss man ständig aus dem Code entnehmen welches jetzt der
'alte' Wert ist und welches der 'Neue'. Das ist nur eine sprachliche
Falle, auch für dich selbst.
trm_1904 schrieb:> Das Problem ist, dass die Frequenzzähler Aussetzer hat, und machmal nur> ein Viertel der tatsächlichen Frequenz ausgibt und das sind ziehmlich> genau ein Viertel. Ich verstehe aber nicht warum...
Beobachte mal, ob die Aussetzer einigermassen regelmässig kommen. Ist
ein Muster erkennbar?
(lass dir auch mal an die Anzeige t1 ausgeben)
Karl Heinz schrieb:> trm_1904 schrieb:>>> Das Problem ist, dass die Frequenzzähler Aussetzer hat, und machmal nur>> ein Viertel der tatsächlichen Frequenz ausgibt und das sind ziehmlich>> genau ein Viertel. Ich verstehe aber nicht warum...>> Beobachte mal, ob die Aussetzer einigermassen regelmässig kommen. Ist> ein Muster erkennbar?>> (lass dir auch mal an die Anzeige t1 ausgeben)
Du kennst ja offenbar den Wert der sich ergeben müsste. Zumindest so
ungefähr. Wenn sich also eine Frequenz errechnet, die kleiner als sagen
wir mal die Hälfte dieses erwarteten Wertes ist, dann lässt du dir t1
ausgeben. Sonst wirst du vermutlich mit Werten geflutet und kannst nicht
sagen, welcher Zählerstand bei deinen falschen Werten vorgelegen hat.
eine Race Condition.
Wenn der Timer kurz vor dem nächsten Puls knapp vor dem Überlauf stand
dann kann dir die Situation passieren, dass
* die ISR aufgerufen wird
* während sich der µC bis zur Stelle vorarbeitet, an der du TCNT1 holst
läuft der Timer natürlich weiter, erreicht seinen Overflow und beginnt
wieder bei 0. Der springende Punkt ist nun, dass du hier ja in einer ISR
bist. D.h. die Overflow-ISR muss noch warten, bis die hier abgearbeitet
ist. Als Konsequenz davon hast du mit TCNT1 einen Wert von 0, 1, 2, ...
(etwas sehr kleines), aber timer1_high_val ist noch nicht erhöht, weil
der Overflow Interrupt noch keine Chance hatte.
(genau deshalb wären diese Zähler Werte alle interessant. Und zwar dann,
wenn es zu einer Fehlmessung kommt. Das Problem ist real, das hab ich
jetzt nicht erfunden. Aber ich kann nicht sagen, ob es genau dieses
Problem ist, das dir zu schaffen macht)
Musst du denn die Frequenz wirklich so genau messen?
Die Probleme werden nicht kleiner, wenn man die Genauigkeit bzw.
Auflösung in die Höhe treibt. Schon alleine das rumwerfen mit uint32 ist
für den AVR nicht so toll. Würde es nicht reichen, wenn du mit 16 Bit
Werten hantieren könntest? Dann bräuchtest du die ganze Overflow
Problematik gar nicht weiter behandeln.
Ich denke, die zwei gröbsten Fehler sind die schon erwähnte falsche
udiff-Berechnung und das falsche Auslesen des Timers in der ISR. Du
kannst nicht einfach das low-Word aus dem Register lesen und dann
irgendwann später mal das high-Word aus der Variablen -
zwischenzeitliche Overflows liefern dann falsche Werte. Google mal nach
AVR 32bit Timer.
Und schmeiss bitte die volatiles für lokalen Variablen raus - das tut
weh ;-)
Vielen Dank für eure Anmerkungen. Ich habe nun endlich eine
funktionierende Version. Ich habe statt dem Timer1 Timer0 verwendet.
Statt die Differenz zweier uint32 Zahlen zu bilden, zähle ich jetzt mit
f0_n_ref_actual hoch und setze ihn bei der negativen Flanke 0, dabei
speicher ich auch f0_n_ref_actual in f0_n_ref_stored. Ich denke der
Fehler lag beim auslesen von TCNT1. Aber da es jetzt so funktioniert bin
ich glücklich und zufrieden. Am Oszi habe ich noch bebachten können,
dass der Fehler dann auftrat wenn PCINT ISR knapp vorher aufgerufen
worden war.
Ich denke, du hast immer noch Raceconditions, nur jetzt etwas kleinere
Zeitfenster. Und das resetten des Overflowcounters klappt so nicht: du
müsstest auch TCNT0 resetten und evtl ausstehende Overflowinterrupts.
Klappt dann natürlich nicht mehr bei mehreren Pins. Ausserdem muss das
Auslesen in getFreqValue atomar sein - bei 32-bit-Werten kann das der
AVR nicht. Also cli/sei drum herum.
Ich tipp hier mal ein, wie ich das angehen würde (completely untested!):
1
volatilestaticuint32_tcycles[1];
2
staticuint32_ttime_prev[1];
3
staticuint32_ttimer0_ovf;
4
staticuint8_tpins_prev;
5
6
ISR(TIMER0_OVF_vect)
7
{
8
timer0_ovf+=256;
9
}
10
11
ISR(PCINT2_vect)
12
{
13
uint8_tcnt,ifr,pins,edges;
14
uint32_ttime;
15
16
cnt=TCNT0;
17
ifr=TIFR;
18
pins=PIND;
19
20
time=timer0_ovf+cnt;
21
if(ifr&(1<<TOV0)&&~cnt&0x80)// overflow before reading TCNT0
22
time+=256;
23
24
edges=pins_prev&~pins;// falling edge
25
pins_prev=pins;
26
27
if(edges&(1<<PD5))
28
{
29
cycles[0]=time-time_prev[0];
30
time_prev[0]=time;
31
}
32
}
33
34
uint16_tgetFreqValue(uint8_tid)
35
{
36
uint32_tn;
37
38
cli();
39
n=cycles[id];
40
sei();
41
42
return25000000ul/n;
43
}
Die 1er-Arrays um anzuzeigen, welche Variablen bei mehreren Pins
anzupassen wären. Btw, wenn der 16-Bit-Timer frei ist, würd ich eher
den nehmen - generiert weniger Overflowinterrupts. Noch besser
natürlich Karl-Heinz' Hinweis: gar keine Overflows ;-)
[Die Overflow-Handling-Idee kommt übrigens aus:
Beitrag "AVR Timer mit 32 Bit"]