Forum: Compiler & IDEs Reset durch Stack overflow?


von Roman D. (smarties)


Angehängte Dateien:

Lesenswert?

Hallo,

ich musste für die FH einen Datenlogger für 10 Solarzellen bauen. Es 
sollte die Spannung, der Strom, der Kurzschlussstrom und zwei 
Temperaturen auf SD Karte aufgezeichnet werden. Das funktioniert auch 
soweit, nur macht mir der Controller jedes mal nach 32 - 34 Messzyklen 
(á 10Sek) einen reset. Ich habe schon das MCUCSR Register mit 
aufgezeichnet und nach dem RESET auf LCD ausgegeben. Das Register ist 
auch nach dem Reset 0. Ich teste jetzt schon seit Tagen und finde den 
Fehler einfach nicht. Ich vermute, es ist ein Stack overflow, der mir 
den reset verursacht (Sprung auf 0 da die Rückspungadresse nicht geladen 
werden kann).
Ich habe den Schaltplan und die Software mal angehängt. Vielleicht kann 
mir da ja jemand helfen.

Vielen Dank schon im Voraus!

von holger (Gast)


Lesenswert?

MAKE.EXE: *** No rule to make target `main.o', needed by `main.elf'. 
Stop.

von Stephan W. (sir_wedeck)


Lesenswert?

Hi,
schreib dir doch eine Testfunktion für den Stack.
Bevor deine Vars ins SRAM kopiert werden, schreibst du von RAM_END an 
eine Anzahl von Bytes mit irgend ein Pattern ins RAM.
(zb. 0xFFFF -> 0xFF00 = 0xA5)
In der Main oder wenn du deine Werte Logs schreibst du die Anzahl der 
Bytes die verändert wurden mit weg.
So kannst du schnell prüfen wie weit dein Stack anwächst.

Beispiel: (kein CODE!!!!)
1
count= 0
2
for 0xFFFF to 0xFF00 Step -1
3
  if (*0xFFFF != 0xA5) then
4
    count++;
5
next
6
7
Print "Der Stack ist " + count + " Bytes gross!"

Vielleicht hilft es dir.

Stephan

von Kurti (Gast)


Lesenswert?

Wo ist die main.c ?

von Roman D. (smarties)


Angehängte Dateien:

Lesenswert?

Danke Stefan für die Info. Werd ich mir ansehen, wie das am einfachsten 
möglich ist.

Kurti schrieb:
> Wo ist die main.c ?

sorry, wollte nicht mit in das zip.

Kurz zur Erklärung:
Im Prinzip wird alle 10 Sekunden die ISR von Timer1 ausgeführt welche 
die funktion "measure_and_save() aufruft.
1
ISR (TIMER1_OVF_vect)
2
{
3
  cli();  // disable interrupts
4
  TCNT1 = 0x676A;  // preload timer for timebase 10s
5
  sei();
6
  
7
  if (count < interval) {
8
    count++;
9
  }
10
  else {
11
    measure_and_save();
12
    count = 1;
13
  }
14
  sleep_mode();
15
}
16
17
...
18
19
20
//******************************
21
// get ADC values and save to SD
22
//******************************
23
24
void measure_and_save (void) {
25
  
26
  toggle_led();
27
  unsigned char str[30];
28
  unsigned char file_name[13]="DATA    DAT";
29
  unsigned int i, j, mux_channel;
30
  RTC_DATE_TIME rtc;
31
  
32
  unsigned int adc_value;
33
  double temp;
34
  double voltage;
35
  double current;
36
  double current_short;
37
  
38
  
39
  lcd_home();
40
  char test[5];
41
  sprintf (test, "%u", MCUCSR);
42
  lcd_string(test);
43
  
44
  
45
  //if(MMC_FILE_NEW == ffopen(file_name)){;}
46
    
47
  
48
  if (rtc_read(&rtc) != RTC_ERROR){
49
    sprintf(str, "%u$1;1;%d%d.%d%d.%d%d%d%d %d%d:%d%d:%d%d;",
50
        MCUCSR, rtc.date[0], rtc.date[1], rtc.date[3], rtc.date[4], rtc.date[6], rtc.date[7], rtc.date[8], rtc.date[9],
51
        rtc.time[0], rtc.time[1], rtc.time[3], rtc.time[4], rtc.time[6], rtc.time[7]);
52
    sprintf(file_name, "_%d%d%d%d%d%d DAT",
53
        rtc.date[8], rtc.date[9], rtc.date[3], rtc.date[4], rtc.date[0], rtc.date[1]);
54
  }
55
  else {
56
    sprintf(str, "$1;1;error");
57
    sprintf(file_name, "_ERROR  DAT");
58
  }
59
  
60
  if(MMC_FILE_EXISTS == ffopen(file_name)){
61
    ffseek(file.length);   // Spult bis zum Dateiende vor um anzuhaengen, geht auch ohne Option MMC_OVER_WRITE !
62
  }
63
  
64
  ffwrites(str);  // write date to file
65
  
66
  // read temp ADC values
67
  adc_value = ADC_Read_Avg(2,3);
68
  temp = ((VREF*100*(long)adc_value)/(float)ADC_MAX_VALUE)-330;
69
  sprintf(str, "%3.1f;", temp);
70
  j=0;
71
  do {
72
    if (str[j] == '.')
73
      str[j]=',';
74
    j++;
75
  } while (str[j]!=0);
76
  ffwrites(str);
77
  
78
  adc_value = ADC_Read_Avg(3,3);
79
  temp = ((VREF*100*(long)adc_value)/(float)ADC_MAX_VALUE)-330;
80
  sprintf(str, "%3.1f;", temp);
81
  j=0;
82
  do { // dots to commas
83
    if (str[j] == '.')
84
      str[j]=',';
85
    j++;
86
  } while (str[j]!=0);
87
  ffwrites(str);
88
  
89
  for (i=0;i<10;i++) {
90
    mux_channel = i;
91
    
92
    PORTA =((PORTA & 0x0F) | (mux_channel<<4));
93
    _delay_us(10);
94
    
95
    adc_value = ADC_Read_Avg(0,3);
96
    voltage = (VREF*adc_value)/(float)ADC_MAX_VALUE;
97
    
98
    adc_value = ADC_Read_Avg(1,3);
99
    current = (VREF*10*adc_value)/(float)ADC_MAX_VALUE;
100
    
101
    PORTB |= (1<<PB3);  // set PB3
102
    _delay_us(10);
103
    adc_value = ADC_Read_Avg(1,3);
104
    current_short = (VREF*10*adc_value)/(float)ADC_MAX_VALUE;
105
    PORTB &= ~(1<<PB3);  // reset PB3
106
    
107
    sprintf(str, "%1.3f;%2.2f;%2.2f;", voltage, current, current_short);
108
    
109
    j=0;
110
    do { // dots to commas
111
      if (str[j] == '.')
112
        str[j]=',';
113
      j++;
114
    } while (str[j]!=0);
115
    ffwrites(str);
116
  }
117
  
118
  // new line
119
  ffwrite(0x0D);
120
  ffwrite(0x0A);
121
  ffclose();    //close file
122
  
123
  cycle++;
124
   
125
  PORTC &= ~(1<<PC5);  // reset PC5
126
  
127
  return;
128
}

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


Lesenswert?

Roman Dissauer schrieb:
> Im Prinzip wird alle 10 Sekunden die ISR von Timer1 ausgeführt welche
> die funktion "measure_and_save() aufruft.

Schlechter Programmierstil.  Eine ISR macht man kurz und flink.
Die sollte nur ein Flag setzen, und die main loop wertet dieses
Flag aus und triggert die weiteren Aktionen.

Ist allerdings in deinem Falle möglicherweise nicht ernstlich
relevant, da du ja nur einen Interrupt hast.

> ISR (TIMER1_OVF_vect)
> {
>  cli();  // disable interrupts

Du solltest mit dem Studium des Controller-Handbuchs beginnen.
Während einer ISR sind weitere Interrupts gesperrt ...

>  TCNT1 = 0x676A;  // preload timer for timebase 10s

Dafür nimmt man den CTC-Modus, statt hier manuell mit dem Timerwert
rumzufummeln.

>  sei();

Sowas mancht man in einer ISR nur, wenn man sich wirklich sicher
ist, was man tut.  Andernfalls riskiert man einen rekursiven
Interruptaufruf, und genau das könnte dein Problem sein.  Falls
es deine Funktion measure_and_save() nämlich nicht schafft, zwischen
zwei Interrupts fertig zu werden, ist der Teufel los.

Die oben genannte Variante mit einem Flag und der Auswertung in der
main loop hat dieses Risiko nicht; die würde dann nur eine Messung
verpassen, aber nicht in die (möglicherweise unendliche) Rekursion
laufen.

von holger (Gast)


Lesenswert?

sprintf(str, "%u$1;1;%d%d.%d%d.%d%d%d%d %d%d:%d%d:%d%d;",
        MCUCSR, rtc.date[0], rtc.date[1], rtc.date[3], rtc.date[4], 
rtc.date[6], rtc.date[7], rtc.date[8], rtc.date[9],
        rtc.time[0], rtc.time[1], rtc.time[3], rtc.time[4], rtc.time[6], 
rtc.time[7]);


Bist du sicher das dieser Rattenschwanz in das hier passt?

unsigned char str[30];

Da kommt möglicherweise dein Stacküberlauf her.
Verwende besser snprintf(str, 30, "..",..);
Dann gibts keinen Arrayüberlauf.

von Roman D. (smarties)


Lesenswert?

Danke Jörg für deine Kommentare! Jetzt läuft es schon über 40min! Zuvor 
gab es alle 5min einen reset.

Jörg Wunsch schrieb:
> Roman Dissauer schrieb:
>> Im Prinzip wird alle 10 Sekunden die ISR von Timer1 ausgeführt welche
>> die funktion "measure_and_save() aufruft.
>
> Schlechter Programmierstil.  Eine ISR macht man kurz und flink.
> Die sollte nur ein Flag setzen, und die main loop wertet dieses
> Flag aus und triggert die weiteren Aktionen.

Dass mein Programmierstil nicht der beste ist kann ich mir gut 
vorstellen. Ist mein erstes uC Projekt. :)

> Während einer ISR sind weitere Interrupts gesperrt ...
Wenn doch während einer ISR alle weiteren Interrupts gesperrt sind, muss 
doch egal sein, wie lange diese ausgeführt wird. Bei mir ist der 
Interrupt alle 10sek gekommen und das Ausführen der ISR dauerte keine 
100ms.

Ich hab den Code jetzt mal im ersten Schritt folgendermaßen geändert:
1
int measure = TRUE;
2
3
ISR (TIMER1_OVF_vect)
4
{
5
  cli();  // disable interrupts
6
  TCNT1 = 0x676A;  // preload timer for timebase 10s
7
  sei();
8
  
9
  if (count < interval) {
10
    count++;
11
  }
12
  else {
13
    measure = TRUE;
14
    count = 1;
15
  }
16
  return;
17
}
18
19
int main (void) {
20
21
...
22
23
while (1) {
24
  if (measure == TRUE) {
25
    measure_and_save();
26
    measure = FALSE;
27
  }
28
  else {
29
    nop();
30
  }
31
}
32
33
return 0; // never reached
34
}

Wie gesagt, ich versteh das zwar nicht ganz warum es jetzt funktioniert, 
aber ich werds mir merken, dass ISR kurz und bündig gemacht werden 
müssen.

>>  TCNT1 = 0x676A;  // preload timer for timebase 10s
>
> Dafür nimmt man den CTC-Modus, statt hier manuell mit dem Timerwert
> rumzufummeln.

Danke für die Info, werd ich auch noch ändern.

> Sowas mancht man in einer ISR nur, wenn man sich wirklich sicher
> ist, was man tut.  Andernfalls riskiert man einen rekursiven
> Interruptaufruf, und genau das könnte dein Problem sein.  Falls
> es deine Funktion measure_and_save() nämlich nicht schafft, zwischen
> zwei Interrupts fertig zu werden, ist der Teufel los.

Wie schon gesagt, meine ISR hat ca. 100ms zum ausführen gebraucht und 
der Interrupt ist alle 10 sek gekommen. Da es jetzt funktioniert, wird 
es wohl daran gelegen sein, aber verstanden hab ich es nicht ganz.

Eine Sache, die ich auch noch nicht ganz verstanden hab:
wenn ich in der Endlosschleife das
1
else {
2
    nop();
3
  }
weglasse, wird die funktion measure_and_save() nie ausgeführt!

von Roman D. (smarties)


Lesenswert?

holger schrieb:
> Bist du sicher das dieser Rattenschwanz in das hier passt?
>
> unsigned char str[30];

ja, da war schon viel mehr drin! :)

Ich hatte im Anfangsstadium der Software die komplette Zeile aus 
measure_and_save() in einem Array
unsigned char str[256];

Nachdem ich Verdacht auf einen Stack overflow hatte, hab ich das dann in 
einzelne kleine abschnitte unterteilt und zum Schluss erst \n\r gemacht.

> Verwende besser snprintf(str, 30, "..",..);

vielen Dank für den Input, ich werd das auch noch aufnehmen.

von Tom M. (tomm) Benutzerseite


Lesenswert?

Roman Dissauer schrieb:
> weglasse, wird die funktion measure_and_save() nie ausgeführt!

Hast du "measure" mittlerweile als volatile deklariert? Diese Variable 
änderst du in der ISR, das muss der Compiler wissen, damit er den Check 
beim if() keinesfalls wegoptimiert.

von Roman D. (smarties)


Lesenswert?

Tom M. schrieb:
> Hast du "measure" mittlerweile als volatile deklariert?

nachdem ich das gemacht hab, hat es auch ohne else{ nop(); } 
funktioniert. Vielen Dank!

Ich hab nun auch den Timer auf CTC umgestellt, funktioniert alles 
blendend! Ich lass das ding mal über Nacht laufen, mal schauen was 
passiert.

Vielen Dank allen nochmal für die schnelle Hilfe!

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


Lesenswert?

Roman Dissauer schrieb:

>> Während einer ISR sind weitere Interrupts gesperrt ...

> Wenn doch während einer ISR alle weiteren Interrupts gesperrt sind, muss
> doch egal sein, wie lange diese ausgeführt wird.

Jein.  Bei dir wäre es in der Tat egal, allerdings hast du ja
gleich am Anfang selbst die Interrupts freigegeben.

Wenn man mehr als einen Interrupt hat, dann führt eine lang
laufende ISR jedoch zu riesigen (und schwankenden) Latenzen
in der Interruptannahme, daher vermeidet man das.

von Rolf Magnus (Gast)


Lesenswert?

Roman Dissauer schrieb:
>> Während einer ISR sind weitere Interrupts gesperrt ...
> Wenn doch während einer ISR alle weiteren Interrupts gesperrt sind, muss
> doch egal sein, wie lange diese ausgeführt wird. Bei mir ist der
> Interrupt alle 10sek gekommen und das Ausführen der ISR dauerte keine
> 100ms.

Naja, während die ISR läuft, ist halt alles andere blockiert.

Jörg Wunsch schrieb:
> Wenn man mehr als einen Interrupt hat, dann führt eine lang
> laufende ISR jedoch zu riesigen (und schwankenden) Latenzen
> in der Interruptannahme, daher vermeidet man das.

Wenn während der Zeit ein anderer Interrupt mehrmals ankommt, verliert 
man auch welche. Wenn du z.B. beschließt, noch für irgendeine 
Zeitstempelung einen weiteren Timer im Millisekunden-Takt laufen zu 
lassen und dort einen Counter zu inkrementieren, dann verlierst du 
während deiner 100 ms halt 99 von den Inkrementierungen.

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.