Forum: Mikrocontroller und Digitale Elektronik Frequenzmessung mit MSP430 im LPM3


von Simon H. (simon12)


Lesenswert?

Hallo zusammen,
für ein Projekt muss ich ein Rechtecksignal im Frequenzbereich von 50 – 
100Hz kontinuierlich überwachen. Das ganze soll möglichst energiesparend 
laufen. Als Microcontroller verwende ich den MSP430G2553. Ich habe zu 
Testzwecken ein kleines Programm geschrieben. Darin definiere ich zu 
Beginn eine Periodendauer (100Hz = 327). Timer_A im Capture Mode 
überwacht das externe Signal und falls die Periodendauer über bzw. 
unterschritten wird, wird P1.0 high (rote LED auf dem Launchpad). Das 
Programm funktioniert soweit wie gewollt allerdings ist der 
Stromverbrauch viel zu hoch (4,7 mA). Im LPM3 mit 32kHz Quarz als ACKL 
habe ich mir einige µA vorgestellt. Wenn ich das im User’s guide richtig 
verstanden habe, sollte die Ausführung doch mit ausgeschalteter CPU 
möglich sein:
"Use low-power integrated peripheral modules in place of software driven 
functions. For example Timer_A and Timer_B can automatically generate 
PWM and capture external timing, with no CPU resources."
Oder wird für die Ausführung der ISR die CPU angeworfen? Gäbe es in 
diesem Fall einen alternativen Lösungansatz?
Würde mich über eure Tipps und Vorschläge freuen! Beschäftige mich 
leider erst seit kurzem mit dem MSP430.

Viele Grüße
Simon

1
#include <msp430.h> 
2
3
volatile int maxPeriod = 347;
4
volatile int minPeriod = 307;
5
volatile int startTime, stopTime, period, error;
6
7
8
void main(void) {
9
    WDTCTL = WDTPW | WDTHOLD;                           // Stop watchdog timer
10
    P1DIR |= 0x40 + 0x01;                               // P1.1 + P1.6 als Ausgang
11
    P1OUT = 0x00;                                       // Ausgänge low setzen
12
    P2SEL |= 0x02;                                      // P2.1 als Eingang für Timer_A
13
    TA1CTL = TASSEL_1 + MC_2;                           // ACLK + Continuous Mode
14
    TA1CCTL1 = CM_1 + CCIS_0 + SCS + CAP + CCIE;        // Capture on rising edge + CCI1A + Synchronous capture + Capture mode
15
    startTime = TA1CCR1;                                // startTime auslesen
16
    _BIS_SR(LPM3_bits + GIE);                           // LPM3 und globale Interrupts aktivieren
17
}
18
19
#pragma vector = TIMER1_A1_VECTOR
20
__interrupt void Timer1_A1_ISR(void) {
21
  switch(TA1IV) {
22
    case  2:    stopTime = TA1CCR1;                                 // stopTime auslesen
23
                period = stopTime - startTime;                      // Periode berechnen
24
                startTime = stopTime;                               // startTime für nächsten Aufruf setzen
25
                if((period < minPeriod) || (period > maxPeriod)) {  // prüfen ob Periode in vorgegebenem Intervall liegt
26
                  error++;                                          // falls nicht Fehlerzähler inkrementieren
27
                }
28
                if(error >= 0x03) {                                 // nach mehr als 3 Fehlern
29
                  error = 0x00;                                     // error-Variable zurücksetzen
30
                  P1OUT = 0x01;                                     // rote LED anschalten
31
                } else {
32
                  P1OUT = 0x00;                                     // sonst rote LED ausschalten
33
                }
34
                break;
35
      case  4:  break;
36
      case 10:  break;
37
    }
38
}

von /Vorkäuer (Gast)


Lesenswert?

Wo fangen wir an? :-*

Für die ISR wird natürlich die CPU angeworfen. Den PWM Ausgang kannst du 
über den Outmode ohne ISR, ohne CPU, erledigen. Das steht doch schon in 
deinen anderen Threads!
Lies the f... Manual: family user guide, Kapitel Timer

Wenn du Strom sparen willst, solltest du dir einen sparsamen CPU clock 
aussuchen.
Lies the f... Manual: family user guide, Kapitel Basic Clock System. Am 
Anfang vom Kapitel gibt es ein schönes Bild dazu.

Probiert dein Glück und stell das Ergebnis hier ein. Dann können wir 
weiter sehen.

von Simon H. (simon12)


Lesenswert?

Danke für deine Antwort! Der Titel ist vielleicht etwas irreführend, 
hier geht es ja eher um eine Überwachung als um eine Messung. Ich will 
ja quasi nur wissen ob die Periode sich gegenüber dem Refernzwert 
geändert hat oder nicht. Ich habe gehofft, dass das irgendwie ohne die 
CPU und nur mit dem Timer-Modul geht. Bei einem 100Hz Signal läuft die 
CPU ja quasi ständig. Da wird man mit einer langsamen Taktrate vom 
Stromverbrauch auch nicht in den 10µA Bereich kommen. Was meintest du 
mit Output-Mode, den compare-Mode? Mit dem kann ich doch nur ein Signal 
mit einem bestimmten Timing erzeugen aber keine Eingänge überwachen.

von /Vorkäuer (Gast)


Lesenswert?

Simon Heller schrieb:
> Ich will
> ja quasi nur wissen ob die Periode sich gegenüber dem Refernzwert
> geändert hat oder nicht.

Das lese ich aus dem Code etwas anders. Du definierst ein Fenster mit 
zwei Konstanten und zählst einen Fehlerzähler hoch. Der Fehlerzähler hat 
einen konstanten Grenzwert ...
Dafür brauchst du die CPU.
Wenn das die einzige Aufgabe ist, geht das auch mit 3 sparsamen CMOS IC.

Simon Heller schrieb:
> Bei einem 100Hz Signal läuft die
> CPU ja quasi ständig.

Warum das? Je niedriger die Frequenz, je länger schläft die CPU. Sie 
läuft ja nur bei der ISR.

Simon Heller schrieb:
> Was meintest du
> mit Output-Mode
Dabei dachte ich an die PWM aus deinen anderen Threads. Wird die nicht 
mehr benötigt?

Simon Heller schrieb:
> den compare-Mode?
Na jetzt kommt endlich eine interessante Alternative ins Spiel. Wozu 
nimmt man Capture und wozu Compare?

von Thomas (Gast)


Lesenswert?

Wie misst Du den Stromverbrauch?  Ist der msp430 noch im Launchpad drin 
und der ganze andere Krams noch angeschlossen?  Der msp430 selbst 
braucht nie und nimmer so viel Strom (ich würde eher 1-2 µA schätzen).

An dem Code kannst Du noch einiges optimieren:  Es gibt keinen Grund, 
warum die Variablen volatile sein sollten.  Außerdem würde ich 
minPeriod/maxPeriod als const deklarieren.  stoptime und period können 
lokale Variablen sein, error ist nicht initialisiert.

von Simon H. (simon12)


Lesenswert?

@ Thomas: Danke für die Tipps, ich dachte ich mach mit den Informationen 
aus der ISR vlt. noch was ;-) Stromverbrauch liegt bei 38µA wenn man den 
Vcc Jumper entfernt und dann extern mit 3.3V speise ;-) Wenn ich das 
runterrechne auf Vcc = 1,8V komme ich wohl auf etwa 11µA raus!

Kann man denn außer alle Pins als Ausgang und low zu setzen sonst noch 
was fürs stromsparen tun?

@Vorkäuer: den Teil mit der PWM brauch ich auch für das Projekt 
allerdings zur Ansteuerung eines Aktors der die Frequenz mechanisch 
nachregelt. Der hier diskutierte Programmteil dient dazu mögliche 
Abweichungen zu erkennen. Meiner Meinung nach dient der Compare-Mode um 
einen Ausgang zu setzen bzw. reseten wenn der Timer einen bestimmten 
Wert erreicht hat. Verrätst du mir noch deine Idee mit den CMOS ICs?

von Thomas (Gast)


Lesenswert?

Simon Heller schrieb:
> @ Thomas: Danke für die Tipps, ich dachte ich mach mit den Informationen
> aus der ISR vlt. noch was ;-)

Jeden Zugriff auf eine volatile Variable muss der Compiler aus dem RAM 
vornehmen, das kostet ordentlich Cycles.  Ich würde zumindest 
sicherstellen, dass auf jede volatile Variable in der ISR höchstens 
einmal lesend und einmal schreibend zugegriffen wird.

Simon Heller schrieb:
> Kann man denn außer alle Pins als Ausgang und low zu setzen sonst noch
> was fürs stromsparen tun?

TI empfielt ungenutzte Pins als Input mit Pulldown zu konfigurieren. 
Port 3 nicht vergessen!

von /Vorkäuer (Gast)


Lesenswert?

Simon Heller schrieb:
> Meiner Meinung nach dient der Compare-Mode um
> einen Ausgang zu setzen bzw. reseten wenn der Timer einen bestimmten
> Wert erreicht hat.

Meiner Meinung nach wird bei Compare-Match ein Flag gesetzt. Das 
boolsche Ergebnis beinhaltet bereits den Vergleich und erfolgt ohne 
(oder auch bei schlafender) CPU. Interrupt und Ausgänge können optional 
aktiviert werden, sind aber kein "muss".
Du könntest einen Compare auf die untere Grenze und einen auf die obere 
setzen. Bei Periodenbeginn - z. B. steigende Flanke - wertest du die 
Flags aus und setzt sie und den Timer zurück. CCR1 und CCR2 stehen im 
gleichen Register. ;-)

Mit CMOS ICs geht das das genauso gut. Ein Zähler und ein paar Gatter 
für Compare und Auswertung.

von Simon H. (simon12)


Lesenswert?

Guten Morgen /Vorkäuer,
danke das du dich noch meldest ;-) Wollte deine Idee gerade umsetzen, 
hänge aber gerade noch daran wie die ISR von TA1CCR0 funktioniert. Wird 
da auch #pragma vector = TIMER1_A1_VECTOR aufgerufen? In welchem 
Register ist denn dann die Flag zu finden? Im User Guide steht ja das es 
für CCR0 ein eigenes Register gibt aber irgendwie hab ich jetzt nicht 
rausgefunden wie das heißt :-/

von /Vorkäuer (Gast)


Lesenswert?

Bei der vorgeschlagenen Compare Variante brauchst du keinen Timer 
Interrupt, sondern den Portpin Interrupt. Die Periode beginnt immer mit 
steigender (oder immer mit fallender) Flanke.

Ich habe den family user guide SLAU144J–December 2004–Revised July 2013 
offen. Im Kapitel 12.3.5 sind die Flags für CCR1 und CCR2 zu finden.

Unter 12.3 gibt es die Register-Übersicht. Der CCR0 Interruppt steht in 
12.2.6.1, er hat einen eigenen Intr-Vektor. Die anderen Intr-Quellen 
teilen sich den anderen Intr-Vektor. Das steht in 12.2.6 und ein 
Beispiel in 12.2.6.3.

Die Quellenangaben kannst du in deine Abschlussarbeit übernehmen. Dann 
gibt es nachher keine "Doktortitelaberkennung".

von Simon H. (simon12)


Lesenswert?

Soooo... Interrupt am Pin 1.4 funktioniert aber der Vergleich mit CCR1 
und CCR2 wohl noch nicht. Muss ich denn das CCIE Bit setzen um im TAIV 
Register die Flag zu sehen? Wenn ich das mache scheint der Pin-Interrupt 
nicht mehr zu funktionieren?! Kann man man den Timer einfach mit
1
 TA1R = 0
 "reseten" oder muss man ihn über
1
 
2
TACTL &= ~MC_02;
3
TACTL |= MC_2;
 neu starten?

Danke für die Referenzen im Users Guide! Habe ich auch gelesen und 
glaube auch verstanden. Ich habe mich nur gefragt wenn ich TA1CCR0 im 
Capture-Mode hätte:
1
  TA1CCTL0 = CM_1 + CCIS_0 + SCS + CAP + CCIE;
wie sähe dann die ISR dazu aus? Habe vorhin mal versuch einfach die LED1 
zu toggeln aber mit dem Code hat es nicht funktioniert:
1
 
2
#pragma vector = TIMER1_A1_VECTOR
3
__interrupt void Timer1_A1_ISR(void) {
4
P1OUT ^= 0x01;
5
}
 Deshalb auch mein Frage ob es für CCR0 auch so ein Register wie TAIV 
für CCR1 und CCR2 gibt.

Aktueller Code:
1
#include <msp430.h> 
2
3
int maxPeriod = 347;
4
int minPeriod = 307;
5
6
void main(void) {
7
    WDTCTL = WDTPW | WDTHOLD;        // Stop watchdog timer
8
9
10
    P1DIR |= 0x01 + 0x40;            // P1.0 + P1.6 als Ausgang
11
    P1OUT &= ~0x01 + 0x40;           // P1.0 + P1.6 low
12
    P1IE |= 0x10;                    // Interrupt bei steigender Flanke an P1.4
13
    TA1CTL = TASSEL_1 + MC_2;        // ACLK + Continuous Mode
14
    TA1CCR1 = minPeriod;             //
15
    TA1CCR2 = maxPeriod;             //
16
    _BIS_SR(LPM3_bits + GIE);        // LPM3 und globale Interrupts aktivieren
17
}
18
19
#pragma vector=PORT1_VECTOR
20
__interrupt void Port_1(void)
21
{
22
    P1OUT ^= 0x01;           // toggle LED1
23
    P1IFG &= ~0x10;          // P1.4 IFG cleared
24
    if(TA1IV != 0x02) {      // Bei unveränderter Frequenz ist CCR1 high und CCR2 negativ. Falls nicht...
25
      P1OUT |= 0x40;         // ...LED2 an
26
    } else {
27
      P1OUT &= ~0x40;        // sonst LED2 aus
28
    }
29
    TA1R = 0;                // Timer neu starten
30
}

von Simon H. (simon12)


Lesenswert?

Ah...seh grad die Flag steht im TACCTLx Register :-/ Melde mich wieder 
wenn ich das korrigiert hab!

von Simon H. (simon12)


Lesenswert?

Neuer Versuch war auch nicht erfolgreich. Vergleich sieht jetzt so aus:
1
 if((TA1CCTL1 & CCIFG) && (!(TA1CCTL2 & CCIFG)))
 Ich glaube es liegt einfach daran, dass der Timer nicht resetet wird. 
Wenn ich mir im Debugger den Wert von TA1R anschaue ist der immer weit 
über 330, den sollte er ja eig. nicht erreichen :-/

von /Vorkäuer (Gast)


Lesenswert?

Am Anfang vom Times Kapitel gibt es ein Bild mit dem Aufbau und der 
internen Verknüpfung. Dort sind die Flags und die Steuerbits alle 
eingezeichnet. Man sieht schnell, was man einstellen muss und was ein 
Steuerbit bewirkt.
-> mit xyIE wird der Interrupt scharf geschaltet und beim Eintreffen in 
die ISR verzweigt.
Die Flags werden immer gesetzt, egal ob xyIE oder nicht.

Dein Chip hat zwei Timer, timer0 und timer1. Beide sind vom Typ A. Jeder 
Timer hat zwei Interrupt-Vektoren.
timer0_a0 für capture compare unit 0
timer0_a1 für die andern Quellen
timer1_a0 für capture co mpare unit 0
timer1_a1 für die anderen Quellen

minP... und maxP... dürfen echte Konstanten sein. ;-)

von Simon H. (simon12)


Lesenswert?

Danke für die Info!!! Das werd ich sicher mal brauchen können! Also das 
Problem scheint jetzt wirklich zu sein, dass der Timer sich nicht 
reseten lässt. Hab jetzt glaub ich so ziemlich jede Variante durch. Wäre 
nett wenn mir da noch auf die Sprünge geholfen werden könnte ;-) Im 
Internet steht, dass es mit
1
 TA1CTL |= TACLR
 bzw.
1
 TA1R = 0
 funktionieren soll. Klappt aber beides nicht! ISR sieht aktuell so aus:
1
#pragma vector=PORT1_VECTOR
2
__interrupt void Port_1(void)
3
{
4
    P1OUT ^= 0x01;              // toggle LED1
5
    P1IFG &= ~0x10;             // P1.4 IFG cleared
6
    if((TA1CCTL1 & CCIFG) && (!(TA1CCTL2 & CCIFG))) {      // Bei unveränderter Frequenz ist CCR1 high und CCR2 negativ. Falls nicht...
7
      P1OUT |= 0x40;      // ...LED2 an
8
    } else {
9
      P1OUT &= ~0x40;      // sonst LED2 aus
10
    }
11
    TA1R = 0;
12
}

von /Vorkäuer (Gast)


Lesenswert?

Um hier ein wenig aufzuräumen, stelle ich einen Code für die 
Compare-Interrupts und beide Timer-Interrupt Vektoren ein. Das läuft auf 
dem Launchpad.

Bei CCR1-Match wird die rote LED eingeschaltet (und die grüne aus). Bei 
CCR2-Match die grüne ein. CCR0 ist der Endwert und löscht beide LED.
Mit S2 wird TAR mit Null beschrieben. Mit S2 kann vor Überlauf der Timer 
neu gestartet werden.
-> TAR = 0 ist also kein Problem.
1
#include "io430.h"
2
3
#define LED_RED     BIT0
4
#define LED_GREEN   BIT6
5
#define KEY_S2      BIT3
6
7
int n = 0;
8
9
void main( void )
10
{
11
    // stop watchdog timer to prevent time out reset
12
  WDTCTL = WDTPW + WDTHOLD;
13
   // clock source DCO 150kHz
14
  BCSCTL1 = XT2OFF | RSEL0;
15
  DCOCTL = DCO1 | DCO0;
16
   // init key
17
  P1REN |= KEY_S2;        // pullup
18
  P1OUT |= KEY_S2;
19
  P1IES |= KEY_S2;        // key down
20
  P1IFG &= ~KEY_S2;       // clear flag
21
  P1IE |= KEY_S2;         // enable intr
22
   // init LEDs
23
  P1OUT &= ~(LED_GREEN | LED_RED);
24
  P1DIR |= LED_GREEN | LED_RED;
25
   // init Timer0_A, SMCLK, up CCR0, div 8 -> 18.75 kHz
26
  TA0CCR0 = 56250;      // 3s
27
  TA0CCR1 = 18750;      // 1s
28
  TA0CCR2 = 37500;      // 2s
29
  TA0CTL = TASSEL_2 | MC_1 | ID_3 | TACLR;
30
  TA0CCTL0 = CCIE;
31
  TA0CCTL1 = CCIE;
32
  TA0CCTL2 = CCIE;
33
34
  __bis_SR_register(LPM1_bits + GIE); // sleep until intr
35
36
}
37
38
39
#pragma vector=PORT1_VECTOR
40
__interrupt void Port_1(void) {
41
  TA0R = 0;
42
  P1IFG &= ~KEY_S2;                      // clear flag
43
  P1OUT &= ~LED_RED;   
44
}
45
46
47
#pragma vector=TIMER0_A0_VECTOR
48
__interrupt void Timer0_A0_ISR(void) {
49
  P1OUT &= ~(LED_RED | LED_GREEN);  
50
}
51
52
53
#pragma vector=TIMER0_A1_VECTOR
54
__interrupt void Timer0_A1_ISR(void) {
55
  
56
  switch (__even_in_range(TA0IV,0x0A)) {
57
    case TA0IV_TACCR1 :
58
      P1OUT |= LED_RED;
59
      P1OUT &= ~LED_GREEN;
60
      break;
61
    case TA0IV_TACCR2 :
62
      P1OUT |= LED_GREEN;
63
      break;
64
    case TA0IV_TAIFG :      // debug only
65
      // break;
66
    default :
67
      for (;;)
68
        n += 4;
69
  }
70
}
Vielleicht hilft dir das Beispiel zum Verstehen der Interrupts.

von Simon H. (simon12)


Lesenswert?

Danke für den Beispiel-Code! Nach dem Mittagessen hats es jetzt dann 
endlich funktioniert! Hatte die Flags in der ISR nicht resetet :-(
1
#include <msp430.h> 
2
3
// AnregungsFrequenz 100Hz
4
5
const int maxPeriod = 347;
6
const int minPeriod = 307;
7
8
void main(void) {
9
    WDTCTL = WDTPW | WDTHOLD;              // Stop watchdog timer
10
    P1DIR |= 0x01 + 0x40;                // P1.0 + P1.6 als Ausgang
11
    P1OUT &= ~0x01 + 0x40;                // P1.0 + P1.6 low
12
    P1IE |= 0x10;                    // Interrupt bei steigender Flanke an P1.4
13
    TA1CTL = TASSEL_1 + MC_2;                        // ACLK + Continuous Mode
14
    TA1CCR1 = minPeriod;                // Compare-Wert setzen
15
    TA1CCR2 = maxPeriod;                // Compare-Wert setzen
16
    _BIS_SR(LPM3_bits + GIE);              // LPM3 und globale Interrupts aktivieren
17
}
18
19
#pragma vector=PORT1_VECTOR
20
__interrupt void Port_1(void)
21
{
22
    P1IFG &= ~0x10;                         // P1.4 IFG cleared
23
    TA1R = 0;                      // Timer reseten
24
    if((TA1CCTL1 & CCIFG) && (!(TA1CCTL2 & CCIFG))) {  // Wenn compare mit CCR1 true und CCR2 false, Frequenz unverändert
25
      P1OUT |= 0x40;
26
      P1OUT &= ~0x01;
27
    } else {
28
      P1OUT |= 0x01;
29
      P1OUT &=~0x40;
30
    }
31
    TA1CCTL1 &= ~0x01;
32
    TA1CCTL2 &= ~0x01;
33
}

Ich kann jetzt nur nicht sagen ob diese Variante energiesparender ist 
als die Vorherige...Wenn ich den Code aufspiele und extern speiße um zu 
messen scheint der µC nicht wirklich zu laufen (LEDs leuchten nicht).

von Thomas (Gast)


Lesenswert?

Simon Heller schrieb:

> Ich kann jetzt nur nicht sagen ob diese Variante energiesparender ist
> als die Vorherige...Wenn ich den Code aufspiele und extern speiße um zu
> messen scheint der µC nicht wirklich zu laufen (LEDs leuchten nicht).

47kΩ Widerstand zwischen VCC und Reset vergessen?

Nicht vergessen, die ungenutzten Pins als Input mit Pulldown 
(P1REN=~(BIT0|BIT6); P2REN=0xFF; P3REN=0xFF) zu konfigurieren, das spart 
ordentlich Strom.

von Guest (Gast)


Lesenswert?

Simon Heller schrieb:
> Ich kann jetzt nur nicht sagen ob diese Variante energiesparender ist
> als die Vorherige...

Erstelle ein Listfile und zähle die Befehle.

von Simon H. (simon12)


Lesenswert?

Danke für die Tipps! Auf dem Launchpad ist ja ein 47kOhm Widerstand 
zwischen Vcc und RST. Im Users Guide zum Launchpad steht das man für 
eine Strommessung am besten alle Jumper bei J3 entfernen soll. Oder 
müssen irgendwelche Jumper gesetzt bleiben? Kann das auch was mit dem 
Debugging zu tun haben? Wenn ich das Programm über "Build - Release" 
uploade sollte der Debugger ja nicht aktiv sein, oder?

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.