Forum: Compiler & IDEs Ungleichmäßiger Takt mit ISR


von Aurel (Gast)


Angehängte Dateien:

Lesenswert?

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

von Rolf Magnus (Gast)


Lesenswert?

> 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.

von Aurel (Gast)


Lesenswert?

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

von johnny.m (Gast)


Lesenswert?

> 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()...).

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Stefan K. (_sk_)


Lesenswert?

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

von Rolf Magnus (Gast)


Lesenswert?

>> 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.

von Karl heinz B. (kbucheg)


Lesenswert?

> 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.

von johnny.m (Gast)


Lesenswert?

@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...

von Rolf Magnus (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> 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.

von Aurel (Gast)


Angehängte Dateien:

Lesenswert?

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

von Rolf Magnus (Gast)


Lesenswert?

> 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.

von Stefan K. (_sk_)


Lesenswert?

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 ;-))

von Aurel (Gast)


Lesenswert?

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?  (-:

von Stefan K. (_sk_)


Lesenswert?

Hoppla, wenn Du für gcc etwas bezahlt hast, dann hast Du was falsch
gemacht - nur spenden lasse ich durchgehen ;-)

Viele Grüße, Stefan

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Aurel (Gast)


Lesenswert?

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?

von Michael Wilhelm (Gast)


Lesenswert?

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

von Stefan K. (_sk_)


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> 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) ...

von Aurel (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.