Forum: Compiler & IDEs Problem: Ext. Interrupt ruft sich sofort nach verlassen von selbst wieder auf


von Dirk (Gast)


Lesenswert?

Hallo,

nach langem erfolglosem herumtestesten möchte ich hier ein Problem von 
mir schildern. Ich habe keine Ahnung wo mein Problem liegt.

Ich möchte im Interrupt eine Rechteckfolge abtasten. Dabei bedeutet ein 
langes LOW gefolgt von einem kurzen HIGH "0" und umgekehrt wird eine "1" 
erkannt. Insgesamt geht es um 8 Bits. Jedes Bit beginnt mit einer 
negativen Flanke.
1
// ATmega64 16MHz
2
3
ISR ( INT3_vect )   // Interrupt 3
4
{
5
unsigned char i;
6
7
PORTB |= (1<<3); // Debug ISR start
8
9
//Bit 0 ist immer "0" deshalb nur warten bis es vorbei ist
10
11
12
while ( !(n64port_in & (1 << n64in)));      // warten auf pos. Flanke
13
14
15
//Bit 1..7
16
i = 6;
17
do {
18
  while ( n64port_in & (1 << n64in));            // warten auf neg. Flanke
19
  TCNT2 = 0;
20
  while ( !(n64port_in & (1 << n64in)));         // warten auf pos. Flanke
21
                                                 //    ___
22
  if (TCNT2 < N64_1_duration) n64cmd[0] |= 0x01; // |_|        -> "1"
23
                                                 //     _
24
    else n64cmd[0] &= ~(1<<0);                   //|___|    -> "0"
25
  n64cmd[0] <<= 1;
26
} while (i--);
27
28
PORTB &= ~(1<<3); // Debug ISR end
29
30
}

Den Interrupt 3 vom mega64 habe ich so initialisiert:
1
EICRA=0x80;
2
EICRB=0x00;
3
EIMSK=0x08;
4
EIFR=0x08;

Was jetzt passiert ist folgendes, bei der negativen Flanke springt der 
AVR korrekt in die Interrupt Routine, tastet dabei auch die 
Rechteckfolge korrekt ab. Er verlässt dann die Routine für 1.7us (AVR 
läuft bei 16MHz) um sie sofort wieder zu durchlaufen.

Mit dem Oszi habe ich das Zeitsignal am entsprechenden Pin gemessen, 
dort gibt es aber zu dem Zeitpunkt, wenn der Interrupt erneut ausgelöst 
wird keine negative Flanke, das Signal steht fest (und rauschfrei) auf 
HIGH.

Wer kann mir sagen, was ich falsch mache?

von Karl H. (kbuchegg)


Lesenswert?

> ISR ( INT3_vect )   // Interrupt 3
> {
> unsigned char i;
>
> PORTB |= (1<<3); // Debug ISR start
>
> //Bit 0 ist immer "0" deshalb nur warten bis es vorbei ist
>
>
> while ( !(n64port_in & (1 << n64in)));      // warten auf pos. Flanke
>
>
> //Bit 1..7
> i = 6;
> do {
>   while ( n64port_in & (1 << n64in));            // warten auf neg.
>                                                  // Flanke

Nur weil der Prozessor in einer ISR steckt, heist das ja nicht,
daß der Interrupt durch die nächste Flanke nicht getriggert
werden kann. In so einem Fall wird das zugehörige Interrupt
Request Bit gesetzt, welches vom Prozessor bei nächster
Gelegenheit so ausgewertet wird, dass die entsprechende ISR
aufgerufen wird. Bei 'nächster Gelegenheit' heist in deinem
Fall, sobald die momentan laufende ISR zu Ende ist.

Die Praxis in einer ISR mit Warteschleifen zu arbeiten lasse
ich mal unkommentiert.

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


Lesenswert?

Dirk wrote:

> nach langem erfolglosem herumtestesten möchte ich hier ein Problem von
> mir schildern. Ich habe keine Ahnung wo mein Problem liegt.

Eigentlich liegt es bereits in der Benutzung eines Externinterrupts.
Ein input capture interrupt wäre vermutlich viel besser geeignet.

Wenn schon Externinterrupt, dann solltest du in der ISR den Interrupt
erstmal abklemmen und einen Timer starten.  Der ablaufenden Timer
tastet dann das Eingangssignal nochmal ab (damit hast du deine
Unterscheidung nach 0- oder 1-Bit) und aktiviert den Interrupt wieder.

> EICRA=0x80;
> EICRB=0x00;
> EIMSK=0x08;
> EIFR=0x08;

Bitte nimm hier symbolische Werte.  Dann sparst du dir und uns das
nochmalige Nachlesen im Datenblatt, was du denn eigentlich getan
hast.

von Dirk (Gast)


Lesenswert?

Danke Karl Heinz für die schnelle Antwort.

Da habe ich anscheinend das Datenblatt falsch verstanden.

Sehe ich das richtig, daß die nächste fallende Flanke, die dann ja 
während der Interrupt Routine kommt, den Interrupt neu triggert und sich 
der AVR das "merkt" und somit nach Ende der Routine gleich wieder einen 
Interrupt ausführt?

Wie könnte ich das verhindern, sprich den IRQ Trigger erst am Ende der 
Interrupt Routine wieder zulassen?

Das mit dem Warten auf die Flanke im Interrupt mag zunächst etwas 
verschwenderisch aussehen. Das Rechtecksignal hat allerdings eine 
Periodendauer von 4us, bei 16MHz muss man deshalb das gesamte Paket in 
einem Interrupt abtasten, es ist sowieso schon knapp beim ersten Bit, 
weil es ca. 30 Takte dauert bis die Interruptroutine aufgerufen ist.

Grüße

Dirk

von STK500-Besitzer (Gast)


Lesenswert?

>Sehe ich das richtig, daß die nächste fallende Flanke, die dann ja
>während der Interrupt Routine kommt, den Interrupt neu triggert und sich
>der AVR das "merkt" und somit nach Ende der Routine gleich wieder einen
>Interrupt ausführt?

richtig!

von Günter R. (galileo14)


Lesenswert?

Dirk wrote:
>
> Wie könnte ich das verhindern, sprich den IRQ Trigger erst am Ende der
> Interrupt Routine wieder zulassen?

Indem Du am Ende der Interrupt-Funktion das Interrupt-Flag (z.B. INTF0) 
einfach mal löschst (indem Du eine '1' reinschreibst - siehe 
Datenblatt). Ob das aber eine wohlüberlegte Funktionsweise der ganzen 
Sache ist, muß Du entscheiden. Normalerweise möchte man keine 
Interrupt-Ereignaisse verlieren.

von Dirk (Gast)


Lesenswert?

Super, vielen Dank Jörg.

Leider verwende ich einen der Input Capture Eingänge schon. Und ich 
brauche zwei solcher Eingänge, habe mich daher für die IRQ Variante 
entschieden. Wenn ich es richtig verstehe muss ich lediglich den INT3 zu 
Beginn der IRQ Routine abschalten und am Ende wieder einschalten.
1
ISR ( INT3_vect )   // Interrupt 3
2
{
3
unsigned char i;
4
5
EIMSK &= ~(1<<INT3);
6
7
(...)
8
9
10
EIMSK |= (1<<INT3);
11
}

Dann sollte es laufen, ist das korrekt?

Grüße

Dirk

von Dirk (Gast)


Lesenswert?

Danke für die vielen Tips.

Dann wäre die für mich CPU Takte sparenste Variante:
1
ISR ( INT3_vect )   // Interrupt 3
2
{
3
unsigned char i;
4
5
(...)
6
7
8
EIFR |= (1<<INTF3);
9
}

Zum Dank schreibe ich einen Beitrag Nintendo64 / GameCube Controller mit 
AVR emulieren, dafür habe ich den ganzen Krempel nämlich benutzt.

Grüße

Dirk

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


Lesenswert?

Dirk wrote:

> Wenn ich es richtig verstehe muss ich lediglich den INT3 zu
> Beginn der IRQ Routine abschalten und am Ende wieder einschalten.

Ich würde ihn nicht innerhalb der ISR wieder einschalten.  Ich würde
dort wirklich nur einen Timer starten, aber den Interrupt noch
ausgeschaltet lassen.  Mit Ablauf des Timers entscheidest du dann
über den Bitwert.  Entweder schaltest du dann an dieser Stelle den
Interrupt wieder zu, oder aber du startest noch einen weiteren
Timer (für eine ,,Karenzzeit''), dessen Ablauf ihn wieder startet.

Damit hast du kürzestmögliche Interruptroutinen und kannst auf alle
ankommenden Ereignisse quasi-parallel reagieren.

Das Wort "Timer" oben meint nicht zwingend einen eigenen Hardware-
Timer-Kanal: man kann mittels eines einzigen solchen Kanals auch gut
in Software multiplex betriebene Timer implementieren (auch als
timer queue bezeichnet).

von Dirk (Gast)


Lesenswert?

Das mit dem "nur den Timer starten und dann wieder raus aus der Routine" 
ist prinzipiell eine gute Idee, aber leider ist mein Eingangssignal zu 
schnell dafür. Ich brauche ~25 Takte bis ich "in" der IRQ Routine nach 
dem Auftreten der neg. Flanke bin.

Wie gesagt, eine 0 besteht aus 3us LOW & 1us HIGH, eine 1 aus 1us LOW & 
3us HIGH. Das bedeutet, daß ich bei 16MHz CPU Takt, die 1 nicht erkennen 
würde, da ich schon >1us für das erreichen der Interrupt Routine 
brauche.

Das ganze funktioniert sowieso nur, weil alle Kommandos von Nintendo64 
bzw. Gamecube mit einer 0 beginnen, ich also das erste Bit fest auf 0 
setzen kann und es dann ab der 2. negative Flanke erst interessant wird.

Grüße

Dirk

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


Lesenswert?

OK, dass du im Mikrosekundenbereich arbeitest, hattest du so explizit
noch nicht geschrieben.  Dann hat es wohl Sinn, dass du einfach am
Ende der ISR die anhängigen Interrupts wieder löschst, so wie es
Günter beschrieben hat.  Dann musst du die Interrupts auch nicht extra
erst ausschalten davor.

von Dirk (Gast)


Lesenswert?

Ihr habt mir auf jeden Fall super geholfen.

Heute Abend wird getestet, danach stelle ich die Routine in die 
Codesammlung.

Grüße

Dirk

von Peter D. (peda)


Lesenswert?

Dirk wrote:
> Ich brauche ~25 Takte bis ich "in" der IRQ Routine nach
> dem Auftreten der neg. Flanke bin.


Ja, das kann hinkommen.
Aber auch nur, wen es keinerlei andere Interrupts gibt und nirgends 
Interruptsperre im Main.

Der Einsprung in den Interrupt dauert 10 Zyklen und dann sichert C 
erstmal nen Haufen Register, ehe es loslegt.
In Assembler könnte man also noch etwas rausholen.


> Wie gesagt, eine 0 besteht aus 3us LOW & 1us HIGH, eine 1 aus 1us LOW &
> 3us HIGH. Das bedeutet, daß ich bei 16MHz CPU Takt, die 1 nicht erkennen
> würde, da ich schon >1us für das erreichen der Interrupt Routine
> brauche.

Ja, die erste 1µs geht verloren.
Den Interrupt verlassen ist nicht möglich, bis das letzte Bit empfangen 
wurde.
Und dann vor dem Verlassen das Interruptflag löschen (auf 1 setzen)!


Ich würde nen ATtiny25 nur für den Datenempfang nehmen und die Daten per 
Slave-SPI an den Haupt-AVR weiterleiten, der kann dann wieder andere 
Interrupts benutzen.


Peter

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.