Forum: Mikrocontroller und Digitale Elektronik [PIC18F] Lokale Variablen nach Interrupt-Return vermatscht


von Enrico K. (ekoeck)


Angehängte Dateien:

Lesenswert?

Hallo!

Ich habe ein sehr schwierig einzuordnendes Problem mit einem PIC18F252 
(MPLAB X IDE v2.00, XC8 v1.34). Folgende Routine gibt mir 16Bit-Zahlen 
zu Debugging-Zwecken auf der UART-Schnittstelle aus. Nach einer 
unbestimmten Zeit (ca. 20 sek, kann nicht genau sagen, ob regelmäßig) 
fängt der Output an kryptisch zu werden (siehe konsole.png). Durch ewige 
Sucherei habe ich endlich herausgefunden, woher der Datenmüll kommt, 
kann mir aber absolut nicht erklären, warum dies so ist. Datenmüll tritt 
auf, wenn der Interrupt in die Routine zur Ausgabe der Zeichen zurück 
springt. Dann haben sich aus mir unerklärlichen Gründen die Werte der 
lokalen Variablen geändert (siehe buf.png, number.png, dez.png). Dass in 
"dez[4]" jetzt komplett andere Werte stehen als bei der Initialisierung 
der Variablen geht mir einfach nicht in den Kopf. Ebenfalls, dass in buf 
(angelegt zu Debugging-Zwecken) noch der Wert steht, welchen "number" 
zum Funktionsaufruf hatte, jetzt aber in "number" ein kompletter 
Mondwert (ja, verändert sich, dürfte aber nur kleiner werden) steht.
Wenn ich die Funktion mit deaktivierten Interrupts laufen lasse und 
immer einen um Eins erhöhten uint16 füttere, läuft alles problemlos.

Hat jemand eine Anregung für mich, woher das Problem kommen könnte? Ich 
bin kurz davor das Programm anders aufzubauen, dies befriedigt aber 
meiner Neugier nicht.

Schonmal vielen Dank im Vorraus!

Problematische Funktion:
1
void UARTputUint16(uint16_t number)
2
{
3
    /* description: send a decimal number over the serial port
4
     * parameters:  -
5
     * returns:     -
6
     */
7
8
    uint16_t dez[4] = {10000, 1000, 100, 10};
9
    uint8_t cnt, i;
10
    uint16_t buf = number;
11
12
    // int16 <= 65535 -> maximum 5 decades
13
    for (i = 0; i < 4; i++)
14
    {
15
        cnt = '0';
16
17
        // subtract decade as long as it fits
18
        while (number >= dez[i])
19
        {
20
            number -= dez[i];
21
            cnt++;
22
        }
23
24
        // output the digit
25
        UARTputChar(cnt);
26
    }
27
28
    if (number > 9)
29
    {
30
        NOP();
31
        buf += 0;
32
    }
33
34
    UARTputChar(number + '0');
35
}

UARTputChar:
1
void UARTputChar(char c)
2
{
3
    /* description: send a character over UART and wait for buffer to clear
4
     * parameters:  c - character to be sent
5
     * returns:     -
6
     */
7
    TXREG = c;
8
9
    // wait untin sending buffer is empty
10
    while(PIR1bits.TXIF == 0)
11
    {
12
        NOP();
13
    }
14
}

Im Interrupt wird ein Flag gesetzt (_ACflag), welches dann in der Main 
gepollt wird und die problematische Ausgabe aufruft.
ISR:
1
void interrupt ISR()
2
{
3
    /* description: interrupt service routine
4
     * parameters:  -
5
     * returns:     -
6
     */
7
8
    // if port change interrupt on PORTB
9
10
    if (INTCONbits.RBIE && INTCONbits.RBIF && (PORTBbits.RB4 == 1))
11
    {
12
        INTCONbits.RBIF = 0;    // clear interrupt flag
13
14
        T3CONbits.TMR3ON = 0;   // stop timer
15
        _periodTime = (TMR3H << 8) + TMR3L; // save period time
16
        TMR3H = 0;
17
        TMR3L = 0;
18
        //_laststate = HIGH;
19
        T3CONbits.TMR3ON = 1;   // start timer again
20
    }
21
22
    if (PIE1bits.CCP1IE && PIR1bits.CCP1IF)
23
    {
24
        PORTBbits.RB3 = !PORTBbits.RB3;
25
26
        // clear interrupt flag
27
        PIR1bits.CCP1IF = 0;
28
29
        if (PORTCbits.RC2 == 1)
30
        {
31
            // next interrupt with falling edge
32
            CCP1CONbits.CCP1M = 0b0100;
33
34
            _xTimeUp = (CCPR1H << 8) + CCPR1L;
35
        }
36
        else
37
        {
38
            // first interrupt with rising edge
39
            CCP1CONbits.CCP1M = 0b0101;
40
41
            _xTimeDn = (CCPR1H << 8) + CCPR1L;
42
43
            uint16_t xPeak = (_xTimeUp + _xTimeDn) >> 1;
44
            uint16_t xPforth = _periodTime >> 2;
45
46
            if (_xTimeUp > _xTimeDn)
47
            {
48
                _phaseTime = xPeak - xPforth;
49
            }
50
            else
51
            {
52
                _phaseTime = xPeak + xPforth;
53
            }
54
55
            _ACflag |= 0x01;
56
        }
57
    }
58
    INTCONbits.GIE = 1;
59
}

main:
1
void main()
2
{    
3
    iniSys();
4
    
5
    iniUART();
6
7
    iniTimer3();
8
9
    INTCONbits.GIE = 1; // global interrupts
10
    INTCONbits.PEIE = 1; // peripheral interrupts
11
12
    PORTBbits.RB0 = 1;
13
14
    while(1)
15
    {
16
        while (_ACflag == 0);
17
18
        _ACflag = 0;
19
        UARTputUint16(_phaseTime); UARTputChar(10); UARTputChar(13);
20
    }
21
}

von Peter II (Gast)


Lesenswert?

Enrico Koeck schrieb:
> Hat jemand eine Anregung für mich, woher das Problem kommen könnte?

vermutlich ein Speicherüberschreiber irgendwo im code. Oder wenn sich 
die Funktionen gegenseitig aufrufen, ein Stack überlauf.

von Little B. (lil-b)


Lesenswert?

Lass dir mal das Disassembly deiner ISR und deiner Uart Ausgabe 
anzeigen. Vieleicht siehst du dann, welcher Speicher tatsächlich von den 
jeweiligen Funktionen verwendet wird.

Mich verwirrt dieses Phänomen, da du in der ISR defacto keine Stack 
Variablen hast.

Disassembly zu finden unter Window->Debug->Disassembly oder so...

: Bearbeitet durch User
von Klaus (Gast)


Lesenswert?

Enrico Koeck schrieb:
> INTCONbits.GIE = 1;

Du gibst den Interrupt innerhalb der ISR frei, das könnte das Problem 
sein.

Und warum machst du das:

Enrico Koeck schrieb:
> // wait untin sending buffer is empty
>     while(PIR1bits.TXIF == 0)
>     {
>         NOP();
>     }

Das UART macht das Senden auch ohne daß du wartest. Erst wenn ein neues 
Byte gesendet werden soll braucht man zu schauen, ob dafür Platz ist.

Little Basdart schrieb:
> Lass dir mal das Disassembly ...

Der C-Debugger zeigt doch die Adressen direkt an

MfG Klaus

von Enrico K. (ekoeck)


Lesenswert?

1
!    uint16_t dez[4] = {10000, 1000, 100, 10};
2
0x618: LFSR 2, 0x30
3
0x61A: NOP
4
0x61C: LFSR 1, 0x1C
5
0x61E: NOP
6
0x620: MOVLW 0x8
7
0x622: MOVFF POSTINC2, POSTINC1
8
0x624: NOP
9
0x626: DECFSZ WREG, F, ACCESS
10
0x628: BRA 0x622
11
!    uint8_t cnt, i;
12
!    uint16_t buf = number;
13
0x62A: MOVFF number, buf
14
0x62C: NOP
15
0x62E: MOVFF config, 0x1B
16
0x630: NOP

Das Problem hat sich jedenfalls in Luft aufgelöst, wenn ich das Array 
dez[4] als globales
1
const uint16_t dez[4]
 anlege, was auch sinnvoller ist. So ein wenig fader Beigeschmack bliebt 
aber trotzdem zurück ^^

von Peter II (Gast)


Lesenswert?

Enrico Koeck schrieb:
> Das Problem hat sich jedenfalls in Luft aufgelöst, wenn ich das Array
> dez[4] als globalesconst uint16_t dez[4] anlege, was auch sinnvoller
> ist. So ein wenig fader Beigeschmack bliebt
> aber trotzdem zurück ^^

richtig, aus dem Grund würde ich es fehlerhaft lassen und lieber den 
Fehler suchen.

von Peter D. (peda)


Lesenswert?

Enrico Koeck schrieb:
> Das Problem hat sich jedenfalls in Luft aufgelöst

Nö.
Globale und lokale Variablen werden einfach nur an verschiedene Stellen 
gelinkt.
D.h. der Fehler verändert jetzt ne andere Variable oder mit etwas Glück 
noch ungenutzten RAM.

von Little B. (lil-b)


Lesenswert?

Klaus schrieb:
> Little Basdart schrieb:
>> Lass dir mal das Disassembly ...
>
> Der C-Debugger zeigt doch die Adressen direkt an

Das ist auch so weit richtig, jedoch nur die Adressen der Variablen. Da 
der PIC aber nur ein Work Register hat, müssen Zwischenergebnisse ins 
RAM geschrieben werden. Diese Adressen werden nicht angezeigt und sieht 
man nur im Disassembly.

von Enrico K. (ekoeck)


Lesenswert?

Also das Problem hat in irgendeiner Art und Weise damit zu tun, dass der 
Portchange-Interrupt und der Capture-Interrupt sich gegenseitig 
verhaken.. Ich habe jetzt meinen Algorithmus angepasst und den Code 
komplett umgeschrieben, sodass dies jetzt nicht mehr vorkommen kann. 
schulterzuck
Vielen Dank für die Anregungen!

von Mike (Gast)


Lesenswert?

In der ISR vermisse ich das Rücksetzen der Inetrrupt-Flags!
Sobald GIE wieder gesetzt wird, wird der Interrupt erneut aufgerufen. 
Durch die vielfache Verschachtelung der Aufrufe läuft der Stack über, 
was vermutlich zum Reset führt. Das explizite Setzen des GIE ist ohnehin 
unnötig, da der PIC das am Ende der ISR über RETFIE automatisch macht!

Gruss
Mike

von Mike (Gast)


Lesenswert?

Sorry, ich habe das Rücksetzen der IF-Flags übersehen. Trotzdem würde 
ich die Zeile
1
INTCONbits.GIE = 1;
 mal auskommentieren, weil das zur Verschachtelung der Interruptaufrufe 
führen kann.

von Enrico K. (ekoeck)


Lesenswert?

Okay, das GIE hab ich mal rausgenommen, wusste nicht genau, ob das 
automatisch gesetzt wird. Die Interruptflags werden immer gleich nach 
der Abfrage, welcher Interrupt kam, gelöscht.

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.