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
unsignedchari;
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
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?
> 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.
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.
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
>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!
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.
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
unsignedchari;
4
5
EIMSK&=~(1<<INT3);
6
7
(...)
8
9
10
EIMSK|=(1<<INT3);
11
}
Dann sollte es laufen, ist das korrekt?
Grüße
Dirk
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).
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
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.
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