PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Timer1 ISRs Genauigkeit



PCMan
01.04.2011, 13:40
Hallo Experten :)

ich habe da mal eine Prinzipfrage. Ich habe zwei Timers am Laufen (T1 und T2). T1 erzeugt mir einen IRQ alle Sekunde, der T2 läuft auf einer kleineren Frequenz.

Im Timer1 ISR wird nur ein Port getoggled und der Preloader neu gesetzt. Im T2 wird nur ein Flag-Bit gesetzt und auch der Preloader neu gesetzt.
Die Auswertung des Flags erfolgt im Hauptprogramm. Ist das Bit gesetzt, werden Zeichen an's LCD gesendet (lcd_putc()) (P. Fleury) und ein bisschen mit strcpy und sizeof gearbeitet.

Mir ist aufgefallen, dass wenn ich das Setzen des Busy-Flags im T2-ISR rauslasse, mein Timer1 wirklich präzise meinen Port toggled (nachgemessen). Wird das Flag dagegen gesetzt, dann hängt mein T1 immer wieder ein bisschen hinterher <-> ungenau. Folglich liegt die ungenauigkeit von T1 nicht am T2-Überlauf, aber an irgendetwas anderem.

Habt ihr eine Idee, ob sizeof, strcpy oder die LCD-Routinen irgendwie die Interrupts sperren/blockieren könnten?

Besten Dank,
Simon

sternst
01.04.2011, 13:57
Was genau meinst du mit "Preloader neu gesetzt"? Wenn du den Timer "von Hand" vorlädst, hast du eh nur durch Glück einen genauen Zeitabstand zwischen den Interrupts. Benutze den CTC-Mode.

Besserwessi
01.04.2011, 14:59
Wie schon gesagt, besser ist der CTC Modus. Mit Preload in der ISR kriegt man nur gleichmäßige Zeiten, wenn man einen hohen Prescaler hat. Mit 2 ISRs muss der Prescaler für eine ISR und den Anfang der anderen reichen. Da wird dann auch ein Prescaler von 64 schon knapp, bei 256 als Prescaler sollte es aber reichen.

PCMan
05.04.2011, 21:06
Hi,

okay, also ich habs jetzt geschafft den T1 im CTC modus alle Sekunde in den Compare Match Vector springen zu lassen.

Den anderen Timer (T0) betreibe ich bisher noch "klassisch" über den Überlauf. Der T0 funktioniert aber nur dann, wenn ich den T1 unkonfiguriert lasse.

Gibt's dafür irgendeinen Grund wieso T1 im CTC Modus die anderen Timer affektiert?

Merci,
Simon

PCMan
05.04.2011, 21:26
Hab den "Fehler" gefunden. Offensichtlich muss man den Timer, der im CTC Modus laufen soll in der Reihenfolge vor jenen Konfigurieren, die "normal" laufen. Verstehe ich zwar nicht aber das hat das Problem irgendwie gelöst...
Ciao,
Simon

sternst
05.04.2011, 21:49
Offensichtlich muss man den Timer, der im CTC Modus laufen soll in der Reihenfolge vor jenen Konfigurieren, die "normal" laufen. Ne, muss man nicht.
Du hast einen Fehler in deinem Code. Da du den ja aber nicht zeigst, kann ich dir auch nicht sagen welchen genau. Aber wenn ich raten soll, würde ich sagen, dass sich die beiden Timer ein Interrupt-Enable-Register teilen, und du bei der Konfiguration des einen Timers dieses mit einem "=" beschreibst, und beim anderen mit einem "|=".

PCMan
06.04.2011, 08:13
Hi,
alles klar.
Stimmt, Code sollte ich mal posten. Werde ich heute Abend nachholen, sorry.
Besten Dank soweit,
Simon

PCMan
07.04.2011, 15:53
Hi,
etwas verspätet, aber trotzdem. Ihr hattet Recht, es lag am Code und einer fehlenden Veroderung im TIMSK-Register. Jetzt geht's:


//Timer0 so Langsam wie möglich
TCCR0 |= (1<<CS02) | (1<<CS00); //Prescaler=1024
TCNT0 = 0;
TIMSK |= (1<<TOIE0);

//timer 1 auf sekundentakt
TCCR1B = ( (1 << CS12) | (1 << CS10)| (1 << WGM12) ); // Prescaler auf 1024 und CTC mode akivieren
OCR1A = 15625; // wert für 1s vorladen
TIMSK |= (1 << OCIE1A) ; // Output Compare Interrupt aktiveren


Ich habe eine andere Frage, und zwar kann ich den Timer2 ja ebenfalls im CTC Modus laufen lassen. Jetzt ist es so, dass ich Frequenzen im Bereich von 60Hz bis 10kHz rausgeben möchte. Der AVR ist mit einem 16MHz Quarz getaktet. Ich dachte Anfangs, ich könnte das einfach so realisieren:


//setze Timer2 in den CTC Modus und lasse jede µs ISR auslösen.
TCCR2 |= (1<<CS20) | (1<<WGM21); // Prescaler von 1 | CTC-Modus
OCR2 = 8; // Vergleichswert für 1µs Periode
TIMSK |= (1<<OCIE2); // Interrupts aktivieren und damit Timer starten


Im ISR Vector hatte ich dann vor, den Port immer wenn eine gewisse Periode erreicht wurde zu togglen:


ISR(TIMER2_COMP_vect)
{
T2count++;
if (T2count == T2period)
{
MOT_A_PORT ^= (1<<MOT_A_C_PIN);
MOT_B_PORT ^= (1<<MOT_B_C_PIN);
T2count = 0;
}
}


Nachdem ich das ausprobierte ging das zwar, aber mir fiel auf, dass egal was ich für einen Wert in OCR2 schreibe, ich keinen Einfluss habe. Nach Überlegungen bin ich drauf gekommen, dass es sein kann, dass alles quasi zu schnell läuft, sprich viele ISRs verschluckt werden, da die Ausführung und Einsprung in die ISR ja wieder Zyklen kostet.
Deswegen wollte ich mir diese Form der Realisierung abschminken. Hättet ihr einen klugen Lösungsansatz, mit dem ich quasi ausgehend von der gewünschten Frequenz den passenden Prescaler und Vorladewert quasi "autodetecten" lassen könnte? (Die Formeln zur Berechnung aus dem Datenblatt kenne ich, aber das Ganze kam mir zu unelegant vor...)

Besten Dank,
Simon

PCMan
07.04.2011, 23:00
Hallo nochmal,
also falls es jemanden interessiert, ich habe das ganze für Meine Zwecke (Ausgabefrequenz habe ich mal auf zwischen 35 bis 2100 Hz festgelegt) folgendermaßen realisiert:



//toggled zwei Ausgänge
ISR(TIMER2_COMP_vect)
{
MOT_A_PORT ^= (1<<MOT_A_C_PIN);
MOT_B_PORT ^= (1<<MOT_B_C_PIN);
}

...

uint8_t autodetect(uint16_t *ps, uint8_t *ocr, uint16_t freq)
{
uint32_t i, ocra;
uint32_t nenner;
uint32_t prescale[] = {1,8,32,64,128,256,1024}; //mögliche Values für Timer2 lt Datenblatt...

for (i = 0; i < 7; i++)
{
nenner = 2*prescale[i]*freq;
ocra = round((16000000/nenner));

//wenn Wert gefunden wurde, der in OCRA passt, schreiben und Funktion beenden
//nach Tests war immer der erste ermittelte Wert der genaueste für die Ausgabefrequenz. Alle folgenden waren ungenauer...
if (ocra < 255)
{
*ps = prescale[i];
*ocr = ocra;
return 0;
}
}

return 1;
}

...
// irgendwo im Programm

uint8_t ocr_value;
uint16_t prescaler;
if (autodetect(&prescaler, &ocr_value,isr_frequency) == 1) return 1;
else
{
if (prescaler == 1) TCCR2 |= (1<<CS20);
else
if (prescaler == 8) TCCR2 |= (1<<CS21);
else
if (prescaler == 32) TCCR2 |= (1<<CS21) | (1<<CS20);
else
if (prescaler == 64) TCCR2 |= (1<<CS22);
else
if (prescaler == 128) TCCR2 |= (1<<CS22) | (1<<CS20);
else
if (prescaler == 256) TCCR2 |= (1<<CS22) | (1<<CS21);
else
if (prescaler == 1024) TCCR2 |= (1<<CS22) | (1<<CS21) | (1<<CS20);
}

TCCR2 |= (1<<WGM21); //setze CTC Modus
OCR2 = ocr_value;
TIMSK |= (1<<OCIE2); //starte Timer


Bin für Kritik natürlich offen.
Viele Grüße,
Simon

sternst
08.04.2011, 00:55
Bin für Kritik natürlich offen.Ok, dann lege ich mal los. ;-)

* Warum ist i ein uint32_t? uint8_t würde reichen.

* Warum das uint32_t bei prescale[]? uint16_t würde reichen.
(wenn du das änderst, muss aber auch die Zeile der nenner-Berechnung geändert werden)

* Warum ist prescale[] eine automatic- und keine static-Variable?

* Wozu soll die Funktion round() da gut sein? "16000000/nenner" ist eine Integer-Division und hat immer ein ganzzahliges Ergebnis. Da gibt es für round() rein gar nichts zu runden.

All diese Punkte produzieren keinen Fehler, drücken aber auf die Performance. Was allerdings ein Fehler ist, ist dass du bei der OCR-Berechnung für den CTC-Modus ein "-1" vergisst.

Noch was: Du scheinst 0 für "Erfolg" und 1 für "Fehler" zu verwenden. Das finde ich etwas ungeschickt, weil es genau entgegen der boolschen Logik ist (0 = False, 1 = True). Ich würde auch nicht den Prescaler-Wert speichern, sondern den Index, denn den kann man im restlichen Code dann direkt verwenden.

So in etwa würde die Funktion bei mir aussehen:

uint8_t autodetect (uint8_t *ocr, uint8_t *pre, uint16_t freq) {

static uint16_t prescale[] = {1,8,32,64,128,256,1024}; //mögliche Values für Timer2 lt Datenblatt...

for (uint8_t i = 0; i < 7; i++) {
uint32_t nenner = 2UL * prescale[i] * freq;
if (nenner >= (F_CPU/256)) {
*ocr = ((F_CPU+(nenner/2)) / nenner) - 1;
*pre = i + 1;
return 1;
}
}

return 0;
}
Und im restlichen Code dann:

uint8_t prescaler_index;
uint8_t ocr_value;
if (!autodetect(&ocr_value,&prescaler_index,isr_frequency))
return 0;
TCCR2 |= (prescaler_index<<CS20);
OCR2 = ocr_value;

PCMan
08.04.2011, 09:02
Hi Sternst,
danke für den Input. Dass alle Variable uint32_t sind war reine Faulheit. Ich habe vorher, wie du geschrieben hast, alle Variablen auf die nötigen Größen reduziert gehabt. Allerdings hat die Rechnung dann Müll produziert und der Fehler schien durch die stupide Erweiterung aller Variablen auf 32 gelöst. Ich weiß, wahrscheinlich hat das das Problem nicht gelöst, sondern nur verschoben.

Gegenfragen:
Wenn ich die Integerdivision durchführe bekomme ich durchaus keine ganzzahligen Ergebnisse. Das ganze wird dann durch die Zuweisung an einen int ganzzahling gemacht. Da aber immer die Nachkommas dabei abgeschnitten werden wollte ich das mit round genauer machen. Wieso sollte das nix bringen?

in dem if-statement, welchen Vorzug bringt:

if (nenner >= (F_CPU/256)) gegenüber
if (F_CPU/nenner) <= 256 ?

Kannst du mir bitte erklären, wie du auf diese Zeile kommst:

*ocr = ((F_CPU+(nenner/2)) / nenner) - 1;
Ich dachte lt. Datenblatt ist OCR = (F_CPU / (2 * Prescaler * Frequenz)) - 1

Wozu brauche ich bei dem prescaler index noch +1 und wozu das static?

Danke,
Simon

sternst
08.04.2011, 10:56
Ich habe vorher, wie du geschrieben hast, alle Variablen auf die nötigen Größen reduziert gehabt. Allerdings hat die Rechnung dann Müll produziertWeil (wie ich schon schrieb) in dem Fall die Berechnungszeile etwas angepasst werden muss. Ohne Anpassung wird die Berechnung dann in uint16_t durchgeführt und läuft über. Man muss sie in uint32_t erzwingen, was in meinem Code das "UL" hinter der "2" erledigt.



und der Fehler schien durch die stupide Erweiterung aller Variablen auf 32 gelöst. Ich weiß, wahrscheinlich hat das das Problem nicht gelöst, sondern nur verschoben.Ne, der Fehler ist damit tatsächlich behoben, nur halt unnötig aufwändig.



Wenn ich die Integerdivision durchführe bekomme ich durchaus keine ganzzahligen Ergebnisse.Das ist schlicht falsch. Das Ergebnis einer Integer-Division ist wieder ein Integer (IMMER!), also ganzzahlig.



Da aber immer die Nachkommas dabei abgeschnitten werden wollte ich das mit round genauer machen. Wieso sollte das nix bringen?Weil das Abschneiden nicht erst bei der Zuweisung passiert, sondern bereits bei der Division selber (siehe vorigen Quote). Das round() bekommt schon keine Nachkommastellen mehr zu sehen.



in dem if-statement, welchen Vorzug bringt:

if (nenner >= (F_CPU/256))gegenüber

if (F_CPU/nenner) <= 256Bei "F_CPU/256" sind beide Operanden Konstanten, daher wird die Division bereits beim Compilieren durchgeführt und durch die resultierende Konstante ersetzt. Bei "F_CPU/nenner" muss die Division zur Laufzeit durchgeführt werden.



Kannst du mir bitte erklären, wie du auf diese Zeile kommst:

*ocr = ((F_CPU+(nenner/2)) / nenner) - 1;Ich dachte lt. Datenblatt ist OCR = (F_CPU / (2 * Prescaler * Frequenz)) - 1Richtig. Meine Zeile folgt genau aus der Formel, nur dass mit dem zusätzlichen Teil "+(nenner/2)" das Integer-Ergebnis gerundet wird (das was du mit round() versucht hast).



Wozu brauche ich bei dem prescaler index noch +1Weil das Array mit "Index 0 = Prescaler 1" beginnt, während die Prescaler-Einstellung im Register mit "Index 0 = Timer gestoppt" beginnt.



wozu das staticOhne das "static" wird das Array bei jedem Aufruf der Funktion neu angelegt und neu mit Werten befüllt. Da aber der Inhalt des Arrays in der Funktion ja gar nicht verändert wird, ist das unnötig. Mit dem "static" passiert das nur einmal am Anfang des Programms.

PCMan
08.04.2011, 11:24
Hi,

besten Dank, sehr eingebend.
Nur diese eine Zeile
*ocr = ((F_CPU+(nenner/2)) / nenner) - 1; wurmt mich noch. In wie fern rundet man dort?

Mit nenner = 2UL*Prescaler*Frequenz ergäbe sich in der Zeile:
*ocr
= ((F_CPU + (2*Prescaler*Frequenz/2)) / 2*Prescaler*Frequenz) -1
= (F_CPU + Prescaler*Frequenz) / (2*Prescaler*Frequenz) -1
= (F_CPU/2*Prescaler*Frequenz + 1/2) -1;

oder hauen mich meine Mathe"kenntnisse" über's Ohr?

Ciao,
Simon

sternst
08.04.2011, 12:00
Nur diese eine Zeile
*ocr = ((F_CPU+(nenner/2)) / nenner) - 1;wurmt mich noch. In wie fern rundet man dort?
"Eigentlich" müsste die Zeile so aussehen:

*ocr = (F_CPU / nenner) - 1; Um das Zwischenergebnis der Division zu runden, habe ich das "+(nenner/2)" hinzugefügt. Wie das funktioniert, kannst du leicht selber herausfinden. "A/B" im Vergleich zu "(A+(B/2))/B". Probiere es einfach mit ein paar Werten, dann siehst du sofort das Prinzip (wenn nicht, frag nochmal).

PS: Deine mathematische Umformung mag richtig oder falsch sein (habe ich mir nicht näher angeschaut), ist aber irrelevant. Denn dort wird nicht berücksichtigt, dass beim "/" der Nachkommaanteil "weggeschmissen" wird. Du kannst eine Integer-Rechnung mit "/" darin nicht einfach nach mathematischen Regeln umformen.
Einfaches Beispiel: "1/2" hat als Ergebnis 0. Du formst es mathematisch um in "1 - 1/2", und schon ist das Ergebnis 1.

PCMan
08.04.2011, 12:43
Besten Dank, sternst.
Wieder was gelernt.
Ciao,
Simon

Bot-Builder
09.04.2011, 18:45
Hallo Stefan,
Hallo Simon,

ich habe euren Thread hier interessiert verfolgt.

Den ocr_Wert über eine Funktion zu bestimmen ist für meinem Fall zu aufwendig. Ich brauche keine so weite Bandbreite an möglichen Frequenzen. Bei mir wechselt nur die Taktfrequenz vom ATMega. Ich wollte also den ocr_Wert mit einer Präprozessor #define berechnen. Und habe folgenden Code verwendet:


....

// CTC-Modus
TIMSK |= (1 << OCIE0);

// ocr_Wert setzen
#define ocr_Wert ((F_CPU/(2 * 1024 * 50))-1)
// #define ocr_Wert 71
#if ((Vergleichswert > 256) || (Vergleichswert < 0))
#error "Fehler bei der Initialisierung Timer0!"
#endif

OCR0 = ocr_Wert;

....Der aber fehlerhaft sein muss. Ich erhalte als Frequenz ca. 14hz, was bei einem Quarz mit 7,3728Mhz auf einen ocs_Wert von 255 schließen läßt.:-k

Der übrigen Code ist aber richtig und funktionsfähig. Wenn ich #define ocs_Wert 71 schreibe (wie auskommendiert) erhalte ich exakt die gewünschten 50hz an einem Ausgangspin.

Das Problem hat vermutlich was mit der Berechnung zu tun: Weil der gcc warning: integer overflow in expression für die Zuweisung OCR0 = ocr_Wert; auswirft.

Präpozessor #defines sind reine Textersetzungen und keine eigenen Berechnungen. So weit klar. Aber der Ausdruck wird/sollte doch vom Compiler als Konstante gesehen werden und auch so gehändelt werden. Wird aber offensichtlich nicht. Des im Ausdruck mit uint16_t gerechnet wird, ist ebenfalls klar. Aber das Ergebniss ist doch eine uint8_t und müsste zu OCR0 passen. Habe ich mir jedenfalls so gedacht, was aber wie gesagt offensichtlich falsch ist. Auch alle Versuche den Ausdrucken Typen zu zu weisen, haben nichts gebracht.:(

Was mache ich dann da noch falsch? Hätte jemand eine Idee?




uint32_t nenner = 2UL * prescale[i] * freq;


@Stefan:
Die Wirkung von 2UL * habe ich zwar verstanden, die Hintergründe aber nicht wirklich. Wo kann man sich da mal schlau lesen? Hättest Du einen Tip für mich.

Auf alle Fälle schon mal vielen Dank.


mfg

Uwe

sternst
09.04.2011, 20:31
Aber der Ausdruck wird/sollte doch vom Compiler als Konstante gesehen werden und auch so gehändelt werden. Wird aber offensichtlich nicht.Doch, wird er, aber auch diese Konstante muss ja erst vom Compiler berechnet werden. Und diese Berechnung geschieht natürlich nach den C-Regeln, damit das gleiche dabei rauskommt, wie wenn die Berechnung erst zur Laufzeit passieren würde. Im Gegensatz zur Berechnung zur Laufzeit kann der Compiler hier aber direkt sehen, dass es zu einem Überlauf kommt, und dich warnen.



Des im Ausdruck mit uint16_t gerechnet wird, ist ebenfalls klar.Ne, ist nicht klar, und auch falsch. Der Teil in der Klammer wird in int gerechnet (und da passiert auch gleich der Überlauf). Das (bereits falsche) Zwischenergebnis wird dann nach long promotet, und der Rest in long weiter gerechnet (außer natürlich dein F_CPU ist ungewöhnlich klein).



Die Wirkung von 2UL * habe ich zwar verstanden, die Hintergründe aber nicht wirklich.Und auch in deinem Fall hier würde ein UL (oder auch nur L) hinter einer der Zahlen in der Klammer weiterhelfen. Dadurch, dass du einen der Operanden in einen größeren Typ zwingst, erzwingst du, dass die Rechnung in der Klammer insgesamt in einem größeren Typ stattfindet.

Bot-Builder
10.04.2011, 10:55
Hallo Stefan,

ich habe mich jetzt versucht mal schlau zu machen. Habe aber noch nicht alles verstanden.


Ne, ist nicht klar, und auch falsch. Der Teil in der Klammer wird in int gerechnet (und da passiert auch gleich der Überlauf). Das (bereits falsche) Zwischenergebnis wird dann nach long promotet, und der Rest in long weiter gerechnet (außer natürlich dein F_CPU ist ungewöhnlich klein).


Nach Deinen Hinweis ist die Sache zumindest einigermaßen klar, habe einen Fehler in den Berechnungen per Taschenrechner gemacht. (2 * 1024 * 5) wird vom Compiler als uint_16 berechnet, da die längste Zahl in diesem Ausdruck uint_16 ist. Das Ergebniss dieser Multiplikation sprengt aber uint_16. Anstelle des richtigen Ergebniss 102.400 wird mit 36.864 weitergerechnet. Die dann folgende Division F_CPU / 36.864 ist dann natürlich auch fehlerhaft. In meinem Fall ergibt sich dann 7372800 / 36.864, also 200. -1 dann, also 199. Stelle ich mir das so richtig vor? Bin mir nicht sicher, weil ich mit meiner nicht funktionieren Formel ca. 14hz gemessen habe, bei 199 müsste sich aber 18hz messen lassen. Kann aber auch an meinem Messgerät liegen. Im unteren Frequenzberich sind die Abweichungen manchmal größer.



Und auch in deinem Fall hier würde ein UL (oder auch nur L) hinter einer der Zahlen in der Klammer weiterhelfen. Dadurch, dass du einen der Operanden in einen größeren Typ zwingst, erzwingst du, dass die Rechnung in der Klammer insgesamt in einem größeren Typ stattfindet.

Okay. L zwingt man einer Zahl einen Datentyp auf. Mit L eine uint_32, mit UL eine uint_64? Richtig so? Weil ich leider nicht viel darüber gefunden habe. Ist das so wie eine CAST-Operator zu verstehen? Welche anderen Typkennzeichen gibt es denn noch? Oder hättest Du einen Tip, wo ich mich da schlau machen könnte?


#define ocr_Wert ((F_CPU / (1024 * 2 * 50L)) - 1)
Was aber am wichtigsten ist, der Code funktioniert jetzt!:p

Vielen Dank für Deine Tips.

mfg

Uwe

sternst
10.04.2011, 23:46
(2 * 1024 * 5) wird vom Compiler als uint_16 berechnetNein, als int (int16_t).



Anstelle des richtigen Ergebniss 102.400 wird mit 36.864 weitergerechnet.Nein, mit -28672.
Für die weitere Rechnung wird das nach uint32_t promotet, was 4294938624 ist. Das Ergebnis der Division ist damit 0. Und 0 - 1 in uint32_t ist 4294967295. Das wird dann nach uint8_t gecastet, und wir landen schlussendlich bei 255.



Mit L eine uint_32, mit UL eine uint_64? Richtig so?Nein, L steht für long (int32_t), und UL für unsigned long (uint32_t).



Ist das so wie eine CAST-Operator zu verstehen? Ja.



Oder hättest Du einen Tip, wo ich mich da schlau machen könnte?In jedem halbwegs brauchbaren C-Buch.