Forum: Compiler & IDEs Endlosschleife bei Division - Interrupt Trap!


von Florian B. (florian_b709)


Lesenswert?

Hi Leute,

ich bin dabei einige Projekte für meinen Haus-BUS endlich 
fertigzustellen. Leider habe ich ein Problem mit einem BUS-Teilnehmer. 
Verwendet werden in allen Teilnehmern entweder PIC16F1827 oder 
PIC16F1936. Der Empfang von Daten wird über einen Interrupt gelöst. 
Außerdem gibt es noch weitere Timer-Interrupts.

Das Problem besteht mit einem PIC16F1936 bei 10MHz. Nach einer 
unterschiedlichen Anzahl von Sende- & Empfangsoperationen kommt das 
Programm zum stoppen. Der uC hängt in einer Endlosschleife fest in 
folgendem Programmabschnitt:
1
// long unsigned unsigned division
2
3
unsigned long int
4
#ifdef __PICC__
5
#warning TODO: update cgpic and this file to use the other prototype
6
__lldiv(unsigned long int divisor, unsigned long int dividend)
7
#else
8
__lldiv(unsigned long int dividend, unsigned long int divisor)
9
#endif
10
{
11
  unsigned long int  quotient;
12
  unsigned char  counter;
13
14
  quotient = 0;
15
  if(divisor != 0) {
16
    counter = 1;
17
    while((divisor & 0x80000000UL) == 0) {
18
      divisor <<= 1;
19
      counter++;
20
    }
21
    do {
22
      quotient <<= 1;
23
      if(divisor <= dividend) {
24
        dividend -= divisor;
25
        quotient |= 1;
26
      }
27
      divisor >>= 1;
28
    } while(--counter != 0);
29
  }
30
  return quotient;
31
}

Mit Hilfe des Debuggers (Pickit3) konnte ich feststellen, dass der uC 
bei einer Division hängen bleibt. Diese Division wird aber vor dem 
Auftreten der Endlosschleife mehrmals erfolgreich durchgeführt.

Mit dem Debugger konnte ich während der Endlosschleife erkennen, dass 
der "divisor" 0 war. Das kann aber zu Beginn der Division nicht der Fall 
gewesen sein, da dies ja vorher geprüft wird.

Beim letzten Mal wurde die Division mit "2500 / 1000" durchgeführt. Ich 
vermute, dann kam ein Interrupt und anschließend war der alte Wert vom 
"divisor" = 0.

Für Hilfe wäre ich wirklich dankbar.

von Markus F. (mfro)


Lesenswert?

Florian B. schrieb:
> Beim letzten Mal wurde die Division mit "2500 / 1000" durchgeführt. Ich
> vermute, dann kam ein Interrupt und anschließend war der alte Wert vom
> "divisor" = 0.
>

Wenn das so ist, würde ich annehmen, der Fehler liegt in einem deiner 
Interrupt-Handler und nicht im gezeigten Code.

von Dumdi D. (dumdidum)


Lesenswert?

Wann war divisor=0? Divisor kann am Ende der Funktion Null sein (weil Du 
nach Rechts schiebst)

von dummschwaetzer (Gast)


Lesenswert?

kann wärend der abarbeitung dieser funktion irgend eint interrupt 
zuschlagen der diese funktion noch einmal aufruft?

von Florian B. (florian_b709)


Lesenswert?

Dumdi D. schrieb:
> Wann war divisor=0? Divisor kann am Ende der Funktion Null sein (weil Du
> nach Rechts schiebst)

Ich hab gesehen, dass "devisor" während der while-Schleife 0 war. 
Vielleicht ist ja auch der Rücksprung vom Interrut das problem. Ich 
werde dem mal nachgehen.

dummschwaetzer schrieb:
> kann wärend der abarbeitung dieser funktion irgend eint interrupt
> zuschlagen der diese funktion noch einmal aufruft?

Auch das werde ich gleich mal prüfen.

Ich danke euch schonmal für eure Tips. Manchmal braucht man eben eine 
andere Sichtweise. Ich berichte wieder!

: Bearbeitet durch User
von Florian B. (florian_b709)


Lesenswert?

Hey Leute,

ich konnte nun ausschließen, dass die Funktion während des Interrupts 
nochmals aufgerufen wird.

Meiner Meinung nach gibt es ein Problem beim Rücksprung zur 
Compiler-Funktion (aus meinem ersten Post), die diese "unsigned long 
Division" durchführt.

Da ich mir nicht anders zu helfen wusste, habe ich nun die Beechnung 
anders gerlöst. Ich habe nun eine float-Operation aus meiner Berechnung 
gemacht. Vorher war IOut_Faktor 1000x größer. Nun taucht der Fehler 
nichtmehr auf. Vielleicht nimmt sich ja trotzdem jemand der Sache an 
oder hat noch DIE Lösung für das Problem. Wenn nötig stelle ich gerne 
auch noch meine Interrupt-Routine zur Verfügung.

Cache[x] ist ein Messwert vom ADC mit 10Bit und ist proportional zu 
einem Strom.

Vorher:
1
unsigned int    IOut_Faktor[2];          // Array für Umrechnungsfaktoren der Strommessungen
2
char            x;                       // Variablen für Schleifenzähler
3
unsigned long   y;                       // 32Bit Variable für Berechnung    
4
unsigned int    Cache[6];                // Variable für 10Bit ADC-Wert
5
6
7
for (x=0; x <= 1; x++) {                 // Die Ströme nacheinander errechnen (Kanal 0 + Kanal 1)
8
   if (Cache[x] <= IOut_Offset[x]) {     // Wenn Messwert unter dem Offset liegt,
9
      IOut[x] =   0;                     // den Messwert auf Null setzen
10
   }
11
   
12
   else{                                 // Wenn der Messwert über dem Offset liegt,
13
      y = (Cache[x] - IOut_Offset[x]);   // den Offset vom Messwert abziehen,
14
      y = (y*IOut_Faktor[x]);            // das Ergebnis Faktorisieren,
15
      y = (y/1000);                      // durch 1000 teilen
16
      IOut[x] =   y;                     // Speichern vom Ergenbnis (Strom in mA)
17
      }
18
   }

Nachher:
1
float           IOut_Faktor[2];          // Array für Umrechnungsfaktoren der Strommessungen
2
char            x;                       // Variablen für Schleifenzähler
3
unsigned long   y;                       // 32Bit Variable für Berechnung    
4
unsigned int    Cache[6];                // Variable für 10Bit ADC-Wert
5
6
7
for (x=0; x <= 1; x++) {                 // Die Ströme nacheinander errechnen (Kanal 0 + Kanal 1)
8
   if (Cache[x] <= IOut_Offset[x]) {     // Wenn Messwert unter dem Offset liegt,
9
      IOut[x] =   0;                     // den Messwert auf Null setzen
10
   }
11
   
12
   else{                                 // Wenn der Messwert über dem Offset liegt,
13
      y = (Cache[x] - IOut_Offset[x]);   // den Offset vom Messwert abziehen,
14
      y = (y*IOut_Faktor[x]);            // das Ergebnis Faktorisieren,
15
      IOut[x] =   y;                     // Speichern vom Ergenbnis (Strom in mA)
16
      }
17
   }

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

Florian B. schrieb:
> Meiner Meinung nach gibt es ein Problem beim Rücksprung zur
> Compiler-Funktion (aus meinem ersten Post), die diese "unsigned long
> Division" durchführt.

Wenn das tatsächlich der Fall ist, handelt es sich vermutlich um ein 
Symptom eines Bugs der sich in deinem Teil des Codes befindet. Damit 
hast du jetzt das Symptom bekämft, aber der Bug ist vermutlich immer 
noch vorhanden.

In einer solchen Situation hilft es nur die Fehlerursache zu finden und 
vollständig zu erklären.

Wir können dir aktuell nicht wirklich Helfen, da wir kein vollständiges 
(und minimales) compilierbares Beispiel haben in dem der Fehler 
auftritt.

von asdfasd (Gast)


Lesenswert?

Meine erste Vermutung: der Interrupt zermantscht ein Register oder den 
Stack.

von Markus F. (mfro)


Lesenswert?

asdfasd schrieb:
> Meine erste Vermutung: der Interrupt zermantscht ein Register oder den
> Stack.

Sehr gut möglich.

Die 16x PICs haben ja nur einen 8 (?) Elemente tiefen HW-Stack. Wenn man 
da zwei unsigned longs und eine Rücksprungadresse (+ lokale Variablen) 
draufpackt, ist nicht mehr viel übrig. Wenn dann noch dummerweise ein 
Interrupt kommt, ist Ende Gelände.

von Florian B. (florian_b709)


Angehängte Dateien:

Lesenswert?

asdfasd schrieb:
> Meine erste Vermutung: der Interrupt zermantscht ein Register oder den
> Stack.

Das ist ja auch meine Vermutung, aber darauf habe ich keinen Einfluss. 
Ich habe ja bereits ausgeschlossen, dass diese Funktion nochmals 
aufgerufen wird. Auch ohne Code-Optimierungen ist der Fehler 
aufgetreten.

Aktuell verwende ich "MPLAB X IDE mit dem xc8 v1.44".

Markus F. schrieb:
> Die 16x PICs haben ja nur einen 8 (?) Elemente tiefen HW-Stack.

Der PIC16F1936 besitzt sogar 16 Stack Level. Außerdem habe ich den 
Stackoverflow-Reset eingeschaltet um diese Variante zu erkennen. Leider 
ohne Erfolg.

Anbei mal das komplette Programm.

: Bearbeitet durch User
von asdfasd (Gast)


Lesenswert?

> Anbei mal das komplette Programm.

[Nur ein kurzer Blick - gut möglich, dass ich was falsch verstanden hab]

Das Handling der BUF-Länge schaut mir dubios aus. Insbesondere in der 
Interrupt-Routine wird BUF[6] gelesen, bevor es überhaupt empfangen 
wurde. Außerdem glaubt die Routine BUF[6] bedingungslos - wenn da Mist 
gesendet wurde, schreibt sie quer über den Speicher.

Btw, die ganzen "<="-Vergleiche sind oft ne Quelle von off-by-one-Bugs.

von Florian B. (florian_b709)


Lesenswert?

Hi asdfasd,

danke schonmal, dass du dir so eine mühe gegeben hast. So schnell hatte 
ich mit keiner Antwort gerechnet.

asdfasd schrieb:
> [Nur ein kurzer Blick - gut möglich, dass ich was falsch verstanden hab]
>
> Das Handling der BUF-Länge schaut mir dubios aus. Insbesondere in der
> Interrupt-Routine wird BUF[6] gelesen, bevor es überhaupt empfangen
> wurde.

Ich hoffe, ich hab die richtige Passage gefunden, die du meinst:
Der Vergleich
1
else if (BUS_Pointer == (BUS[6] + 8))
 ist erst erfüllt, wenn mindestens 8Bytes fehlerfrei empfangen wurden. 
Sollte ein Byte zu spät kommen, wird BUS_Pointer resettet. Daher sehe 
ich dort kein Problem. Natürlich mache ich auch (offenkundig) Fehler.


asdfasd schrieb:
> Außerdem glaubt die Routine BUF[6] bedingungslos - wenn da Mist
> gesendet wurde, schreibt sie quer über den Speicher.

Das ist soweit korrekt, ich hoffe dem zu begegnen mit meiner "Lo-CRC" 
aus einem einfachen Zähler. Kommt allerdings ein Byte zu wenig an, wird 
BUS_Pointer resettet.


asdfasd schrieb:
> Btw, die ganzen "<="-Vergleiche sind oft ne Quelle von off-by-one-Bugs.

Dem werde ich gleich nochmal nachgehen. Ich habe diese Routine aber 
bereits in diversen Controllern problemfrei im Einsatz.

von asdfasd (Gast)


Lesenswert?

> Ich hoffe, ich hab die richtige Passage gefunden, die du meinst:

Ja, BUS, nicht BUF.

> Der Vergleich
>
1
else if (BUS_Pointer == (BUS[6] + 8))
> ist erst erfüllt, wenn mindestens 8 Bytes fehlerfrei empfangen wurden.

Ein "BUS_Pointer >= 7" seh ich aber nirgendwo.
[Btw, wenn BUS[6] größer als 0xf7 ist, ist die Bedingung immer false.]

>> Außerdem glaubt die Routine BUF[6] bedingungslos - wenn da Mist
>> gesendet wurde, schreibt sie quer über den Speicher.
>
> Das ist soweit korrekt, ich hoffe dem zu begegnen mit meiner "Lo-CRC"

Zu spät, dann ist der Speicher schon überschrieben.

von Florian B. (florian_b709)


Lesenswert?

asdfasd schrieb:
> Ein "BUS_Pointer >= 7" seh ich aber nirgendwo.
> [Btw, wenn BUS[6] größer als 0xf7 ist, ist die Bedingung immer false.]

Da bin ich dann auch drauf gekommen, nachdem du drauf hingewiesen 
hattest. Ich habe nur etwas länger gebraucht. Mir fehlt der Abstand zu 
dem Programm (Wald vor lauter Bäumen nicht und so...)

Ich habe nun folgende Zeilen eingefügt:
1
else if (BUS_Pointer == 6 && (BUS[6] > 50)) {    // -> Nachrichtenlänge nicht okay <-
2
   TMR0CS = 1;                                   // Timer0 zur Zeitüberlaufüberwachung stoppen
3
   BUS_Init();                                   // UART neustarten
4
}
5
else if (BUS_Pointer == (BUS[6] + 8)){          // Wurden alle Bytes empfangen?
6
...
7
etc.
8
...


...und in der BUS_Init(); werden anschließend alle "falsch" gefüllten 
Zellen geleert.
1
unsigned char   Pointer;
2
for (Pointer = 0; Pointer < 58; Pointer++){      // Wenn noch nicht alle Zellen gelöscht wurden,
3
   BUS[Pointer] = 0;                             // nächstes Byte löschen.
4
}

Danke schonmal bis hierher. Ich werde das nachher mal testen.

von Florian B. (florian_b709)


Lesenswert?

Hi,

ich danke dir asdfasd. Nun scheint alles zu funktionieren. Da hatte ich 
mir aber einen schönen Fehler eingebaut ;-)

Grüße und vielen Dank,
Flo

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.