Forum: Mikrocontroller und Digitale Elektronik MSP430 Low Power Mode Interrupt Timer mit wählbarer Dauer


von Lars H. (demoniacmilk)


Lesenswert?

== kurzes Hallo ==

Hallo Leute,

ich bin im Moment dabei mich wegen meiner Bachelorarbeit in das Thema 
der µC Programmierung einzuarbeiten. Bei der Suche nach Hilfe hat google 
mich des öfteren auf diese Seite geführt, die mir stellenweise gut 
geholfen hat.

Ich dachte mir ich gebe was zurück, auch wenn das mit meinen Kentnissen 
natürlich auf einem total niedriegen level ist :P

Nachdem ich irgendwann die Begriffe Interrupts und Timer mit Inhalt 
füllen konnte habe ich eine "sleep(time)" Funktion implementiert, die 
ich gerne weitergeben würde, falls mal wieder jemand wie ich neu ist und 
sowas leicht und schnell implementieren will (geht alles geschickter 
nehme ich an, Infos willkommen).
Vermutlihc programmieren erfahrene Leute das in 3 Minuten, aber ich vlt 
ist es ja mal für jemanden hilfreich :)
Ich versuche übermäßig detailiiert zu erklären, dass ist etwas worüber 
ich mcih beim lesen immer gefreut habe^^

== Der Delay Timer =

Die Software die ich programmiere liest Temperatur- und Druckwerte aus 
einem Sensor aus. Dafür brauch ich regelmäßig Wartezeiten (zB um dem 
Sensor Zeit zum Messen zu lassen). Habe ich anfangs alles über 
__delay_cycles(xxxx) gelöst. Funktionierte, allerdings setzt diese Art 
des Delays den Prozessor für xxxx Takte unter Vollast. Für 
Batteriegeräte nicht der Hammer.
Ich brauchte also eine Möglichkeit, variable Pausen zu generieren, die 
wenig oder keine Energie verbrauchen. Der hier gezeigte Delay schickt 
den µC in einen Low Power Mode und weckt ihn nach X ms wieder auf. Das 
ist sehr viel sparsamer und kann auch von Variablen gefüttert werden, 
was beim __delay_cycles nicht der Fall ist.
Mein µC ist ein F5529.


Alles was ihr bruacht ist das hier:
1
//-------------------------------------------------------------
2
void sleep_Setup(void) 
3
// Stellt den Timer ein. ALCK als clock wird gewählt, da diese im LPM3 (niedriger power modus) aktiviert bleibt.
4
{
5
    TA0CCTL0 = CCIE;    // CCR0 interrupt einschalten (löst bei Erreichen von TA0CCR0 aus)
6
    TA0CCR0 = 32000;    // Timer Konstante, zähle immer bis TA0CCR0. Wird mit Aufruf von "sleep" überschrieben
7
    TA0EX0 =  7;        // Timer Teiler auf 8 setzen -> lange wartezeiten möglich (bis 32678
8
    TA0CTL = TASSEL_1 + MC_0 + TACLR;    // TASSEL_1 = ALCK als clock, MC_0 = nicht zählen, TACLR = Zähler zurücksetzen
9
}
10
11
void sleep(int time)
12
// setzt den Low Power Mode 3 (alles aus außer ACLK), wird durch timer nach "time" Millisekunden wieder aufgeweckt
13
// Interrupts werden nach wie vor behandelt (GIE)
14
{
15
    if(time>8191){time = 8191;};     // time * 4 muss kleiner als 32767 bleiben
16
17
    TA0CCR0 = time<<2;               // <<2(=x4) mit Timer Teiler auf 8 = time in ms
18
    TA0CTL |= TACLR | MC_1;          // Zähler zurücksetzen, einschalten im Modus Hochzählen
19
  __bis_SR_register(LPM3_bits + GIE);     // LPM3, Interrupts einschalten
20
  __no_operation();
21
  TA0CTL = TASSEL_1 + MC_0 + TACLR;  // Wie in sleep_Setup, Timer Einstellungen wie oben und anhalten
22
}
23
24
25
// Interrupt, macht nichts weiter als den µC wieder aufzuwecken
26
// Deklaration checkt, welcher Compiler installiert ist und gibt dem Interrupt einen entpsrechenden Namen, welchen der compiler zuordnen kann
27
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
28
#pragma vector=TIMER0_A0_VECTOR
29
__interrupt void TIMER0_A0_ISR(void)
30
#elif defined(__GNUC__)
31
void __attribute__ ((interrupt(TIMER0_A0_VECTOR))) TIMER0_A0_ISR (void)
32
#else
33
#error Compiler not supported!
34
#endif
35
{
36
  LPM3_EXIT;      // Low power mode verlassen, damit wird das Programm am Einstiegspunkt des low power modes fortgesetzt.
37
}
38
//--------------------------------------------------------------

== Aufbau und Verwendung ==

Der dargestellt Delay enthält 3 Dinge:
* Eine Initialisierungs-Funktion, die die erforderlichen Register so 
setzt, dass der Timer seine Funktion erfüllen kann.
* Eine Funktion die später verwendet wird um den Timer zu setzen.
* Ein Interrupt

Um den delay zu verwenden reicht folgendes aus (hier wird einfach eine 
LED blinken gelassen mit Unterschiedlichen Zeiten):

Packt das oben Dargestellte in eure C Datei.
Dann könnt ihr in der main() function eine Pause mit sleep(t) aufrufen.
NOTE: sleep(1000) ist nicht exakt eine Sekunde, sondern ein kleines 
bisschen weniger (ca 0,98s)
1
//----------------------------------------------
2
void main(void)
3
{
4
    WDTCTL = WDTPW + WDTHOLD;  // WDT stoppen 
5
6
    sleep_Setup();    // Initialisierung des sleep Timers
7
    __enable_interrupt();
8
9
    P1DIR = BIT0;
10
11
12
    while(1){
13
        sleep(1000);
14
        P1OUT ^= BIT0;
15
        sleep(300);
16
        P1OUT ^= BIT0;
17
    }
18
}
19
//----------------------------------------------

: Bearbeitet durch User
von Felix (Gast)


Lesenswert?

Hallo Lars,

Ansich sieht der Code gut aus und wird sicher anderen Leuten helfen bzw. 
Anregungen geben.

Ich persönlich würde zuerst TACCR0 Register setzen und dann den Timer 
starten. Sonst könnten ggf. unerwünschte Sideeffekts auftreten (In 
diesem Fall sicherlich nicht, aber man sollte das immer im Hinterkopf 
behalten ;)).

Ausschnitt aus einem Userguide des MSP430:

It is recommended to stop the timer before modifying its operation (with 
exception of the interrupt enable, interrupt flag, and TBCLR) to avoid 
errant operating conditions.

von Lars H. (demoniacmilk)


Lesenswert?

Ok cool, danke Felix!
Ich denke du beziehst dich auf folgende Zeilen in sleep()?
1
    TA0CTL |= TACLR | MC_1;          // Zähler zurücksetzen, einschalten im Modus Hochzählen
2
    TA0CCR0 = time<<2;               // <<2(=x4) mit Timer Teiler auf 8 = time in ms

Klingt sinnvoll erst alles einzustellen und dann zu starten :)
Habe die beiden Zeilen deshalb getauscht.

: Bearbeitet durch User
von watsche (Gast)


Lesenswert?

Warum einen Timer verschwenden? Geht das nicht auch mit dem WDT?

von Felix (Gast)


Lesenswert?

Hi Watsche,

Prinzipiell möglich.
Aber für diese Anwendug suboptimal, da der WDT beim MPS430 nur bestimmte 
Intervalle zulässt, die abhängig vom Clocksource sind. Somit wären die 
Delays nicht frei wählbar.

MfG
Felix

von watsche (Gast)


Lesenswert?

Hää? Jeder Timer hat nur bestimmte Auflösungen. Das gilt für Timer A, B 
und auch den WDT. Wer keinen Watchdog braucht, kann für das delay den 
WDT nutzen u d den Timer für wichtige Gere Dinge nutzen.

von Felix (Gast)


Lesenswert?

Ich sehe beim WDT  nur das Controllregister, mit dem vorbestimmte 
Intervalle eingestellt werden können. Damit kann man den WDT nicht 
wirklich einsetzen.

von watsche (Gast)


Lesenswert?

Hää? Man muss ja nicht volle ms auflösen. Mit krummen Werten geht es 
doch genauso gut. Du hast wenig Phantasie. :-[

von Felix (Gast)


Lesenswert?

Da je nach Derivat der MSP430 recht gut bestückt ist(Anzahl der Timern), 
nehme ich lieber ein Timer und Stelle im ms-Bereich mein Delay, als 
einen WDT der nur 1,95ms , 15,625ms, 250ms, 1s ... kann ;)

Wenn natürlich das geforderte Delay zufällig damit möglich ist okay.

von Lars H. (demoniacmilk)


Lesenswert?

Den WDT zu benutzen ist vielleicht gar keine so schlechte Idee. Weiß 
grade nicht wie das im LPM3 damit aussieht, kann mir vorstellen dass der 
dann abgeschaltet ist, sonst wäre warten im LPM3 ja nie möglich ohne 
Reset?!

von Felix (Gast)


Lesenswert?

WDT ist in LPMx nicht abgeschaltet. Beachtet werden muss nur, dass die 
Clocksource aktiv bleibt und somit bspw. mit ACLK(LFXT1) trotz 
LPM4-Befehl der LFXT1 weiterläuft und somit der Stromverbrauch 
wesentlich höher wäre.

Falls du ihn für deine Zwecke verwenden möchtest, wird der PUC eh 
deaktiviert und du bekommst nur einen Interrupt, der das Device wieder 
aufweckt.

von Lars H. (demoniacmilk)


Angehängte Dateien:

Lesenswert?

Ich habe jetzt, ein halbes Jahr später, bemerkt, dass es einen Fehler im 
obigen Code gibt^^. Der Timer wird nämlich nicht gestoppt. Das fällt 
auf, wenn man etwas im Stil von
1
    P4OUT ^= BIT7;
2
    sleep(100);
3
    __bis_SR_register(LPM0_bits + GIE); // Warten bis foo
programmiert. Die LED binkt rapide, weil der timer nach Ablauf von 100 
Takten weiter läuft, und nach weiteren 100 Takten den nächsten LPM 
Modus, welcher erst durch ein Event hätte verlassen werden sollen, 
verlässt.

Ich habe .c und .h Datei für die aktuelle Version angehängt (kann den OP 
leider nicht mehr ändern)

: Bearbeitet durch User
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.