Hallo, irgendwie komme ich nicht hinter folgendes Problem: Randbedingungen: ATMEGA8 im STK500 Board, ISP Programmierung mit Studio 4 und WinAVR auf Win XP Rechner. Neuer Controller eingestellt auf internen Oszylator mit 8MHz (CKSEL=0100 SUT=10). Verwendetes Programm wie angehängt. Code optimierung -00. Für eine PID Reglung möchte ich eine konstante Zykluszeit haben die unabhänig von der Laufzeit des Programms ist. Zumindestens solange die Ausführung des Programms kürzer als die Zykluszeit ist. Da es zu Problemen gekommen ist habe ich alles bis auf die Erzeugung der Zykluszeit reduziert und das ist das angehängte Programm: Nach der Initialisierung vom Timer0 und Pin PB1 als Ausgang gehts in die Endlosschleife. Hier wird gewartet bis der Timer0 320 mal durchgezählt hat. Die ISR zählt dazu bei jedem Timer 0-Durgang hoch. Ist 320 erreicht gibt es einen Impuls (Spike) am PB1 damit ich mit dem Ossy nachmessen kann. Und hier ist das Problem: 1.) Der Takt ist unregelmäßig und variiert zwischen 10mS und 8mS. Warum und was kann ich dagegen tun? 2.) Um eine Zykluszeit von 10mS zu erreichen musste ich den Timer Takt ohne prescaling lediglich durch 320 teilen. Ich habe aber eine Oszylatorfrequenz von 8MHz, die geteilt durch 320 entspricht 40µS. Wo wird die Frequenz verringert und wo kann man das nachlesen? Mit freundlichen Gruß Aurel
> 2.) Um eine Zykluszeit von 10mS zu erreichen musste ich den Timer > Takt ohne prescaling lediglich durch 320 teilen. Ich habe aber > eine Oszylatorfrequenz von 8MHz, die geteilt durch 320 entspricht > 40µS. Wo wird die Frequenz verringert und wo kann man das > nachlesen? Timer0 ist ein 8bit-Timer, zählt also pro 256 Schritte bis zum Überlauf. Alle 320 Überläufe wird der Impuls erzeugt. 8 Mhz / 256 = 31,25 kHz 31,25 kHz / 320 = 97,65 Hz 1 / 97,65 Hz = 10,24 ms Mal abgesehen davon, daß Wartschleifen in C bäh sind, sehe ich kein Problem im Code.
Ja logisch, der Timer selbst Teilt ja schon mit 256 und nicht mit 1. Danke, jetzt ist das erstes Brett von Kopf weg! Warteschleifen in C lieber mit _delay? Hier dient es lediglich zu testzwecken damit ich den Impuls auf dem Ossy sehen kann. Prima, dann muss nur noch einer das mit dem unregelmäßigen Takt herausbekommen. Dann kann ich wieder weiter. Gruß Aurel
> Warteschleifen in C lieber mit _delay?
Nee, das ist noch... wie heißt die Steigerung von "bäh"? In Deinem
Fall, wenn das Programm in der Zwischenzeit eh nichts anderes zu tun
hat als warten, ist das so schon OK (jedenfalls besser als mit
_delay_xx()...).
Nö, sinnvoller wäre es, beim Erreichen von 319 den Pin auf high zu ziehen, dann bei 320 den Pin auf low und die Zählvariable rücksetzen. Der Impuls ist dann breiter (ist sowieso besser für das Oszillofon), aber er braucht keine CPU-Zeit mehr für seine Erzeugung, und man kann die CPU den Rest der Zeit auch getrost schlafen legen.
meine Vermutung: Der IR tritt auf zwischen dem Vergleich von High- und Low-Byte der 16-Bit-Variable ZeitCount. Du solltest während dem Vergleich die IR sperren. uint8_t Zeitcount_kleiner_320(void){ cli(); if (ZeitCount < 320){ sei(); return(1); } else{ sei(); return(0); } } while(1){ while (ZeitCount_kleiner_320 ); // ca. 10mS Takt ZeitCount = 0; PORTB &= ~(1<<PB1); // Spike an for (i=0;i<50;i++); // Spike Breite PORTB |= (1<<PB1); // Spike aus } Viel Spass, Stefan
>> Warteschleifen in C lieber mit _delay? > Nee, das ist noch... wie heißt die Steigerung von "bäh"? Wie bist du auf den Blödsinn gekommen? > In Deinem Fall, wenn das Programm in der Zwischenzeit eh nichts > anderes zu tun hat als warten, ist das so schon OK (jedenfalls > besser als mit _delay_xx()...). Warum sollte was handgeschraubtes besser sein als die extra dafür vorgesehenen Bibliotheksfunktionen? Die _delay-Funktionen haben den Vorteil, nicht einfach vom Compiler wegoptimiert zu werden, wenn man Optimierungen einschaltet. Abgesehen davon produzieren sie eine nachvollziehbare Wartezeit, weil sie in Assembler implementiert sind und daher eine bekannte Zahl an Taktzyklen pro Durchlauf brauchen. Wie Jörg schon schreibt, ist es meistens besser, gar nicht zu warten, sondern sowas vom Timer erledigen zu lassen. Wenn man dann aber doch warten will, dann doch wenigstens mit den delay-Funktionen der Bibliothek.
> Neuer Controller eingestellt auf internen Oszylator mit 8MHz ... > 1.) Der Takt ist unregelmäßig und variiert zwischen 10mS und 8mS. > Warum und was kann ich dagegen tun? das sind ca 20%. Das würde ich dem internen Oszillator durchaus zutrauen. Die sind nicht so genau.
@Rolf: Ich meinte damit eigentlich, dass eine Wartezeit mit Timer (wie er es ja im Prinzip gemacht hat) einer _delay-Routine vorzuziehen ist! Und ich kenne keinen Compiler, der in der Lage wäre, einen ganzen Timer wegzuoptimieren...
Für dein Impuls hat er aber eine Warteschleife benutzt, und um die ging es ja. Die Frage war, ob er die durch einen delay-Aufruf ersetzen soll. > ich kenne keinen Compiler, der in der Lage wäre, einen ganzen > Timer wegzuoptimieren... Naja, in der Lage dazu wäre er schon. Würde er auch normalerweise machen, wenn nicht die passenden Vorkehrungen getroffen worden wären. Da die Interrupt-Routinen nie explizit aufgerufen werden, kann er ohne diese Vorkehrungen auf die Idee kommen, daß sie nicht gebraucht werden und sie weglassen.
> das sind ca 20%. Das würde ich dem internen Oszillator durchaus > zutrauen. Keinesfalls, zumindest nicht, solange Vcc nicht gerade aus ungeglätteten 100 Hz besteht... Wir benutzen im Lab zu Debugzwecken den RC-Oszillator die ganze Zeit zum Ansteuern einer RS-232. Mit einmaliger Kalibrierung läuft das wenigstens einige Stunden problemlos.
Nun habe ich Feierabend und gleich mal eure Vorschläge ausprobiert. Und es funktioniert! Es scheint so als hat der Interrupt gerade immer die ZeitCount Abfrage gestört. Man man was für ein Zufall, hätte ich Lotto gespielt hätte ich auch bestimmt 'ne Milionen gewonnen. Das modifizierte, funktionierende Programm habe ich beigelegt. Jetzt gehts übrigens auch mit laufenden Interrupt (-: Also klasse und vielen Dank an euch, echt ein super Forum hier! Gruß Aurel
> Man man was für ein Zufall, hätte ich Lotto gespielt hätte ich > auch bestimmt 'ne Milionen gewonnen. Nein. Du mußt dir vor Augen halten, daß die while-Schleife im Bereich von Millionen Mal pro Sekunde durchläuft und sehr kurz ist. Die Wahrscheinlichkeit, daß der Interrupt genau zwischen den beiden Hälften der Überprüfung kommt, ist viel höher als du denkst. Übrigens, der Funktionsname Zeitcount_erreicht ist eher ungünstig gewählt.
Hallo Aurel, >Man man was für ein Zufall, hätte ich Lotto >gespielt hätte ich auch bestimmt 'ne Milionen gewonnen. Ist viel weniger Zufall, als Du denkst. Für solche Probleme ist es immer sinnvoll, sich mal den Assembler-Code anzuschauen. Dann wird Dir nämlich klar, dass eine einfache C-Zeile aus mehreren einzelnen Befehlen besteht: while (ZeitCount < 320 ); Dazu muss der Compiler - weil 16-Bit-Variable - zwei Bytes laden und dann in einer zweistufige Subtraktion die Konstante 320 abziehen: 753c: 80 91 e2 00 lds r24, 0x00E2 7540: 90 91 e3 00 lds r25, 0x00E3 7544: 80 54 subi r24, 0x40 ; 64 7546: 91 40 sbci r25, 0x01 ; 1 7548: c8 f3 brcs .-14 ; 0x753c Dein mc führt also während dem Warten immer nur zyklisch diese 5 Befehle aus - unterbrochen von den IRs, die ZeitCount hochzählen. Irgendwann wird ZeitCount vom IR von 255 = 00-FF-hex auf 01-00-hex hochgezählt. In ca. 80% aller Fälle passiert nichts. Tritt aber der IR zwischen den beiden lds-Befehlen auf, passiert folgendes: lds r24, 0x00E2 <- in ZeitCount steht 00FFh -> FFh wird nach R24 geladen !!! Interrupt tritt auf -> ZeitCount wird von 00FFh auf 0100h !!! incrementiert !!! Interrupt wird wieder beendet lds r25, 0x00E3 <- in ZeitCount steht 0100h -> 01h wird nach R25 geladen Jetzt steht im Registerpaar R24/25 also 01FFh (entspricht 511) statt den eigendlich richtigen 256. Diese Zeit entspricht auch den gemessenen 8ms. Viel Spass noch, Stefan P.S.: Wenn Du einen Lottotrick mit ähnlicher Gewinnchance kennst - lass es mich wissen - aber nicht über das Forum hier ;-))
Ja Danke für die ausführliche Erklärung. Jetzt habe ich das sogar im Detail verstanden. D.h. wenn ich mit mehr als 8 Bit rum mache muss ich Aufpassen wenn ein Wert sich "selbständig" verändern kann. Ist ja nervig, kann da nicht der Compiler aufpassen, woführ bezahle ich den denn? (-:
Hoppla, wenn Du für gcc etwas bezahlt hast, dann hast Du was falsch gemacht - nur spenden lasse ich durchgehen ;-) Viele Grüße, Stefan
Anyway, du willst nicht wirklich, dass der Compiler dir um jeden 16-bittigen Zugriff einen Interruptschutz herum baut: das würde saumäßig auf die Performance hauen und wäre in 99,999 % der Fälle überhaupt nicht notwendig. Es ist ja nur notwendig, weil du eine 16-bit-Variable zwischen ISR und der ,Außenwelt' kommunizieren lässt.
OK ich merke schon, da hat man sich ausreichend Gedanken gemacht. Dann werde ich halt aufpassen. Aber einen Gedanken habe ich noch. Nur so zum Verständnis, stimmt folgende Aussage? cli(); while(ZeitCount < 320); sei(); --> geht nicht weil die ISR keine Chance hat ZeitCount zu erhöhen. while(ZeitCount < 320){ sei(); cli(); } --> müsste gehen. Allerdings wird CPU Zeit verbraten. Werden eingetretene IR's nachgeholt wenn man sie z.B. mit sei() wieder aktiviert?
Die Flags, die das Auftreten eines Interrupts zeigen, bleiben solange stehen, bis entweder dieInterruptroutine angesprungen wird oder du das Flag (die Flags) löscht. Ausnahme ist der UART Receive Interrupt. Da wird das Flag nicht durch den Einsprung in die Routine sondern durch das Lesen des UDR-Registers gelöscht. MW
Hi Aurel, > while(ZeitCount < 320){ > sei(); > cli(); > } ist theoretisch möglich. Aber bedenke: Du verlässt die while-Schleife mit IRs gesperrt! Nicht vergessen, danach mit sei() die IR wieder einzuschalten, sonst läuft Dein restliches Programm ohne IRs. > Werden eingetretene IR's nachgeholt wenn man sie z.B. mit > sei() wieder aktiviert? Ja. Und Nein. Wenn Du die IRs länger sperrst, können IRs verloren gehen. In Deinem Beispiel: Wenn die IRs länger als 32us gesperrt sind, geht Dir ein Timer-Event verloren (der mc merkt, dass der Timer übergelaufen ist, aber kann nicht mitzählen, dass das schon mehrmals seit dem Sperren der IR passiert ist). Viele Grüße, Stefan
> Ausnahme ist der UART Receive Interrupt. Da wird das Flag nicht > durch den Einsprung in die Routine sondern durch das Lesen des > UDR-Registers gelöscht. Aber zumindest geht der USART-Interrupt nicht verloren. Die meisten Interruptbedingungen löschen sich ,,automatisch'', sowie der passende Interrupthandler aufgerufen wird, oder halt sonst irgendwie ,,sinnvoll'' wie bei der USART. Eine prominente Ausnahme ist das TWI-Interface: hier muss man den Interrupt mit der Hand löschen, weil das zugleich die nächste Bustransaktion einleitet. Aber es gibt auch eine Interruptbedingung, die nicht ,,kleben'' bleibt: ein pegelgetriggerter Externinterrupt. Dort ist der Status der Leitung zugleich das Interruptflag, und wenn das Signal nur während einer Interruptsperre aktiv war, aber vor dem Aufheben der Sperre wieder verschwunden ist, dann wird der Interrupt nicht ausgelöst. Hier noch ein anderer Vorschlag:
1 | static inline int getZeitCount(void) // feines Denglisch :) |
2 | {
|
3 | int tmp; |
4 | cli(); |
5 | tmp = ZeitCount; |
6 | sei(); |
7 | return tmp; |
8 | }
|
9 | ...
|
10 | while (getZeitCount() < 320) ... |
OK jetzt weiß ich aber auch das letzte Detail. Ich glaub ich gehe morgen mal zu meinem Chef und erzähle ihm das dann gibts betimmt eine Gehaltserhöhung.... hust Träumen darf man doch. Gruß und Dank Aurel
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.