Forum: Mikrocontroller und Digitale Elektronik MSP430G2553 Launchpad, Timer0 problem


von Sören K. (foxalem)


Lesenswert?

Moin,
ich versuch nun seit 2 Stunden den Timer zum laufen zu bekommen.
Im grunde möchte ich nur den Timer mit 20ms durchlaufen lassen und bei 
einem Interrupt die Bits von P2.0 bis P2.2 "durchshiften". D.h. das 
immer nur einer von diesen Portpins aktiv ist.

Mein Problem, er springt anscheinend nicht in die ISR des Timers. Ich 
weiß nicht ob er nicht anläuft oder ob er einfach nich reinspringt etc. 
pp.
Evtl. kann mir auch jemand erklären wie ich mir das anzeigen lassen 
könnte beim Debugging (wie in Keil zum beispiel, allerdings ist dies ja 
ein Simulator.)

Anbei mein Code und schonmal danke für die Hilfe.
1
/*
2
 * main.c
3
 * Ein Programm zum Ansteuern und durchlaufen lassen von einem RGB-SMD-LED-Streifen.
4
 * Die ausgänge gehen über einen 1k-Widerstand auf die Basis eines BC538, welcher die
5
 * Masse auf die einzelnen LEDs durschaltet.
6
 */
7
8
#include "msp430g2553.h"
9
10
void main(void) {
11
  WDTCTL = WDTPW + WDTHOLD;  // Stop WDT
12
13
  P2DIR = 0xFF;      //Port 2 als Ausgang nutzen
14
  P2OUT = 0x00;      //Port 2 ausstellen
15
  
16
  //Timer0 einstellen
17
  TACCR0 = 0xC350;    //=50000 * 250000 * e-1 = 20ms
18
  TACTL = TAIE + TASSEL1 + ID1;  //Up-Mode, zählt bis TACCRO0, Interrupt enabled
19
20
  P2OUT |= BIT0;
21
  TACTL |= MC0;
22
  __bis_SR_register(GIE); // interrupts enabled
23
24
  while (1)
25
  {
26
27
  }
28
}
29
30
31
// Timer0 interrupt Service Routine
32
#pragma vector=TIMER0_A0_VECTOR
33
__interrupt void Timer0_ISR (void)
34
{
35
  if (P2OUT == BIT3)
36
  {
37
    P2OUT = BIT0;
38
  } else {
39
    P2OUT = P2OUT << 0x01;
40
  }
41
}

Mfg

von yes MSP430 can (Gast)


Lesenswert?

Welche IDE verwendest du? Ich nutze IAR und den integrierten Simulator 
auch für Interrupts.

Probier einmal die Beispiele hier aus dem Forum:
http://www.mikrocontroller.net/articles/MSP430_Codebeispiele#Initialisierung_des_Timers_A

oder eine fertige Lösung:
http://processors.wiki.ti.com/index.php/MSP430_LaunchPad_LED_Timer#Code

von Sören K. (foxalem)


Lesenswert?

Danke für die Codebeispiele ;)
Mir ist beim ansehen aufgefallen, dass ich ein Interrupt nicht aktiviert 
habe (CCIE im TACCTL0 / Compare-Capture Register 0).
Allerdings ist es wohl jener welcher, den ich dafür benötige.

Ich benutze CCS5 von TI, muss mich da wohl einfach nur noch richtig 
reinfuchsen, damit ich evtl besser Debuggen kann.

Mfg

Edit:
Jetzt springt er zwar in die ISR rein, allerdings is der Programmcounter 
nach dem RETI bei 0xFFFF und bleibt dort hängen.
Is irgendwie nich mein Tag heute

Edit2:
"This is typically the case when you have an itnerrupt enabled but no 
ISR for it. The CPU will then fetch the default 0xffff from the 
interrupt vector table as the ISR's address and jumps there. Of course 
there is no source code available or disassembly possible. It's a crash 
situation. And yes, the MSP won't forgive this mistake :)" <---
Ich hatte in TACTL noch das TAIE-Interrupt gesetzt, von dem ich ja 
dachte es löst den Interrupt für den Vector TIMER0_A0_VECTOR aus.

von Torsten C. (torsten_c) Benutzerseite


Angehängte Dateien:

Lesenswert?

TAIE geht doch auf den TIMER0_A1_VECTOR und nicht auf den 
TIMER0_A0_VECTOR.

Also CCS5 von TI macht's einem doch echt leicht, sowas in Zukunft auch 
selbst nachzuschauen, siehe Anlage.

von Torsten C. (torsten_c) Benutzerseite


Lesenswert?

Hallo Sören, geht's nun mit dem TIMER0_A1_VECTOR bei Dir?

Ich selber hatte bis eben noch mit dem Assembler-Beispiel in slau144i, 
Kapitel 12.2.6.3 zu kämpfen. Ich arbeite mit Funktions-Vektoren, da ich 
die Interrupt-Pointer für die Zustandsautomaten verbiege:

Beitrag "Interrupt-Pointer verbiegen für Zustandsautomat"
1
#include <msp430.h>
2
void dummy() {volatile int test = TA0IV;} // für asm("…") - ToDo: kann man TA0IV geschickter definieren?
3

4
#pragma vector=TIMER0_A1_VECTOR
5
__interrupt void TIMER0_TAIV_ISR(void) {
6
  // aus slau144i, Kapitel 12.2.6.3
7
  asm("        ADD &TA0IV,PC     ; Add offset to Jump table");
8
  asm("        RETI              ; Vector 0: No interrupt");
9
  asm("        JMP CCIFG_1_HND   ; Vector 2: TACCR1");
10
  asm("        JMP CCIFG_2_HND   ; Vector 4: TACCR2");
11
  asm("        RETI              ; Vector 6: Reserved");
12
  asm("        RETI              ; Vector 8: Reserved");
13
  asm("        RETI              ; Vector 10: TAIFG Flag");
14
  asm("        RETI              ; Back to main program");
15
  asm("CCIFG_2_HND:              ; Vector 4: TACCR2");
16
  asm("        CALL &I2C_State+0 ; Task starts here");
17
  asm("        RETI              ; Back to main program");
18
  asm("CCIFG_1_HND:              ; Vector 2: TACCR1");
19
  asm("        CALL &PWM_State+0 ; Task starts here");
20
  asm("        RETI              ; Back to main program");
21
}

Für TAIE musst Du natürlich das Vector-10-RETI durch ein entsprechendes 
CALL ersetzen.

Ich hoffe, ich konnte helfen.

Wenn einer 'ne Idee hat, wie ich/wir wir das "void dummy()" vermeiden 
können, nur zu. Es dient nur dazu das Symbol "TA0IV" für die erste 
Assembler-Zeile bekannt zu machen.

von nur mal so (Gast)


Lesenswert?

Torsten C. schrieb:
> Ich arbeite mit Funktions-Vektoren, da ich
> die Interrupt-Pointer für die Zustandsautomaten verbiege:

Hmmm, ich sehe keine Interrupt-Pointer und keine verbogenen 
Funktions-Vektoren. In dem Beispiel von TI geht es um etwas anderes:
Für den einen Timer-Interrupt gibt es mehrere Quellen. Um die Quelle 
heraus zu finden, muss man das TAIV auswerten. Üblicherweise macht man 
das mit einer switch case in der ISR.

Beim TAIV gibt es eine Besonderheit und zwar wird nur der Wert des 
höchstwertigen Interrupts eingetragen. Die Werte der einzelnen 
Interrupt-Quellen im TAIV unterscheiden sich immer um zwei (0, 2, 4, 
...). Dies macht TI sich zum Nutzen und addiert einfach den Wert vom 
TAIV auf den PC. Der Code direkt dahinter ist eine Tabelle, in der die 
gewünschten Ausführungen für die jeweilige Interrupt-Quelle stehen. Je 
Eintrag stehen nur 2 Byte zur Verfügung, also steht da ein Jump oder 
gleich RETI.

Also besser du nimmst die switch case und nutzt die die Option für nur 
"gerade" Werte.

von nur mal so (Gast)


Lesenswert?

Torsten C. schrieb:
> Wenn einer 'ne Idee hat, wie ich/wir wir das "void dummy()" vermeiden
> können, nur zu.

Ach ja, TAIV ist nur eine Adresse. Du kannst den Wert auch direkt 
angeben.

von Torsten C. (torsten_c) Benutzerseite


Lesenswert?

nur mal so schrieb:
> … TAIV ist nur eine Adresse. Du kannst den Wert auch direkt angeben.

Danke für die gute Erklärung. Genau das macht das o.g. 
Assembler-Beispiel aus dem Datenblatt slau144i in Kapitel 12.2.6.3.

nur mal so schrieb:
> Also besser du nimmst die switch case und nutzt die die Option für nur
> "gerade" Werte.

Switch Case dauert bei der Ausführung deutlich länger als
1
ADD &TA0IV,PC
Interrupts sollen "so kurz wie möglich" das laufende Programm 
unterbrechen, da sonst andere Interrupts verloren gehen könnten, es sei 
denn, man erlaubt Interrupts in Interrupts. Das ist aber egal, wenn man 
keine zeitkritischen Funktionen hat.

Switch Case ist in C natürlich einfacher zu programmieren.

"&I2C_State" und "&PWM_State" sind übrigens die o.g. "verbogenen 
Interrupt-Pointer", aus dem verlinkten Beitrag, die vorher wie folgt 
deklariert sind und je nach Zustand der "State-Machine" andere Werte 
haben:
1
void(*PWM_State)(void) = &PWM_State_idle;
2
void(*I2C_State)(void) = &I2C_State_idle;

@"nur mal so": kann man TA0IV auch geschickter definieren, als durch die 
Dummy-Funktion? Der Assembler kennt sonst das Symbol nicht.

VG Torsten

von nur mal so (Gast)


Lesenswert?

Torsten C. schrieb:
> Interrupts sollen "so kurz wie möglich" das laufende Programm
> unterbrechen

Sehe ich auch so, aber

Torsten C. schrieb:
> asm("        CALL &I2C_State+0 ; Task starts here");

ist dann eher suboptimal, denn vom CALL will das Programm mit RET wieder 
zurück. Und solange ist die ISR noch aktiv, solange bis sie auf ein RETI 
läuft.



Torsten C. schrieb:
> nur mal so schrieb:
>> … TAIV ist nur eine Adresse. Du kannst den Wert auch direkt angeben.
Torsten C. schrieb:
> Der Assembler kennt sonst das Symbol nicht.

Hinter TAIV oder TA0IV steckt die Adresse eines Registers. Schau ins 
Datenblatt.

Torsten C. schrieb:
> "&I2C_State" und "&PWM_State" sind übrigens die o.g. "verbogenen
> Interrupt-Pointer"

Dat sind käne verbojene Intrups!!!
In der ISR vom Timer nutzt du eine Sprungtabelle, um weiteren Code 
auszuführen oder eine Funktion (auch über Funktionszeiger) aufzurufen. 
Richtig wäre direkt die Interrupt-Vektortabelle zu verändern und dort 
einen Sprung auf die aktuell gewünschte ISR einzutragen. Das geht 
natürlich im Flash nicht so gut, schnell und häufig.

Ein guter Compiler erzeugt eine effektive Sprungtabelle automatisch. 
Beim MSP430 solltest du einmal nach "even_in_range" suchen. Die 
Bedeutung wird meist im Zusammenhang mit dem TAIV erklärt.

von Torsten C. (torsten_c) Benutzerseite


Lesenswert?

nur mal so schrieb:
> Torsten C. schrieb:
>> Der Assembler kennt sonst das Symbol nicht.
>
> Hinter TAIV oder TA0IV steckt die Adresse eines Registers. Schau ins
> Datenblatt.

Der Assembler kennt das Datenblatt nicht. ;-) Die Zeile sorgt dafür, 
dass dem Assembler das Label bekannt wird. Hat niemand eine schlauere 
Lösung? Egal, so geht's auch.

nur mal so schrieb:
> Dat sind käne verbojene Intrups!!!  … Das geht
> natürlich im Flash nicht so gut, schnell und häufig.

OK, es sind keine "verbogenen Interruppts", es ist eine Art, 
Interrupt-Vektoren zu verbiegen, ohne in's Flash zu schreiben. Die 
meisten Alternativen wurden im 
Beitrag "Interrupt-Pointer verbiegen für Zustandsautomat" ^^ ja Diskutiert.

> Beim MSP430 solltest du einmal nach "even_in_range" suchen.

Ich hab's mal ausprobiert, so richtig optimerit sieht das noch nicht 
aus. "ADD &TA0IV,PC" wäre optimal. Vielleicht mache ich noch was falsch:
1
Timer0_TAIV_ISR:
2
        PUSH.W    r15
3
        MOV.W     &TA0IV+0,r15
4
        SUB.W     #2,r15
5
        JEQ       $C$L3
6
        SUB.W     #2,r15
7
        JEQ       $C$L3
8
        SUB.W     #6,r15
9
        JEQ       $C$L3
10
$C$L2:    
11
        JMP       $C$L2

Hier der C-Code dazu:
1
#pragma vector=TIMER0_A1_VECTOR
2
__interrupt void Timer0_TAIV_ISR (void) {
3
  switch (__even_in_range(TA0IV,10)) {
4
    case 0x02: // TACCR1 CCIFG
5
      // ToDo: Event 1 ergänzen
6
      break;
7
    case 0x04: // TACCR2 CCIFG
8
      // ToDo: Event 2 ergänzen
9
      break;
10
    case 0x0A: // TAIFG
11
      // ToDo: Event 3 ergänzen
12
      break;
13
    default:
14
      while (1); // trap CPU on strange TAIV
15
  }
16
}

Eigentlich sollte es so gehen, wie hier beschrieben:

http://e2e.ti.com/support/microcontrollers/msp430/f/166/t/238317.aspx

von Torsten C. (torsten_c) Benutzerseite


Lesenswert?

PS: Komisch, so macht der Compiler die Vereinfachung:

Aus diesem C-Code:
1
   TA0IVRead = TA0IV;
2
    switch (__even_in_range(TA0IVRead,10)) 

Wird dieser Assembler:
1
    MOV.W     &TA0IV+0,&TA0IVRead$4+0
2
    ADD.W     &TA0IVRead$4+0,PC

Irgendwie kann "even_in_range" das SFR TA0IV nicht vertragen. :-(

von Torsten C. (torsten_c) Benutzerseite


Lesenswert?

Torsten C. schrieb:
> Hallo Sören, geht's nun mit dem TIMER0_A1_VECTOR bei Dir?
Keine Antwort? Ich habe den Haken bei "E-Mail-Benachrichtigung 
einschalten" entfernt. Wenn's noch 'ne Frage gibt:
http://www.mikrocontroller.net/user/show/torsten_c

von nur mal so (Gast)


Lesenswert?

Torsten C. schrieb:
> Irgendwie kann "even_in_range" das SFR TA0IV nicht vertragen.

Hmmmh, ich weiss nicht wie du das anstelltst, aber mein IAR macht ohne 
große Optimierung aus
1
void foo(void);
2
void (*funcarr[3])(void) = {foo, foo, foo};
3
4
#pragma vector = TIMER0_A1_VECTOR
5
__interrupt void TIMER0_A1_VECTOR_ISR (void)
6
{
7
  switch (__even_in_range(TAIV, 10)){
8
    case 0x02: // TACCR1 CCIFG
9
      funcarr[0]();
10
      break;
11
    case 0x04: // TACCR2 CCIFG
12
      funcarr[1]();
13
      break;
14
    case 0x0A: // TAIFG
15
      funcarr[2]();
16
      break;
17
    default:
18
      while (1); // trap CPU on strange TAIV
19
  }
20
}

das
1
   \   000000   0D12         PUSH.W  R13
2
   \   000002   0C12         PUSH.W  R12
3
   \   000004   0F12         PUSH.W  R15
4
   \   000006   0E12         PUSH.W  R14
5
   \   000008   10522E01     ADD.W   &0x12e, PC
6
   \                     `?<Jumptable for TIMER0_A1_VECTOR_ISR>_0`:
7
   \   00000C   123C         JMP     ??TIMER0_A1_VECTOR_ISR_1
8
   \   00000E   043C         JMP     ??TIMER0_A1_VECTOR_ISR_2
9
   \   000010   0A3C         JMP     ??TIMER0_A1_VECTOR_ISR_3
10
   \   000012   0F3C         JMP     ??TIMER0_A1_VECTOR_ISR_1
11
   \   000014   0E3C         JMP     ??TIMER0_A1_VECTOR_ISR_1
12
   \   000016   0A3C         JMP     ??TIMER0_A1_VECTOR_ISR_4
13
   \                     ??TIMER0_A1_VECTOR_ISR_2:
14
   \   000018   9212....     CALL    &funcarr
15
   \                     ??TIMER0_A1_VECTOR_ISR_0:
16
   \   00001C   3E41         POP.W   R14
17
   \   00001E   3F41         POP.W   R15
18
   \   000020   3C41         POP.W   R12
19
   \   000022   3D41         POP.W   R13
20
   \   000024   0013         RETI
21
   \                     ??TIMER0_A1_VECTOR_ISR_3:
22
   \   000026   9212....     CALL    &funcarr + 2
23
   \   00002A   F83F         JMP     ??TIMER0_A1_VECTOR_ISR_0
24
   \                     ??TIMER0_A1_VECTOR_ISR_4:
25
   \   00002C   9212....     CALL    &funcarr + 4
26
   \   000030   F53F         JMP     ??TIMER0_A1_VECTOR_ISR_0
27
   \                     ??TIMER0_A1_VECTOR_ISR_1:
28
   \   000032   FF3F         JMP     ??TIMER0_A1_VECTOR_ISR_1

Da braucht man keinen ASM mehr.

Und nebenbei kannst du sehen, wie anstatt TAIV die Register-Adresse aus 
dem Datenblatt eingesetzt wurde.

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.