Forum: Mikrocontroller und Digitale Elektronik ungeklärte Phänomene mit AVR ISR


von Arne F. (-arne-)


Lesenswert?

Hallo,
Ich verarbeitete innerhalb einer ISR eines ATmega16 ein Datensignal. 
Eine Taktleitung triggert die ISR über INT1. Der Takt ist so hoch, dass 
ich beim Lesen der Daten keinen komplette 32-Bit Wert schieben kann, 
daher schiebe ich die einzelnen Bytes. Nach 29 Zyklen kommen ca. 200ms 
Pause auf der Taktleitung.

1) Führe ich die mit '***' gekennzeichnete Zeile innerhalb main() aus, 
funktioniert alles.
2) Führe ich sie stattdessen in der ISR aus, wird der Wert in main() 
niemals ausgegeben.
3) kapsele ich dann aber die ISR zusätzlich in cli()/sei() wie in den 
Kommentaren angedeutet, bekomme ich einen Wert, der halb so groß ist, 
wie mit Lösung (1)?

Ich steh grad etwas auf dem Schlauch, kann mir jemand das merkwürdige 
Verhalten erklären? Insbesondere verstehe ich nicht, warum cli/sei 
innerhalb der ISR eine Auswirkung hat, da 'ISR (INT1_vect)' auch ohne 
zusätzliche Maßnahmen in der ISR keine rekursiven Interrupts zulassen 
sollte, oder?

Hier der Code (habe hier geschweifte Klammern zwecks kompakterer 
Darstellung wegoptimiert):
1
ISR (INT1_vect)
2
{
3
    //cli()
4
    if (!trigger)
5
    {
6
        if (timer > 0x20)  bitCount = 1; // first bit after pause
7
        else               ++bitCount;
8
9
        timer = 0;
10
        uint8 databit = (PIND & (1<<CHANNEL2PIN)) ? 0x80 : 0;
11
12
        if (bitCount <= 8)         d1 = (d1 >> 1) | databit;
13
        else if (bitCount <= 16)   d2 = (d2 >> 1) | databit;
14
        else if (bitCount <= 24)   d3 = (d3 >> 1) | databit;
15
        else if (bitCount <= 28)   d4 = (d4 >> 1) | databit;
16
        else if (bitCount == 29)
17
        {
18
            //data = ((uint32) d4)<<24 | ((uint32)d3)<<16 | ((uint32)d2)<<8 | d1; //***
19
            trigger = 1;
20
        }
21
    }
22
    else
23
    {
24
        timer = 0xff;
25
    }
26
    //sei()
27
}
28
29
ISR (TIMER2_OVF_vect)
30
{
31
    if (timer < 0xff) ++timer;
32
}
33
34
int main ()
35
{
36
   //...
37
38
    while (1)
39
    {
40
        if (trigger)
41
        {
42
            data = ((uint32) d4)<<24 | ((uint32)d3)<<16 | ((uint32)d2)<<8 | d1; //***
43
44
            //gib data aus
45
46
            trigger = 0;
47
        }
48
    }
49
}

Alle in der ISR verwendeten Variablen sind 'volatile' deklariert:
1
volatile uint8 timer = 0;
2
volatile uint8 bitCount = 0xff;
3
volatile uint8 trigger = 0;
4
volatile uint8 d1 = 0;
5
volatile uint8 d2 = 0;
6
volatile uint8 d3 = 0;
7
volatile uint8 d4 = 0;
8
volatile uint32 data;

von Andreas K. (derandi)


Lesenswert?

Arne F. schrieb:
> Ich steh grad etwas auf dem Schlauch, kann mir jemand das merkwürdige
> Verhalten erklären? Insbesondere verstehe ich nicht, warum cli/sei
> innerhalb der ISR eine Auswirkung hat, da 'ISR (INT1_vect)' auch ohne
> zusätzliche Maßnahmen in der ISR keine rekursiven Interrupts zulassen
> sollte, oder?

Soweit ich weiß, muss man solche rekursiven Szenarien explizit 
unterbinden.
Bauchgefühl sagt also, der Controller ist zu langam und kommt ins 
Stolpern.

von Floh (Gast)


Lesenswert?

Wie schnell ist denn dein Takt ca?

von Arne F. (-arne-)


Lesenswert?

Dummerweise funktioniert das Unterbinden mit cli/sei in diesem Beispiel 
ja auch nicht richtig.

Auch hier im AVR-GCC Tutorial finde ich folgendes Statement:
"Während der Ausführung der Funktion sind alle weiteren Interrupts 
automatisch gesperrt. Beim Verlassen der Funktion werden die Interrupts 
wieder zugelassen."
Daher bin ich weiterhin ratlos...

von Arne F. (-arne-)


Lesenswert?

@Floh: 8MHz (was der RC-Oszillator halt so her gibt ;-)

von Floh (Gast)


Lesenswert?

Und dein Signal?

von Arne F. (-arne-)


Lesenswert?

ca. 100Khz

von Andreas K. (derandi)


Lesenswert?

Arne F. schrieb:
> "Während der Ausführung der Funktion sind alle weiteren Interrupts
> automatisch gesperrt. Beim Verlassen der Funktion werden die Interrupts
> wieder zugelassen."
> Daher bin ich weiterhin ratlos...

Ok, hab ich mich geirrt. Mein Fehler.
Also bleiben nur rund 80 Takte zwischen jeden Aufruf übrig, ist auch 
nicht unbedingt viel. Wird die Zeit zu knapp verschluckt er einen Takt.

Ich würd der Einfachheit halber einen Quarz dranschrauben und mal die 
Taktfrequenz erhöhen.

von Arne F. (-arne-)


Lesenswert?

Die zeitkritische Verarbeitung der 28 Datenbits funktioniert ja. Und 
nach dem 29. Takt kommen 200ms Pause, also reichlich Zeit für die das 
bisschen 32-Bit Wert verarbeiten.

von Arne F. (-arne-)


Lesenswert?

Letztlich habe ich ja eine funktionierende Lösung (Berechnung in main 
statt ISR), möchte jedoch verstehen, warum cli/sei innerhalb der ISR 
einen (positiven) Einfluss hat und wie es zu solchen Problemen kommt, um 
das in Zukunft zu vermeiden.

von Karl H. (kbuchegg)


Lesenswert?

Arne F. schrieb:
> Letztlich habe ich ja eine funktionierende Lösung (Berechnung in main
> statt ISR), möchte jedoch verstehen, warum cli/sei innerhalb der ISR
> einen (positiven) Einfluss hat und wie es zu solchen Problemen kommt, um
> das in Zukunft zu vermeiden.

Ich denke mal, du bist knapp an der Kippe.

Der sei kommt zu früh, noch ehe der Epilog der compilergenerierten ISR 
beginnt an deren Ende dann das ultimative Freigeben der Interrupts 
steht. Du bist mit deinem eigenen sei etwas zu früh drann, so dass die 
Ausführung der nächsten ISR schon beginnt, noch ehe die vorhergehende 
fertig war.
-> deine ISR stapeln sich rekursiv und irgendwann kracht es.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Arne F. schrieb:
> Alle in der ISR verwendeten Variablen sind 'volatile' deklariert:

Das ist Unsinn. Variablen, die Du ausschließlich in der ISR nutzt, 
brauchen lediglich in der ISR-Funktion als static definiert werden - 
ohne volatile. Das spart nicht nur Code in der ISR, sondern sie wird 
auch wesentlich flotter abgearbeitet.

Ein Kandidat wäre da zum Beispiel bitCount.

Was kostet es denn an Code/Takten, die volatile-Variable "data" aus 4 
Bytes zusammenzusetzen?

Gruß,

Frank

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Nachtrag:

Wenn Du data in der ISR zusammenbaust, gehören d1 bis d4 als 
static-NON-volatile in die ISR.

Wenn du data in main zusammenbaust, gehört data als lokale Variable ins 
main().

Du verbrätst in beiden Varianten jede Menge Code bzw. Taktzeiten. 100kHz 
(= 80 Takte) sind nicht von Pappe.

von Peter D. (peda)


Lesenswert?

Arne F. schrieb:
> Ich steh grad etwas auf dem Schlauch, kann mir jemand das merkwürdige
> Verhalten erklären? Insbesondere verstehe ich nicht, warum cli/sei
> innerhalb der ISR eine Auswirkung hat, da 'ISR (INT1_vect)' auch ohne
> zusätzliche Maßnahmen in der ISR keine rekursiven Interrupts zulassen
> sollte, oder?

Gerade durch das cli/sei wird die Rekursion erlaubt, da das Enable schon 
vor dem Epilog des Interrupthandlers erfolgt.


Peter

von arne (Gast)


Lesenswert?

Ok, aber es bleibt die Frage, warum es ohne cli/sei noch schlechter 
funktioniert

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

arne schrieb:
> Ok, aber es bleibt die Frage, warum es ohne cli/sei noch schlechter
> funktioniert

Weil da irgendwas faul ist, zum Beispiel das Zusammenbauen von 4 
volatile-Variablen d1,d2,d3,d4 zu einer volatile-Variablen "data". Okay, 
das machst Du zwar in der 200ms-Pause, aber das hier ist grausam:
1
        if (bitCount <= 8)         d1 = (d1 >> 1) | databit;
2
        else if (bitCount <= 16)   d2 = (d2 >> 1) | databit;
3
        else if (bitCount <= 24)   d3 = (d3 >> 1) | databit;
4
        else if (bitCount <= 28)   d4 = (d4 >> 1) | databit;
5
        else if (bitCount == 29)

Das sind alles volatiles. Das kostet viel Zeit und viel Takte. Und die 
hast Du nicht. Ich bin mir sicher, dass Deine ISR mehr Zeit verbrät als 
Du hast bei 100kHz-Signal.

Ich habe Deinen Code gerade mal durch den Compüiler geschickt. Der Code 
für die ISR ist 166 Bytes groß.

Mach bitCount,d1,d2,d3,d4 static (ohne volatile) und definiere sie 
direkt in der ISR. Ich bin mir sicher, dass dann Deine Variante, die 
Variable "data" in der ISR zusammenzubauen, auch erfolgreich ist.

EDIT:
Da Du mir die Frage, wieviel Takte das Zusammenbauen von 4 
volatile-Bytes zu einer volatile-32-bit-Variblen kostet, nicht 
beantwortet hast, habe ich es gerade mal selber getestet:

Die Zeile
1
data = ((uint32_t) d4)<<24 | ((uint32_t)d3)<<16 | ((uint32_t)d2)<<8 | d1;

vergrößert den Code um 150 Bytes. Weia.

von Arne F. (-arne-)


Lesenswert?

@Frank:
1. ich habe alle diese Variablen volatile deklariert, da ich sie zu 
Testzwecken in main() auf einem Display ausgegeben habe.
2. nein, auch wenn ich bitCount und d1-d4 nicht volatile deklariere 
funktioniert es auch nicht. (Im übrigen ist es aus Laufzeitsicht(!) 
völlig unerheblich, ob die Vars static im Scope der ISR oder global 
definiert werden)
3. dass die Berechnung von data innerhalb der ISR das Laufzeitproblem 
darstellt ist unstrittig und war schon vorher klar. Die Frage zielte 
nicht auf eine Codeoptimierung, denn eine funktionierende Variante habe 
ich, wie zuvor geschrieben. Stattdessen war die Frage, wie es zu dem 
Verhalten kommen kann, obwohl eine ISR nicht rekursiv ausgeführt wird 
(wenn man es nicht explizit erzwingt):
Die Berechnung von data wird ca. 4x pro Sekunde ausgeführt, dafür sind 
die 150 Byte Code also unerheblich. Trotzdem wird scheinbar entweder 
trigger nie gesetzt (eher unwahrscheinlich) oder main() kommt gar nicht 
mehr dran. Womit wir wieder beim ursprünglichen Problem wären.

Vielleicht wird's doch mal Zeit, sich einen JTAG-Adapter anzuschaffen...

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.