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:
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_ti;
4
5
PORTC&=~((1<<PC6)|(1<<PC4)|(1<<PC5));
6
7
for(i=0;i<10000;i++)
8
{
9
asmvolatile("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
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
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".
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.
... 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.
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!
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.
> 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.
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...
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.
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.
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 ;)
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.
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.
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.
> 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?
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.
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
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.
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.
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.