mikrocontroller.net

Forum: Compiler & IDEs Reset durch Stack overflow?


Autor: Roman D. (smarties)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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!

Autor: holger (Gast)
Datum:

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

Autor: Stephan W. (sir_wedeck)
Datum:

Bewertung
0 lesenswert
nicht 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!!!!)
count= 0
for 0xFFFF to 0xFF00 Step -1
  if (*0xFFFF != 0xA5) then
    count++;
next

Print "Der Stack ist " + count + " Bytes gross!"

Vielleicht hilft es dir.

Stephan

Autor: Kurti (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wo ist die main.c ?

Autor: Roman D. (smarties)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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.
ISR (TIMER1_OVF_vect)
{
  cli();  // disable interrupts
  TCNT1 = 0x676A;  // preload timer for timebase 10s
  sei();
  
  if (count < interval) {
    count++;
  }
  else {
    measure_and_save();
    count = 1;
  }
  sleep_mode();
}

...


//******************************
// get ADC values and save to SD
//******************************

void measure_and_save (void) {
  
  toggle_led();
  unsigned char str[30];
  unsigned char file_name[13]="DATA    DAT";
  unsigned int i, j, mux_channel;
  RTC_DATE_TIME rtc;
  
  unsigned int adc_value;
  double temp;
  double voltage;
  double current;
  double current_short;
  
  
  lcd_home();
  char test[5];
  sprintf (test, "%u", MCUCSR);
  lcd_string(test);
  
  
  //if(MMC_FILE_NEW == ffopen(file_name)){;}
    
  
  if (rtc_read(&rtc) != RTC_ERROR){
    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]);
    sprintf(file_name, "_%d%d%d%d%d%d DAT",
        rtc.date[8], rtc.date[9], rtc.date[3], rtc.date[4], rtc.date[0], rtc.date[1]);
  }
  else {
    sprintf(str, "$1;1;error");
    sprintf(file_name, "_ERROR  DAT");
  }
  
  if(MMC_FILE_EXISTS == ffopen(file_name)){
    ffseek(file.length);   // Spult bis zum Dateiende vor um anzuhaengen, geht auch ohne Option MMC_OVER_WRITE !
  }
  
  ffwrites(str);  // write date to file
  
  // read temp ADC values
  adc_value = ADC_Read_Avg(2,3);
  temp = ((VREF*100*(long)adc_value)/(float)ADC_MAX_VALUE)-330;
  sprintf(str, "%3.1f;", temp);
  j=0;
  do {
    if (str[j] == '.')
      str[j]=',';
    j++;
  } while (str[j]!=0);
  ffwrites(str);
  
  adc_value = ADC_Read_Avg(3,3);
  temp = ((VREF*100*(long)adc_value)/(float)ADC_MAX_VALUE)-330;
  sprintf(str, "%3.1f;", temp);
  j=0;
  do { // dots to commas
    if (str[j] == '.')
      str[j]=',';
    j++;
  } while (str[j]!=0);
  ffwrites(str);
  
  for (i=0;i<10;i++) {
    mux_channel = i;
    
    PORTA =((PORTA & 0x0F) | (mux_channel<<4));
    _delay_us(10);
    
    adc_value = ADC_Read_Avg(0,3);
    voltage = (VREF*adc_value)/(float)ADC_MAX_VALUE;
    
    adc_value = ADC_Read_Avg(1,3);
    current = (VREF*10*adc_value)/(float)ADC_MAX_VALUE;
    
    PORTB |= (1<<PB3);  // set PB3
    _delay_us(10);
    adc_value = ADC_Read_Avg(1,3);
    current_short = (VREF*10*adc_value)/(float)ADC_MAX_VALUE;
    PORTB &= ~(1<<PB3);  // reset PB3
    
    sprintf(str, "%1.3f;%2.2f;%2.2f;", voltage, current, current_short);
    
    j=0;
    do { // dots to commas
      if (str[j] == '.')
        str[j]=',';
      j++;
    } while (str[j]!=0);
    ffwrites(str);
  }
  
  // new line
  ffwrite(0x0D);
  ffwrite(0x0A);
  ffclose();    //close file
  
  cycle++;
   
  PORTC &= ~(1<<PC5);  // reset PC5
  
  return;
}



Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: holger (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Roman D. (smarties)
Datum:

Bewertung
0 lesenswert
nicht 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:
int measure = TRUE;

ISR (TIMER1_OVF_vect)
{
  cli();  // disable interrupts
  TCNT1 = 0x676A;  // preload timer for timebase 10s
  sei();
  
  if (count < interval) {
    count++;
  }
  else {
    measure = TRUE;
    count = 1;
  }
  return;
}

int main (void) {

...

while (1) {
  if (measure == TRUE) {
    measure_and_save();
    measure = FALSE;
  }
  else {
    nop();
  }
}

return 0; // never reached
}

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
else {
    nop();
  }
weglasse, wird die funktion measure_and_save() nie ausgeführt!

Autor: Roman D. (smarties)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Tom M. (tomm) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Roman D. (smarties)
Datum:

Bewertung
0 lesenswert
nicht 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!

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.