Forum: Mikrocontroller und Digitale Elektronik Sonderbares Timer-Verhalten


von Thomas K. (thomas99)


Lesenswert?

Hey
ich möchte ein -eigentlich einfaches- Programm schreiben:

-der PinChangeInterrupt wird (mit einer konstanten Frequenz von 20Hz) 
ausgelöst, er setzt einen Pin auf HIGH und startet einen Timer
-Sobald ein vorher eingestellter CompareMatch erreicht ist, wird der 
Timer-Interrupt aufgerufen und der Pin wieder auf LOW gelegt.

Soweit so gut, allerdings klappt das ganze nur Teilweise. Testweise habe 
ich folgendes Programm erstellt:
1
#define F_CPU 10000000UL
2
3
#include <avr/io.h>
4
#include <util/delay.h>
5
#include <avr/interrupt.h>
6
7
int main ()
8
{
9
  DDRC |= (1<<PC6);
10
  
11
  //******************** PIN CHANGE INTERRUPT ***********************
12
  //Pin Change Interrupt 3 anschalten (für Pins PCINT31..24)
13
  PCICR |= (1<<PCIE3);    
14
  
15
  //Interrupt durch drei Pins zulassen
16
  PCMSK3 |= (1<<PCINT31) | (1<<PCINT30) | (1<<PCINT29);
17
  
18
  
19
  //************************* TIMER ********************************
20
  //Timer1 (16Bit): Prescaler = 1
21
  TCCR1B |= (1<<CS10);          
22
  TCCR1B &=~ ((1<<CS11) | (1<<CS12));
23
  
24
  TIMSK1 |= (1<<OCIE1A);
25
  
26
  //Impulsdauer einstellen
27
  OCR1A = 6000;      //600µs bei 10MHz
28
  
29
  sei();
30
  
31
  while(1)
32
  {
33
     _delay_ms(5); //Nichts tun
34
  }
35
}
36
37
ISR(PCINT3_vect)
38
{ 
39
  //Timer auf Anfang, Pins auf HIGH 
40
  TCNT1 = 0;
41
  PORTC |= (1<<PC6);
42
}
43
44
ISR(TIMER1_COMPA_vect)
45
{ 
46
  //Pins auf LOW 
47
  PORTC &=~ (1<<PC6);  
48
}

Wenn ich dieses Programm ausführe, gibt es kein Problem, der Pin ist 
immer schön für 600µs auf HIGH.
Wenn ich allerdings am Ende der Timer-ISR noch einige 'NOPs' hinzufüge 
geht das ganze plötzlich nicht mehr:
1
ISR(TIMER1_COMPA_vect)
2
{  
3
  uint16_t i;
4
5
  PORTC &=~ ((1<<PC6) | (1<<PC4) | (1<<PC5));  
6
        
7
  for(i = 0; i<10000; i++)
8
  {
9
     asm volatile ("NOP");
10
  }
11
}

Da, wo ich vorher am Osziloskop noch 600-µs-Impulse gesehen habe sind 
jetzt nur mehr 5µs-Impulse, und zwar unabhängig davon wie OCR1A 
eingestellt ist. Die Frequenz der Impulse ist immer noch bei 20Hz.

Das "sonderbare" verhalten tritt erst auf, sobald ich die for-Schleife 
über ca. 9500 gehen lasse, bei for(i=0; i<9000; i++) ist das 
Ausgangssignal noch ganz normal bei 600µs.

Kennt jemand den Grund für dieses Verhalten? Warum wird mein Pin fast 
sofort wieder abgeschalten, wenn die ISR etwas länger wird? :(

Hilfe, bitte... :(

Achja, der Controller ist ein ATmega644PA und ich programmiere über 
Atmel Studio 6.2, wenn das was hilft...

Thomas

von der alte Hanns (Gast)


Lesenswert?

Welchem Zweck dient die NOP-Schleife? Während dieser läuft der Timer ja 
weiter und erreicht OCRA, folglich ist der Interrrupt gleich wieder 
gesetzt.

von Peter D. (peda)


Lesenswert?

Daran ist nichts sonderbar, Code ausführen dauert nunmal Zeit.

Du solltest daraus 2 Sachen gelernt haben:
- Interrupts so kurz wie möglich
- Delay-Loops in Interrupts strengstens verboten

von Thomas K. (thomas99)


Lesenswert?

der alte Hanns schrieb:
> Welchem Zweck dient die NOP-Schleife?

Die ist in diesem Fall nur ein Beispiel. In meinem "Richtigen" Programm 
habe ich an dieser Stelle noch einige Anweisungen stehen, wodurch das 
selbe Verhalten ausgelöst wird.

der alte Hanns schrieb:
> Während dieser läuft der Timer ja
> weiter und erreicht OCRA

Vielleicht erklär ich es noch einmal schnell mit anderen Worten:
Mit einer Frequenz von 20Hz wird ein PC-Interrupt ausgelöst. Zwischen 
den einzelnen Interrupts sind also ca. 50ms Zeit.

Sobald ein PC-Interrupt ausgelöst wird setzt dieser den Timer zurück, so 
dass dieser bei NULL anfängt zu zählen. Nach 6000 Zählerschritten (also 
600µs) wird OCR1A ausgelöst.
->Die Zeitdauer zwischen "Pin High" und "Pin Low" sollte also immer 
gleich sein, da der Timer direkt davor resettet wurde!
->Selbst wenn OCR1A danach noch einmal ausgelöst wird sollte es keinen 
Unterschied machen, da der Pin sowieso auf LOW liegt.

Oder sehe ich das falsch?


Peter Dannegger schrieb:
> Daran ist nichts sonderbar

Das "sonderbare", was ich mir nicht erklären kann, ist dass diese 
"Impulsdauer" ab einer bestimmten Dauer der COMPA-ISR  !!sprunghaft!! 
von 600µs auf 5µs absinkt. Diese 5µs sind auch komplett unabhängig 
davon, welcher Wert vorher für OCR1A eingestellt war und auch unabhängig 
davon, ob man die ISR noch weiter "verlängert".

von der alte Hanns (Gast)


Lesenswert?

Das ist jetzt nur Theorie, ausprobiert habe ich es nicht:
Durch die lange Verzögerung ist der Timer-Interrupt ständig gesetzt, 
diese ISR also praktisch ständig aktiv, nur zwischendurch kommt kurz der 
PCI durch, da dieser vorrangig bearbeitet wird.

von der alte Hanns (Gast)


Lesenswert?

... und die 5 us sind die Zeit, die vergeht vom Verlassen der PC-ISR bis 
zum Beginn der Timer-ISR.
Zwar wird in der PC-ISR TCNT zurückgesetzt, aber das Timer-IR-Flag war 
schon gesetzt und bleibt es auch.

von Thomas K. (thomas99)


Lesenswert?

Hm, ich werde morgen gleich mal ausprobieren, den PC-Interrupt zu 
sperren und erst zum Ende des OCR-Interrupts wieder freizugeben...

Allerdings sind selbst 10000 "NOPs" bei 10MHz nur eine Millisekunde, im 
gegensatz zu den 50 Millisekunden, die zwischen den einzelnen 
PC-Interrupts liegen (die kommen Zeitlich recht genau..) und den ca. 6ms 
bis zu einem Timer-Overflow...

Trotzdem, werd ich mir auf jeden Fall mal anschauen ;) Danke!

von der alte Hanns (Gast)


Lesenswert?

Die 10000 sind mehr als die 6000, die im OCR stehen.

von MWS (Gast)


Lesenswert?

Thomas K. schrieb:
> Allerdings sind selbst 10000 "NOPs" bei 10MHz nur eine Millisekunde,

Das ist nicht richtig, da die umgebende Schleife deutlich mehr Zyklen 
als das einzelne NOP verbraucht.

von der alte Hanns (Gast)


Lesenswert?

> den ca. 6ms bis zu einem Timer-Overflow
Wo kommen die plötzlich her? Aber egal, zwar bin ich der Meinung, dass 
die Konstruktion ein Holzweg ist, habe aber von c zu wenig Ahnung, und 
verabschiede mich.

von Thomas K. (thomas99)


Lesenswert?

der alte Hanns schrieb:
> Die 10000 sind mehr als die 6000, die im OCR stehen.

Timer1 ist ein 16 bit Timer, der Comparematch-Interrupt springt also bei 
Zählerstand 6000 rein, während der Interrupt-Routine zählt der Timer bis 
6000 + 10000 = 16000 hoch, was immer noch weniger ist als die ca. 65000 
Zählerstände die ein 16-Bit-Timer haben kann.

MWS schrieb:
>> Allerdings sind selbst 10000 "NOPs" bei 10MHz nur eine Millisekunde,
>
> Das ist nicht richtig, da die umgebende Schleife deutlich mehr Zyklen
> als das einzelne NOP verbraucht.

okay, sagen wir aus 10.000 NOPs werden 30.000 zyklen (obwohl ich das für 
sehr viel halte), dann ist der CompareMatch-Interrupt trotzdem fertig, 
bevor der Timer überläuft. Der Abstand der einzelnen PC-Ints beträgt 
50ms, was auch ausreichend groß sein sollte, damit der Impuls, der durch 
den vorherigen PC-Int ausgelöst wurde nicht gestört wird.

der alte Hanns schrieb:
>> den ca. 6ms bis zu einem Timer-Overflow
> Wo kommen die plötzlich her?

2^16(Timer-Werte)/10.000.000(F_CPU) = 0,0065 s


sollte ich hier schmarrn erzählen, dann macht mich bitte darauf 
aufmerksam... ;)

Vielleicht ist es echt das Beste, wenn ich morgen einfach mal den 
Overflow-Interrupt mit aktiviere und den Timer damit automatisch immer 
auf einen Wert größer als OCRA setze, wenn er überläuft...

von Thomas K. (thomas99)


Lesenswert?

der alte Hanns schrieb:
> zwar bin ich der Meinung, dass
> die Konstruktion ein Holzweg ist

Was wäre denn eine bessere Lösung? Die Aufgabenstellung ist einfach, 
sobald ein Signal ankommt soll ein Impuls mit einer bestimmten Dauer 
ausgelöst werden.

von Peter D. (peda)


Lesenswert?

Thomas K. schrieb:
> okay, sagen wir aus 10.000 NOPs werden 30.000 zyklen (obwohl ich das für
> sehr viel halte)

Schau ins Listing.
Es sollten mindestens 50.000 sein.

von MWS (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Es sollten mindestens 50.000 sein.

So ist das. Ein SBIW 2 Takte, ein BRNE auch 2, ein NOP 1 Takt.

von Thomas K. (thomas99)


Lesenswert?

Peter Dannegger schrieb:
> Schau ins Listing.
> Es sollten mindestens 50.000 sein.

sauber, das hätte ich mir nicht gedacht... ja, dann wird es wohl doch 
daran liegen und ich hab mal wieder nur falsch gerechnet ;)

von der alte Hanns (Gast)


Lesenswert?

Entschuldigung, mein Fehler war, dass ich, warum auch immer, vom 
CTC-Modus ausgegangen war.

>sobald ein Signal ankommt soll ein Impuls mit einer bestimmten Dauer
Also ein SW-Monoflop? Da gibt es sicher verschiedene Möglichkeiten; z.B. 
in der PC-ISR den Timer mit vorbelegtem TCNT starten und in der TOVF-ISR 
stoppen.

von Conny G. (conny_g)


Lesenswert?

Thomas K. schrieb:
> der alte Hanns schrieb:
>> zwar bin ich der Meinung, dass
>> die Konstruktion ein Holzweg ist
>
> Was wäre denn eine bessere Lösung? Die Aufgabenstellung ist einfach,
> sobald ein Signal ankommt soll ein Impuls mit einer bestimmten Dauer
> ausgelöst werden.

Den extra Code aus der Timer ISR nehmen.
Stattdessen ein Flag setzen, das in der Hauptschleife - die jetzt nichts 
macht - ausgewertet wird und dann diese Aktion ausgeführt wird.

von Peter D. (peda)


Lesenswert?

Thomas K. schrieb:
> Die Aufgabenstellung ist einfach,
> sobald ein Signal ankommt soll ein Impuls mit einer bestimmten Dauer
> ausgelöst werden.

Das hast Du doch schon auch ohne die 50.000 NOPs.

So als Richtwert, ein Interrupt sollte <500 Zyklen verbrauchen.

von der alte Hanns (Gast)


Lesenswert?

> Das hast Du doch schon
Tatsächlich? Wenn die 20 Hz auf dem PCI ausbleiben, wird zwar kein 
Impuls mehr erzeugt, aber die Timer-ISR mit "einige Anweisungen" wird 
trotzdem alle 65536 Takte durchlaufen; ist das beabsichtigt?

von Karl H. (kbuchegg)


Lesenswert?

Wenn es bei der Systematik bleiben soll, würde ich hier
1
ISR(PCINT3_vect)
2
{ 
3
  //Timer auf Anfang, Pins auf HIGH 
4
  TCNT1 = 0;
5
  PORTC |= (1<<PC6);
6
}

ein eventuell gesetztes Compare Match Interrupt Flag löschen, damit 
sicher gestellt ist, dass der Compare Match Interrupt tatsächlich erst 
durch den nächsten Match ausgelöst wird.
1
ISR(PCINT3_vect)
2
{ 
3
  //Timer auf Anfang, Pins auf HIGH 
4
  TCNT1 = 0;
5
  PORTC |= (1<<PC6);
6
  TIFR1 = (1<<OCF1A);
7
}

von Oliver (Gast)


Lesenswert?

Peter Dannegger schrieb:
> So als Richtwert, ein Interrupt sollte <500 Zyklen verbrauchen.

Wenn der TO alle 20ms etwas ausführen will, und dazwischen gar nichts, 
sollte die ISR so als Richtwert <20ms verbrauchen ;)

Oliver

von Thomas E. (thomase)


Lesenswert?

Auch wenn es in diesem Falle unerheblich ist, aber das sind keine 600µs, 
sondern 600,1µs

> OCR1A = 6000;      //600µs bei 10MHz

Richtig ist:
1
OCR1A = 5999;      //600µs bei 10MHz

> ISR(PCINT3_vect)
> {
>   //Timer auf Anfang, Pins auf HIGH
>   TCNT1 = 0;
>   PORTC |= (1<<PC6);
> }
>
> ISR(TIMER1_COMPA_vect)
> {
>   //Pins auf LOW
>   PORTC &=~ (1<<PC6);
> }

Betreib deinen Timer vernünftig als Start/Stop-Timer und setz ihn nicht 
in vollem Lauf zurück.

1
ISR(PCINT3_vect)
2
{
3
  TCCR1B |= (1<<CS10); //Start Timer
4
  PORTC |= (1<<PC6);
5
}
6
ISR(TIMER1_COMPA_vect)
7
{
8
  //Pins auf LOW
9
  PORTC &=~ (1<<PC6);
10
  TCCR1B &= ~(1<<CS10);  //Stop Timer
11
  TCNT1 = 0;  
12
}

Dann treten auch keine unerwünschten Interrupts mehr auf.


mfg.

von Peter D. (peda)


Lesenswert?

Oliver schrieb:
> Wenn der TO alle 20ms etwas ausführen will, und dazwischen gar nichts,
> sollte die ISR so als Richtwert <20ms verbrauchen ;)

Als Richtwert betrachte ich etwas, was sich für einen Großteil der 
Anwendungen bewährt hat.
Vielleicht soll das Programm später noch andere Tasks und Interrupts 
ausführen.

Daß man im Spezialfall vom Richtwert abweichen kann/muß, ist klar.

von Peter D. (peda)


Lesenswert?

der alte Hanns schrieb:
> aber die Timer-ISR mit "einige Anweisungen" wird
> trotzdem alle 65536 Takte durchlaufen; ist das beabsichtigt?

Das kann nur der OP beantworten.
Einen gelöschten Pin nochmal löschen, stört ja nicht.

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.