Forum: Mikrocontroller und Digitale Elektronik MSP430 Timer soll while Schleife abbrechen


von Jens (Gast)


Lesenswert?

Hallo Zusammen,

ich möchte eine Wartezeit programmieren, damit sich die Hardware erstmal 
beruhigen kann (nicht der MSP430 selber aber was daran hängt).

Hierfür wollte ich den Timer B nutzen, dieser hängt der über XT2 mit 8 
Mhz Quartz und Divider 8 auf SMCLK und Divder 8 auf den Timer B 
initialisiert wurde.

In wait() will ich den TimerB starten und im Interrupt von TimerB wird 
das Flag Ready auf 1 gesetzt und dies beendet die while Schleife in 
wait(). Zumindest war dies mein Plan. Das ganze bleibt aber in der while 
Schleife hängen, und der Interrupt von TimerB wird nie aufgerufen (mit 
Breakpoint überwacht). Ich habe mir in der while Schleife den Counter 
TBR anzeigen lassen, der wird brav immer wieder hochgezählt, aber der 
Interrupt wird nie ausgelöst. Kann mir einer sagen was ich falsch mache? 
Wenn ich keine while Schleife nutze wird irgendwann der Interrupt 
ausgelöst.

Bin für jede Hilfe dankbar.

Grüße Jens

Auszug aus main:
  WDTCTL = WDTPW + WDTHOLD;     // Stop watchdog timer
  BCSCTL1 = 0;
  _BIC_SR(OSCOFF);              // Schaltet LFXT1 aus im Statusregister

  do                            // do Schleife wartet bis XT2 stabil ist
  {
    IFG1 &=~OFIFG;
    for(i=0xFF;i>0;i--);
  }
  while (IFG1&OFIFG);           // diese while Schleife ist nicht 
gemeint
  BCSCTL2 = SELM1 + SELS + DIVS0 + DIVS1; // MCLK-Source ist XT2,
                                          //SMCLK-Source ist XT2,
                                          //Divider MCLK = 1,
                                          //Divider SMCLK = 8
  ...
  wait();
  ...

Subroutinen:
// **************************************
// Init Timer B
// **************************************
void init_timer_B(void)
{
  TBCCTL0 = CCIE;            // CCR0 interrupt enabled
  stopTimerB();
  TBCTL = TBSSEL1 + ID0 + ID1 + MC0;  // SMCLK, divide by 8

  if (Master == 0)
  {
    StatusTimerB = 0;
    CounterTimerB = TIMERBTAKT;
    runTimerB();
  }
  else
  {
    stopTimerB();
  }
  _BIS_SR(GIE);                           // General interrupt enable
  _EINT();
}

// *****************************
// Stopt Timer B
// *****************************
void stopTimerB(void)
{
  TBCCR0 = 0;
  TBCTL &= ~MC0;
}

// *****************************
// Startet Timer B
// *****************************
void runTimerB(void)
{
  TBCTL |= MC0;
  TBCCR0 = 0;
  TBCCTL0 = CCIE;             // CCR0 interrupt enabled
  TBCCR0 = CounterTimerB;
}

//*******************************
// Wartet bis Timertaktweg
//*******************************
void wait(void)
{
  Ready = 0;
  stopTimerB();
  StatusTimerB = 3;             // Flag für TimerB setzten, signalisiert
                                // Warten bis wieder dran
  CounterTimerB = TIMERBTAKTWEG;
  runTimerB();                  // TimerB starten
  while (Ready==0)
  {
    for(;;);                    // bleibt hier hängen
                                // TBR wir aber hochgezählt
  }
}

// ***************************
// Timer B interrupt
// ***************************
#pragma vector=TIMERB0_VECTOR
__interrupt void Timer_B(void)
{
  ...
  switch(StatusTimerB)      // hier Breakpoint gestezt kommt nie an :(
  {

   ...

    case 3:
        stopTimerB();
        ...
        Ready = 1;     // hier soll Flag für while Schleife geändert 
werden
      break;
  }
}

von Analog (Gast)


Lesenswert?

TIMSK wo wird das bei dir gesetzt ? MCUCR ? zeig mal!

von Analog (Gast)


Lesenswert?

sorry ist ja msp. hast du vielleicht vergessen den Interrupt 
einzuschalten`??

von Jens (Gast)


Lesenswert?

Hi,

danke für die Antwort. Eigentlich sollte der Interrupt eingeschaltet 
sein. Zum einen im Statusregister in main

_BIS_SR(GIE);

und für den TimerB in der Subroutine

in init_Timer_B

TBCCTL0 = CCIE;            // CCR0 interrupt enabled

Wenn ich die while Schleife rausnehme gehts ja auch.

Vielleicht verstehe ich etwas falsch.

Danke jedenfalls

Gruß Jens

von Stefan (Gast)


Lesenswert?

StatusTimerB und Ready sind als volatile deklariert?

von Jens (Gast)


Lesenswert?

Beide werden global deklariert (oberhalb von main)

P.S. Mit den genauen Begriffen hakts etwas bei mir. Ich denke volatil 
bedeut innerhalb der Subroutine, sind sich nicht. Falls es was anderes 
bedeutet bräuchte ich eine kurze Erläuterung.

Danke für die Antwort.

Gruss Jens

von Stefan (Gast)


Lesenswert?

Globale Variablen, die auch in Interrupt-Service-Routinen (ISR) geändert 
werden können, musst Du explizit als volatile deklarieren:
1
volatile int Beispiel;

Ansonsten kann es Dir passieren, dass Dein Compiler den Zugriff auf 
Deine Variablen derart optimiert, dass nicht mehr das gewünschte 
Ergebnis rauskommt (Siehe Dein Problem)!

von Jens (Gast)


Lesenswert?

Danke Stefan,

ich werde es gleich probieren.

Grüße

Jens

von Jens (Gast)


Lesenswert?

Hi nochmal,

leider löst das mein Problem nicht. Das wird zwar richtig sein was 
Stefan sagt, aber das Problem ist das der Interrupt von TimerB gar nicht 
aufgerufen wird, obwohl TBR hochzählt. Er bleibt einfach in der while 
Schleife hängen.

Ist irgendwas generelles falsch?

Grüße

Jens

von Jens (Gast)


Lesenswert?

Hi ich habs probiert leider bleibt er immer noch hängen.

Grüße

Jens

von Stefan (Gast)


Lesenswert?

OK

Was glaubst Du denn, was hier passiert?
1
while (Ready==0)
2
  {
3
    for(;;);                    // bleibt hier hängen
4
                                // TBR wir aber hochgezählt
5
  }

Aus der for-Schleife wirst Du nimmer mehr herauskommen, die hat kein 
Abbruchkriterium!

Du brauchst lediglich das hier:
1
while(!Ready);

von Jens (Gast)


Lesenswert?

Hi Stefan,

hab's gerade ausprobiert. Gleiches Ergebnis. Der msp bleibt in der while 
Schleife hängen.

Es stimmt zwar das er aus der for Schleife nicht rauskommt, aber vor dem 
Aufruf der for Schleife habe ich eigentlich den TimerB gestartet und 
wenn der Interrupt von TimerB aufgerufen wird, wird in der Interrupt 
Routine Ready auf 1 gesetzt. Das sollte dann die Schleife abbrechen.

Nur wird dieser verflixte Interrupt nicht aufgerufen obwohl TBR 
hochzählt und der IR freigeschaltet ist. Ich glaube ich mache irgendwas 
grundsätzliches falsch.

Mit

while(CounterTimerB != TBR)
{
  for(;;);
}

klappt es jetzt, aber eigentlich ist das unsauber programmiert. Falls 
Jemand eine Lösung mit Interrupt hat wäre ich nachwievor dankbar.

Danke Stefan auf jeden Fall.

Grüße

Jens

von Stefan (Gast)


Lesenswert?

>Es stimmt zwar das er aus der for Schleife nicht rauskommt, aber vor dem
>Aufruf der for Schleife habe ich eigentlich den TimerB gestartet und
>wenn der Interrupt von TimerB aufgerufen wird, wird in der Interrupt
>Routine Ready auf 1 gesetzt. Das sollte dann die Schleife abbrechen.

Dann hast Du wohl Glück, dass Dein Compiler die Nutzlosigkeit der 
for-Schleife erkannt und diese weg-optimiert hat. Denn wenn Du einmal in 
der for-Schleife sitzt, dann kommst Du nie mehr zur Abfrage des 
Abbruchkriteriums in der while-Schleife!!!

So aus purer Verzweiflung... mal die Errata für Deinen MSP angeguckt... 
TimerB Bug...???

von Jens (Gast)


Lesenswert?

Wo finde ich im IAR Compiler die Errata? Beim Compilieren heißt es null 
Errors und null Warnings.

von Supa M. (supa_micha)


Lesenswert?

Kann es sein, dass du immer wieder ein Interrupt kriegst?

Und zwar hast du den Interruptvektor TIMERB0_VECTOR definiert, der ist 
aber für die Capture and Compare Register des Timermoduls zuständig. 
Versuche anstatt dessen mal den TIMERB1_VECTOR zu verwenden.

Michael

von Jens (Gast)


Lesenswert?

Hi Supa Micha,

jetzt habe ich alles auf de TimerB1_Vector umgeschrieben,

inklusive TBCCR1 = 0; beim stoppen des Timers

Und TBCCR1 = CounterTimerB;
und TBCCTL1 = CCIE;

beim starten. Gleiches Ergebnis. Initialisiere ich vielleicht falsch?

von Jens (Gast)


Lesenswert?

Sind nicht alle 7 Interrupt gleichermaßen für Capture und Compare 
zuständig?

von szimmi (Gast)


Lesenswert?

Kannst Du bitte mal Deinen kompletten Code posten ?

von Jens (Gast)


Lesenswert?

Das sind 27 Seiten, was genau brauchst du den davon? Die meisten 
Subroutinen dürften von keinem besonderen Interesse sein.

Grüße

Jens

von szimmi (Gast)


Lesenswert?

Ok. Dann anders. Lass das Programm laufen und halte es nach geraumer 
Zeit (z.B. 5 Sekunden) mal an. Dann schau Dir im Debugger mal die SFR's 
an.
Ist das CCIE wirklich gesetzt? Wird der Timer geclocked ? Ist das GIE 
wirklich gesetzt (SR)?

von Jens (Gast)


Lesenswert?

Hi szimmi ,

jetzt habe ich noch etwas ausführlicher gelistet. Wie gesagt Ready und 
StutstimerB sind mittlerweile als volatile global definiert. Der Punkt 
ist, das der Interrupt für TimerB nicht aufgerufen wird, TBR aber immer 
wieder hochgezählt wird.???

Nee Idee?

//*******************************
// Hauptroutine
//*******************************
void main(void)
{
  float Alpha, Beta;                    // Argumente von Cosinus und 
Sinus Werten
  int wert;                             // Buffer
  int NN;

  WDTCTL = WDTPW + WDTHOLD;               // Stop watchdog timer
  shortBreak(10);
  BCSCTL1 = 0;
  _BIC_SR(OSCOFF);              // Schaltet LFXT1 aus im Statusregister
  do                            // do Schleife wartet bis XT2 stabil ist
  {
    IFG1 &=~OFIFG;
    for(i=0xFF;i>0;i--);
  }
  while (IFG1&OFIFG);

  BCSCTL2 = SELM1 + SELS + DIVS0 + DIVS1;

...

  // *****************************************
  // Timer initialisieren
  // *****************************************
  init_timer_A();
  init_timer_B();

...

} // Ende Main Loop

// **************************************
// Init Timer A
// **************************************
void init_timer_A(void)
{
  TACCR0 = TIMERAVALUE;                  // Load timer compare register
  TACTL = TASSEL1 + ID0 + ID1 + MC0 + MC1;// SMCLK, divide by 8, up a. 
down
  if (Master == 1)
  {
    runTimerA();                   // Timer A läuft permanet "up and 
down"
                                   // run Timer A enabled nur den 
Interrupt
  }
  _BIS_SR(GIE);                    // General interrupt enable
}

// **************************************
// Init Timer B
// **************************************
void init_timer_B(void)
{
  TBCCTL0 = CCIE;                      // CCR0 interrupt enabled
  stopTimerB();                       // Nur Startwert wird später 
geändert
  TBCTL = TBSSEL1 + ID0 + ID1 + MC0;  // SMCLK, divide by 8, up

  if (Master == 0)
  {
    StatusTimerB = 0;
    CounterTimerB = TIMERBTAKT;
    runTimerB();
  }
  else
  {
    stopTimerB();
  }
  _BIS_SR(GIE);                           // General interrupt enable
  _EINT();
}

// *****************************
// Stopt Timer B
// *****************************
void stopTimerB(void)
{
  TBCCR0 = 0;
  TBCTL &= ~MC0;
}

// *****************************
// Startet Timer B
// *****************************
void runTimerB(void)
{
  TBCTL |= MC0;
  TBCCR0 = 0;
  TBCCTL0 = CCIE;                      // CCR0 interrupt enabled
  TBCCR0 = CounterTimerB;
}

//******************************************
// Wechselt zwischen Master und Slave
//******************************************
void toggle_master_slave(void)
{
...
  wait();
...
}

//***************************
// Timer A interrupt
//***************************
#pragma vector=TIMERA0_VECTOR
__interrupt void Timer_A(void)
{
...
toggle_master_slave();
...
}

//*******************************
// Wartet bis Timertaktweg
//*******************************
void wait(void)
{
  Ready = 0;
  stopTimerB();
  StatusTimerB = 3;   // Flag für TimerB setzten
  CounterTimerB = TIMERBTAKTWEG;
  runTimerB();  // TimerB starten
  while (Ready==0)
  {
    for(;;);
  }
}

von Jens (Gast)


Lesenswert?

So,

was SFR sind weiß ich ehrlich nicht, da ich nicht so firm in der Materie 
bin.

Ich habe aber das Programm mal laufen lassen und mir nach 5 Sekunden den 
Wert für TBCCTL0 anzeigen lassen. Der ist 17, das interpretiere ich so, 
dass Bit und Bit 0 und Bit 4 gesetzt sind, das wäre CCIFG und CCIE. CCIE 
habe ich auch gesetzt.

Das müßte bedeuten Interrupt enabled (CCIE) und sogar das er ausgelöst 
(CCIFG) wurde, aber warum springt er nicht rein. Ohne wihle loop tut er 
es, leider irgendwo aus dem Program raus.

:(

von szimmi (Gast)


Lesenswert?

Alles sehr mystisch. Was steht nach den 5s im Register SR? Ist GIE 
gesetzt?
Bitte mal für Testzwecke die ISR wie folgt ändern:

// Timer B0 interrupt service routine
#pragma vector=TIMERB0_VECTOR
__interrupt void Timer_B (void)
{
  P1OUT ^= 0x01;                            // Toggle P1.0
  TBCCR0 += 50000;                          // Add Offset to CCR0
}

Und einen Breakpoint auf die P1OUT-Zeile setzen. Da muss er hin, da kann 
der Compiler nix wegoptimieren.

von Jens (Gast)


Lesenswert?

Danke sizimi,

im Statusregister SR war tatsächlich er GIE zurückgesetzt worden. Beim 
ersten Durchlauf hat es noch nicht geklappt, da ich die Einstellungen 
noch auf den #pragma vector=TIMERB1_VECTOR umgeschrieben hatte (s.o.). 
Wieso im SR aber der GIE zurückgestzt wurde ist mir ein Rätsel. Da ich 
das SR mit _BIS_SR(GIE) gesetzt hatte, dachte ich alles wäre geritzt.

Wodurch wird den der GIE zurückgesetzt? Macht das der msp automatisch 
nach einem up Durchlauf?

Jetzt klappt es jedenfalls, vor den Aufruf runTimerB() habe ich nochmal 
ein _BIS_SR(GIE) gesetzt jetzt läuft es.

Nochmals danke,

Grüße Jens

von Falk B. (falk)


Lesenswert?

@ Jens (Gast)

>was SFR sind weiß ich ehrlich nicht, da ich nicht so firm in der Materie
>bin.

http://www.mikrocontroller.net/articles/Speicher#Register

MFG
Falk

von Jens (Gast)


Lesenswert?

Danke Falk,

von alleine setzten die sich aber doch nicht zurück, speziell jetzt der 
SR vom MSP430 oder?

Grüße

Jens

von Falk B. (falk)


Lesenswert?

@ Jens (Gast)

>von alleine setzten die sich aber doch nicht zurück, speziell jetzt der
>SR vom MSP430 oder?

Was heisst zurücksetzten? Zürücksetzen bzw. löschen muss man ggf. nur 
Interruptflags. Das macht enweder der MSP selber beim ausführen der ISR 
oder man muss es manuell per Befehl machen. Das SR ist ein zentrales 
SFR, nämlich das Statur Register der CPU. Das ist quasi heilig. Dort 
sollte man tunlichst nicht wild rummurksen.

Um deinem Probelm auf die Sprünge zu helfen. Poste mal VOLLSTÄNDIGEN 
Quelltext als ANHANG.

MFG
Falk

von Jens (Gast)


Lesenswert?

Danke Falk,

mein Problem hat sich geklärt. Das mit dem Interrupt klappt jetzt. Wenn 
ich vorher das GIE setzte verläßt er die while Schleife. Morgen versuche 
ich noch rauszufinden warum das GIE zurückgesetzt wurde. Jetztt ist 
erstmal Bett angesagt. Danke allen die mir geholfen haben.

Grüße

Jens

von szimmi (Gast)


Lesenswert?

Das GIE wird vom Prozessor beim Eintritt in die ISR gelöscht. Nach 
Beendigung der ISR wird es wiederhergestellt. Man kann es in der ISR 
auch wieder setzen, ist aber tricky, dann werden pending interrupts 
bedient ohne Rücksicht auf die Priorität. Also besser Finger weg.
Ansonsten liegt das GIE in Deiner Hand. Ich nehme also an, dass Du 
irgendwo das GIE explizit löscht.
Wenn Du es durch ein Codereview nicht findest, kannst Du Dich auch 
schrittweise durch Deine Initialisierung debuggen und schauen, an 
welcher Stelle das GIE noch gesetzt ist bzw. zurückgenommen wurde.
Am besten ist, Du setzt zuerst den Brechpunkt ans Ende Deiner 
Initialisierung und halbierst dann immer die Zeilenanzahl. So findet man 
es in der Regel am schnellsten (so ne Art binäre Suche).

von Stefan (Gast)


Lesenswert?

Kann mich meinen Vorrednern nur anschließen!
Irgendwo in Deinem Programm setzt Du (ungewollt) das GIE zurück.
Such mal nach _BIC_SR(GIE) oder nach _DINT()

Was mir an Deinem Code nicht gefällt, dass Du sowohl in init_Timer_A() 
als auch in init_Timer_B() das GIE setzt! Ausserdem setzt du es im 
letzen Fall gleich zweimal, denn _BIS_SR(GIE) und _EINT() machen das 
gleiche!
Die globale Interruptfreigabe setzt man (im Normalfall) ein einziges Mal 
im Programm! Deshalb vermute ich, dass sich bei Dir irgendwo ein _DINT() 
eingeschlichen hat, ohne dass Du's gemerkt hast ?!

von szimmi (Gast)


Lesenswert?

Ahhh, jetzt hat es klick gemacht. Ich glaube, ich weiss, was passiert.
Das wait rufst Du aus der TimerA-ISR auf. Dort ist natürlich das GEI 
gelöscht (siehe oben). In der TimerA-ISR wartest Du also darauf, das in 
der TimerB-ISR das Ready-Flag gesetzt wird.
Die TimerB-ISR wird aber solange nicht ausgeführt, solange Du in der 
TimerA-ISR rumwerkelst. Klassischer Deadlock.
Ich würde Dir empfehlen, die Struktur Deines Programms zu überdenken.
Nimm Dir einen Timer her, der z.B. die Systemzeit hochzählt und werte 
diesen im Main für Deine Wartezeit auf. Oder erzeuge eine Timervariable, 
die Du im Timer dekrementierts und wenn die Variable zu Null wird, dann 
ist auch Deine Wartezeit zu Ende.
Aber mit der aktuellen Struktur wirst Du noch andere Probleme bekommen.
Also, am bestene nochmal zurück auf Los (Deine Subroutinen musst Du 
deswegen nicht neu schreiben).

von szimmi (Gast)


Lesenswert?

Noch ein Hinweis (muss mal den Schlaumeier raushängen lassen). While 
oder Endlos-For-Schleifen habe in einer ISR höchst selten etwas zu 
suchen. Kurz und knackig ist das Stichwort.

von Johnny Maxwell (Gast)


Lesenswert?

> Noch ein Hinweis (muss mal den Schlaumeier raushängen lassen). While
> oder Endlos-For-Schleifen habe in einer ISR höchst selten etwas zu
> suchen. Kurz und knackig ist das Stichwort.

Vor allem wenn man Energie sparen könnte in dem man einfach schläft. 
Hier mal meine Version eines schlafenden waits für Timer A (mit 32768 Hz 
Uhrquarz für ACLK) für einen msp430f169:
1
#include <msp430x16x.h>
2
#include <msp430/common.h>
3
#include <signal.h>
4
5
6
/* Mit mspgcc sorgt "wakeup" für Aufwachen nach der ISR *
7
 * bei anderem Compiler eben von Hand                   */
8
interrupt (TIMERA0_VECTOR) wakeup timerA0_irq (void) {
9
        return;
10
}
11
12
/* wartet d/32768 Sekunden */
13
int wait(unsigned int d) {
14
        TACTL = TASSEL0 + TACLR;        /* ACLK */
15
        CCR0 = d;
16
        CCTL0 = CCIE;
17
        CCTL1 = 0;
18
        CCTL2 = 0;
19
        TACTL |= MC1;
20
        LPM0;                           /* Hier wird geschlafen */
21
        TACTL &= ~MC1;
22
        return 1;
23
}
24
25
int main(void) {
26
        WDTCTL = WDTPW + WDTHOLD;             /* watchdog aus */
27
        BCSCTL1 |= RSEL0 + RSEL1 + RSEL2;
28
        DCOCTL |= DCO0 + DCO1 + DCO2;
29
30
        P1DIR = 0xFF;
31
        P1OUT = 0x00;
32
        eint();
33
        while(wait32768(33)) {
34
                P1OUT ^= 0x01;
35
        }
36
}

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.