Forum: Mikrocontroller und Digitale Elektronik Eigenartiges Verhalten des MSP430 ( Absturz / Neustart )


von Markus M. (mmax)


Lesenswert?

Hallo,

Ich hab immer wieder mal ein eigenartiges Verhalten meines Programmes, 
das auf einem MSP430G2553 laeuft, kompiliert mit msp-gcc. Da ich es 
schwer erklaeren kann, hab ich mal ein Video von der Ausgabe via UART 
auf der Konsole gemacht.

Das video (739KB) gibts hier:
http://www.airmax.at/uploads/msp430_hangup.avi

So wies ausschaut wird da mein Stackpointer "zerschossen" (willkuerliche 
Vermuting), auf jeden Fall startet die Mainroutine immer wieder neu, 
oder so. Ich denke mal es hat was mit sprintf und senden via UART zu 
tun, bin mir aber nicht sicher ... hat von euch jemand eine Idee?

Was fuehrt generell zu so einem Verhalten?
Kann ich das mit debuggen herausfinden?

So schaut mein main.c aus:
1
int main(void) {
2
    WDTCTL = WDTPW + WDTHOLD; // Stop WDT
3
    
4
    BCSCTL1 = CALBC1_1MHZ;    // Set "Basic clock system control 1" to 1MHz
5
    DCOCTL  = CALDCO_1MHZ;    // Set "DCO clock frequency control " to 1MHz
6
    
7
    P1DIR |= BIT0;
8
    P1OUT &= ~BIT0;
9
  
10
    // Initialize Hardware UART
11
    uart_init();
12
    
13
    __enable_interrupt();
14
    
15
    uart_write("\n\r-------------------------- START ------------------------------\n\r");
16
    
17
    ms5611_init();
18
    busy_waiting( 65000 );
19
    
20
    ms5611_read_prom();
21
    busy_waiting( 65000 );
22
    
23
    timer_counter = 0;
24
    
25
    TACCTL0 = CCIE;               // Set Capture/Compare interrupt enable
26
    TACCR0 = 4800;                // Timer a Capture/Compare register
27
    TACTL = TASSEL_2 + MC_2;      // Source = SMCLK (Sub-System master clock)
28
                                  // Mode control = MC_2 (Continuous mode)
29
    
30
    _BIS_SR(LPM0_bits + GIE);     // Enter LPM0 w/ interrupt
31
    
32
    return 0;
33
}
34
35
// Timer A0 interrupt service routine
36
#pragma vector=TIMER0_A0_VECTOR
37
__interrupt void Timer_A (void)
38
{
39
    if( (timer_counter%40) == 0 ) {
40
        sprintf( debug, "pres = %d\r\n", (int)ms5611_get_pressure() );
41
        uart_write(debug);
42
        
43
        P1OUT ^= BIT0;        // Hardbeat signal
44
        TACCR0 += 4800;       // Add Offset to CCR0
45
    }
46
    
47
    timer_counter++;
48
}

lG,
Max

von Tobias K. (kurzschluss81)


Lesenswert?

Also ein sprintf() ist generell auf einem Microcontroller eine schlechte 
Idee.
Aber in einer ISR ist das Tödlich.

von Markus M. (mmax)


Lesenswert?

Ich hab jetzt sprintf rausgeschmissen und mir nach Vorlage von

http://www.msp430launchpad.com/2012/06/using-printf.html

ein uart_printf gebaut. Kann man das in einer ISR verwenden oder sollte 
man darin gar nichts via UART senden?

von Dietrich L. (dietrichl)


Lesenswert?

Markus Manninger schrieb:
> So wies ausschaut wird da mein Stackpointer "zerschossen" (willkuerliche
> Vermuting), auf jeden Fall startet die Mainroutine immer wieder neu,
> oder so.

Ja, denn wo ist die Endlosschleife in "main"? Du verlässt ja "main" mit 
"return 0;" und landest im Nirvana. Oder ist da noch was versteckt in 
einer Funktion, was man so nicht sieht?

Gruß Dietrich

von Markus M. (mmax)


Lesenswert?

Mit "_BIS_SR(LPM0_bits + GIE);" leg ich den MC schlafen ... der Rest 
wird zyklisch in der Timer_A Routine erledigt. Das sollte so schon 
passen.

lG,
Max

von Dietrich L. (dietrichl)


Lesenswert?

Markus Manninger schrieb:
> Mit "_BIS_SR(LPM0_bits + GIE);" leg ich den MC schlafen ... der Rest
> wird zyklisch in der Timer_A Routine erledigt. Das sollte so schon
> passen.
Aber er muss ja irgendwann wieder aufwachen, sonst tut er ja nie was.
Und wenn er aufwacht und die Timer_A Routine ausführt, dann ist er beim 
Verlassen der Routine immer noch wach und macht bei "main" weiter. Oder 
ist die Endlosschleife in "_BIS_SR(LPM0_bits + GIE);" und wird dort 
erneut schlafen gelegt?

Gruß Dietrich

von Martin (Gast)


Lesenswert?

Markus Manninger schrieb:
> Das sollte so schon passen.

Offensichtlich nicht...
Du legst ihn zwar schlafen, aber was passiert, wenn er durch den 
Interrupt wieder aufgeweckt wird?

Pack doch einfach mal ein while(1) um Dein "_BIS_SR(LPM0_bits + GIE);".

von Markus M. (mmax)


Lesenswert?

Das Timer wird continuierlich ausgefuehrt. Die ISR wird also immer 
wieder aufgerufen, waehrend die main angehalten ist.

Sowas funktioniert ja auch problemlos:
1
int main(void)
2
{
3
        WDTCTL = WDTPW + WDTHOLD;                       // Stop WDT
4
5
        BCSCTL1 = CALBC1_1MHZ;
6
        DCOCTL  = CALDCO_1MHZ;
7
8
        P1DIR |= BIT0;                                  // set P1.0 (LED1) as output
9
        P1OUT &= ~BIT0;                                 // P1.0 low
10
11
        // Configure Timer A
12
        TACCTL0 = CCIE;                                 // CCR0 interrupt enabled
13
        TACCR0 = 1000;                                  // 
14
        TACTL = TASSEL_2 + ID_2 + MC_3;                 // SMCLK, /4, upmode/down
15
16
        _BIS_SR(LPM0_bits + GIE);                       // Enter LPM0 w/ interrupt
17
        return 0;
18
}
19
20
// Timer A0 interrupt service routine
21
#pragma vector=TIMER0_A0_VECTOR
22
__interrupt void Timer_A (void)
23
{
24
  
25
        P1OUT ^= BIT0;                                  // Toggle LED
26
        TACCR0 += 1000;
27
}

von Dietrich L. (dietrichl)


Lesenswert?

Markus Manninger schrieb:
> Das Timer wird continuierlich ausgefuehrt. Die ISR wird also immer
> wieder aufgerufen, waehrend die main angehalten ist.

Hast Du denn wenigstens mal ausprobiert, eine Endlosschleife vor das 
"return 0;" zu schreiben? Das kostet nichts.

Außerdem: es wird nicht "main" angehalten, sondern der Prozessor. Und 
wenn er durch den Interrupt wieder startet, läuft auch "main" weiter, 
wenn nach Ende der ISR und erneutem Aufruf doch eine Lücke sein sollte.

Was für ein Interrupt ist das denn überhaupt?

> Sowas funktioniert ja auch problemlos:

Auch wenn ein Programm anscheinend funktioniert, muss es nicht 
fehlerfrei sein. Ob der Fehler sichtbar wird hängt u.U. noch von vielen 
anderen Bedingungen ab.

Gruß Dietrich

von Jörg S. (joerg-s)


Lesenswert?

Dietrich L. schrieb:
> Außerdem: es wird nicht "main" angehalten, sondern der Prozessor. Und
> wenn er durch den Interrupt wieder startet, läuft auch "main" weiter,
Nö, solange kein Exit LPM in der ISR steht, schläft er weiter.

von Dietrich L. (dietrichl)


Lesenswert?

Jörg S. schrieb:
> Nö, solange kein Exit LPM in der ISR steht, schläft er weiter.

OK, das wusste ich nicht (ich kenne den MSP diesbezüglich nicht).

von Jörg S. (joerg-s)


Lesenswert?

Markus Manninger schrieb:
> Ich hab jetzt sprintf rausgeschmissen und mir nach Vorlage von
> http://www.msp430launchpad.com/2012/06/using-printf.html
> ein uart_printf gebaut.
Und das macht den gleichen Fehler?

von Marc N. (Gast)


Lesenswert?

Jetzt schreibe ein while(1) rein. Das ist doch nicht so schwer. Ich 
hasse diese renitenten Besserwisser ohne den geringsten Anteil von 
Ahnung ...

von Markus M. (mmax)


Lesenswert?

Baue ich das Auslesen vom Temperatur und Druck des Sensors in die main() 
ein, also ganz ohne Timer Interrupt, funktioniert alles:
1
int main() {
2
 ...
3
    while( 1 ) {
4
        P1OUT ^= BIT0;
5
        
6
        uart_printf( "temp = %l\r\n", ms5611_get_temperature() );
7
        delay_ms( 29 );
8
        uart_printf( "pres = %l\r\n", ms5611_get_pressure() );
9
        
10
        delay_ms( 1000 );
11
    }

Ich dachte mir aber dass es ja kein Problem sein sollte das in der ISR 
zu machen, solange die Abarbeitung nicht laenger dauert wie der naechste 
Interrupt auftritt. In der ISR funktionierts auch mit der uart_pintf 
nicht.

Da die endgueltige Schaltung mit Solar betrieben werden soll, will/muss 
ich Strom sparen. Wie kann ich denn am besten jede Sekunde eine Messung 
durchfuehren ohne in einer while schleife zu loopen?

Danke,
Max

von Markus M. (mmax)


Lesenswert?

Was aber funktioniert, ist den Timer via "TACCTL0 &= ~CCIE;" in der ISR 
zu deaktivieren, Sensor auslesen und wenn alles erledigt ist, den Timer 
via "TACCTL0 = CCIE;" wieder scharf zu schalten.

Sollte man das so machen?

von Markus M. (mmax)


Lesenswert?

@Marc N.:
Es funktioniert auch mit einem while(1) in der main nicht!
Ich bin sicher kein Besserwisser, ganz im Gegenteil. Aber ich will Dinge 
verstehen und haette gerne eine Erklaerung weshalb was funktioniert bzw. 
nicht funktioniert.

von Jörg S. (joerg-s)


Lesenswert?

Markus Manninger schrieb:

> Ich dachte mir aber dass es ja kein Problem sein sollte das in der ISR
> zu machen, solange die Abarbeitung nicht laenger dauert wie der naechste
> Interrupt auftritt.
Selbst wenn es länger dauert, sollte der µC trotzdem nicht abstürzen.


>Was aber funktioniert, ist den Timer via "TACCTL0 &= ~CCIE;" in der ISR
>zu deaktivieren, Sensor auslesen und wenn alles erledigt ist, den Timer
>via "TACCTL0 = CCIE;" wieder scharf zu schalten.
Das deutet natürlich auf ein Timing Problem hin. Timer kommt bevor 
letzte ISR abgeschlossen ist. Allerdings sollte das die bestehende ISR 
nicht kümmern .

Was ich mir vorstellen könnte: Die UART Routine soll das letzte Byte 
ausgeben, das wird in den Buffer übertragen, die UART Routine wartet 
aber nicht bis es fertig übertragen worden ist und kehrt in die ISR 
zurück die gleich wieder in die main und somit in den LPM springt und 
die Ausgabe des letzten Bytes abwürgt.

Hast du eigentlich kein Debugger? Dann kannst du dir doch wunderschön 
ansehen was passiert.


Was sollen die Zeilen:
TACCR0 += 4800;
und
((timer_counter%40) == 0 )
bewirken?

von Martin (Gast)


Lesenswert?

Markus Manninger schrieb:
> Was aber funktioniert, ist den Timer via "TACCTL0 &= ~CCIE;" in der ISR
> zu deaktivieren, Sensor auslesen und wenn alles erledigt ist, den Timer
> via "TACCTL0 = CCIE;" wieder scharf zu schalten.

Markus Manninger schrieb:
> Ich dachte mir aber dass es ja kein Problem sein sollte das in der ISR
> zu machen, solange die Abarbeitung nicht laenger dauert wie der naechste
> Interrupt auftritt.

Bist Du Dir sicher, dass dem so ist?

Bist Du Dir wirkich sicher, dass Dein Prozessor nicht doch aufwacht, an 
das Ende der Main kommt und seltsame Sachen macht (z.B. einen Reset oder 
den PC komplett durchzählen, irgendwelchen Müll dabei machen und dann 
wieder von vorne anfangen...)

von Tobias K. (kurzschluss81)


Lesenswert?

Ich würde dir mal dieses Dokument empfehlen um den Umgang mit ISR 
Sleepmode und ähnlichem anzulesen
http://www.ti.com/lit/an/slaa294a/slaa294a.pdf
Da geht es um MSP430 Software Coding Techniques

von Markus M. (mmax)


Lesenswert?

Jörg S. schrieb:
> Was ich mir vorstellen könnte: Die UART Routine soll das letzte Byte
> ausgeben, das wird in den Buffer übertragen, die UART Routine wartet
> aber nicht bis es fertig übertragen worden ist und kehrt in die ISR
> zurück die gleich wieder in die main und somit in den LPM springt und
> die Ausgabe des letzten Bytes abwürgt.
>
> Hast du eigentlich kein Debugger? Dann kannst du dir doch wunderschön
> ansehen was passiert.

Davor hab ich mich bis jetzt gedrueckt ... werd mich aber mit dem 
mspdebug wohl beschaeftigen muessen.

> Was sollen die Zeilen:
> TACCR0 += 4800;

Ich befuerchte das ist eh um sonst, da der Timer immer bis FFFFh zaehlt 
und dann feuert, da ich den MC_2 mode benutze.

> ((timer_counter%40) == 0 )

Naja, damit fuehre ich den Code innerhalb der if Abfrage nur jeden 40ten 
ISR Aufruf aus. Auch nicht sehr schoen, ich weiss. Aber ich bekomm mit 
1MHz Clock keine vernueftige Sekunde zusammen. Werd aber in Zukunft eh 
mit 32kHz takten.

Gibts eigentlich eine Moeglichkeit zu zaehlen wieviele CPU cycles 
zwischen zwei Iterationen (Codestellen) vergangen sind?

von auf die schnelle (Gast)


Lesenswert?

TACCR0 += 4800;       // Add Offset to CCR0

Das muss außerhalb der if Bedingung stehen. Sonst fehlt der Wert für den 
nächsten compare.

von wk (Gast)


Lesenswert?

Hi,
wie sieht denn dein UART printf aus?
Verwendest du UART interrupts?

grüße

von mmax (Gast)


Lesenswert?

Ich hab das printf via UART von

http://www.msp430launchpad.com/2012/06/using-printf.html

übernommen. Denke dass sprintf auf einem MC nicht so gut ist, mit dem 
uart_printf funktionierts recht gut ... aber das Hauptproblem war, dass 
ich innerhalb der Timer ISR den Timer nicht angehalten hab. Seitdem ich 
das mache, funktioniert auch alles soweit.

von Markus M. (mmax)


Lesenswert?

So, jetzt hab ichs glaub ich recht sauber umgesetzt. In der while() leg 
ich den MC schlafen und setz vorher den Timer, der den MC wieder 
aufweckt. Simple und wie von TI empfohlen
1
int main(void) {
2
    WDTCTL = WDTPW + WDTHOLD; // Stop WDT
3
    
4
    BCSCTL1 = CALBC1_1MHZ;    // Set "Basic clock system control 1" to 1MHz
5
    DCOCTL  = CALDCO_1MHZ;    // Set "DCO clock frequency control " to 1MHz
6
    
7
    P1DIR |= BIT0;
8
    P1OUT &= ~BIT0;
9
10
    // Configure TimerA0
11
    TACTL = TASSEL_2 | ID_3 | MC_3;   // Source = SMCLK (Sub-System master clock)
12
                                      // Mode control = MC_1 (Up mode)
13
    
14
    while( 1 ) {
15
        TACCR0  = 62500;          // Add Offset to CCR0
16
        TACCTL0 = CCIE;           // Start timer interrupt
17
        _BIS_SR(LPM0_bits + GIE); // Enter LPM0 w/ interrupt
18
        
19
        P1OUT ^= BIT0;
20
    }
21
22
    return 0;
23
}
24
25
// Timer A0 interrupt service routine
26
#pragma vector=TIMER0_A0_VECTOR
27
__interrupt void Timer_A (void)
28
{
29
    _BIC_SR_IRQ(LPM0_bits);
30
}

Mich wunderst nur dass mein LED nicht "genau" im 1 Sekunden Takt blinkt. 
Bei 1MHz Tankt, 1/8 Teiler (ID_3), Up- Down Mode (MC_3) und TACCR0  = 
62500 muesste ich da doch einigermassen hinkommen. 62500 x 8 x 2 = 
1000000, oder was versteh ich da falsch?

Und gibts keine Moeglichkeit zwischen zwei Durchlauefen in der while 
Schleife die Anzahl der CPU Ticks auszulesen?

von auf die schnelle (Gast)


Lesenswert?

Die Aktualisierung des Timers muss in der ISR erfolgen.

Vergiss vorerst die Sache mit dem Low Power Mode. Das kannst du später 
noch nachholen.

Dietrich L. schrieb:
> Und wenn er aufwacht und die Timer_A Routine ausführt, dann ist er beim
> Verlassen der Routine immer noch wach und macht bei "main" weiter.
Nein, der Power Mode steht im PSW und wird mit dem RETI restauriert. Es 
wird kein weiterer Befehl im Hauptprogramm ausgeführt. Man findet viele 
Programmbeispiele für den MSP ohne Endlosschleife.

von Jörg S. (joerg-s)


Lesenswert?

Markus Manninger schrieb:
> Mich wunderst nur dass mein LED nicht "genau" im 1 Sekunden Takt blinkt.
Wie blinkt er denn?


> Und gibts keine Moeglichkeit zwischen zwei Durchlauefen in der while
> Schleife die Anzahl der CPU Ticks auszulesen?
Nein. Dafür kannst du Timer nutzen ;)

von Markus M. (mmax)


Lesenswert?

Jörg S. schrieb:
> Markus Manninger schrieb:
>> Mich wunderst nur dass mein LED nicht "genau" im 1 Sekunden Takt blinkt.
> Wie blinkt er denn?

Naja, nicht ganz genau eine Sekunde. Das passt aber schon.

>> Und gibts keine Moeglichkeit zwischen zwei Durchlauefen in der while
>> Schleife die Anzahl der CPU Ticks auszulesen?
> Nein. Dafür kannst du Timer nutzen ;)

Schade, das waere sehr praktisch.

von Jörg S. (joerg-s)


Lesenswert?

Markus Manninger schrieb:
> Schade, das waere sehr praktisch.
Der Timer macht ja genau das was du dir davon versprichst.

von auf die schnelle (Gast)


Lesenswert?

So, mal auf die schnelle aufs launchpad gepackt:
1
#include "io430g2553.h"
2
3
typedef unsigned char     uint8_t;
4
typedef unsigned int      uint16_t;
5
6
volatile uint16_t ticks = 0;
7
8
void main( void )
9
{
10
    // stop watchdog timer to prevent time out reset
11
  WDTCTL = WDTPW + WDTHOLD;
12
   // clock source DCO, set to 1MHz
13
  BCSCTL1 = CALBC1_1MHZ;
14
  DCOCTL = CALDCO_1MHZ;
15
   // init port
16
  P1DIR |= BIT0;                        // Set P1.0 to output direction
17
   // WDT Timer 8ms
18
  WDTCTL = WDT_MDLY_8;
19
   // enable interrupts
20
  IE1 |= WDTIE;
21
  __enable_interrupt();
22
  while(1)
23
  {
24
     // do something
25
    if(ticks % 2) P1OUT |= BIT0;
26
    else P1OUT &= ~BIT0;
27
  }
28
}
29
30
 //Watchdog Timer interrupt service routine
31
#pragma vector=WDT_VECTOR
32
__interrupt void WDT_ISR (void)
33
{
34
  static uint8_t count = 0;
35
  if(count++ >= 125)
36
  {
37
    count = 0;
38
    ticks++;
39
  }
40
}
Anstatt Timer A habe ich den WDT genommen.

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.