Forum: Mikrocontroller und Digitale Elektronik MSP430 Interrupts zählen (Herzfrequenz)


von Harry (Gast)


Lesenswert?

Hallo, ich hoffe mir kann jemand helfen. Ich bin noch nicht sehr fit im
programmieren mit C. Ich will auftretende peaks mit einem µController
zählen, schaffe es aber nicht ene Variable zu deklarieren die
"überall" bekannt ist. Ich sollte es mit Zeigern machen, hab aber
keine Ahnung wie das funktioniert. Hat jemand eine Idee?

Momentaner Quellcode, welcher eine LED die an P1.0 hängt und toggelt,
bei einer fallenden Flanke an P2.0:

#include <msp430xG43x.h>

void main(void)
{
WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer
P1DIR = 0x01; // Set P1.0 to output direction
P2IE |= 0x01; // P2.0 interrupt enabled
P2IES |= 0x01; // P2.0 Hi/lo edge
P2IFG &= ~0x01; // P2.0 IFG cleared


_BIS_SR(LPM4_bits + GIE); // Enter LPM4 w/interrupt

for(; ; ; )       // Endlosschleife, das Programm auf µC nicht
{                 //beendet wird
}

}
// Port 2 interrupt service routine
#pragma vector=PORT2_VECTOR
__interrupt void Port_2(void)
{
P1OUT ^= 0x01; // Toggle P1.0
P2IFG &= ~0x01; // P2.0 IFG clearedf

}


Vielen Dank schon mal.

von KoF (Gast)


Lesenswert?

eins der zauberwörter heißt globale variable ;-)
ist unter der "sauberen" programmierung verpöhnt (wie auch goto in
c/c++). wird aber oft (besonders bei uc`s) benutzt da man ohne großen
aufwand mit ihnen arbeiten kann.
1
#include <dingsdabums.h>
2
3
int i;    //globale variable
4
5
int main(void)
6
{
7
  for(;;)
8
  {  
9
    while(i<100)   //solange i < 100
10
    {
11
      i++;         // i = i + 1;
12
    }
13
    i = 0;         // i = 0;
14
  }
15
  return 0;
16
}

oder aber du definierst dir eine struct und schreibst dir die
zugriffsoperationen. ist zwar aufwendig, aber es ist wesentlich
sauberer
1
typedef struct _zahl
2
{
3
  int i;
4
  char c;
5
}zahl;
6
7
zahl* set_i(int i)
8
{
9
  zahl z;
10
  z = (zahl*)malloc(sizeof(*z));
11
  z->i = i;
12
  return z;
13
}
14
zahl* get_i(void)
15
{
16
  zahl z;
17
  return(zahl->i);
18
}

mfg
KoF

von Tom (Gast)


Lesenswert?

Hi Leute

Ob Struktur oder Integer, beide sind global definiert. Die
Zugriffsroutinen sind meiner Meinung nach übertrieben, aber das kommt
halt stark auf den Einsatzzweck an. Viel wichtiger ist, und das sollte
sich jeder Codeschreiberling für jede Programmiersprache gut merken,
für alle Bezeichner selbsterklärende Namen zu verwenden. Auch
Präfixe, die den Datentyp anzeigen sind sehr zu empfehlen. So zum
Beispiel:

int   iHerzImpulsZaehler = 0;
// oder
float fFrequenz = (float)iHerzImpulsZaehler / MESSZEIT;
iHerzImpulsZaehler = 0;

Daruch verlieren globale Variablen besonders in kleinen Programmen ihre
Bösheit. Genau so wichtig sind viel und gute Kommentare die deine
Überlegungen oder grössere Abläufe beschreiben.

Zeiger sind nichts anderes als Variablen für Speicheradressen. Weil an
diesen Speicheradressen selbst wieder ein Wert steht, der einen
bestimmten Datentyp hat, muss der Zeiger auch immer einen Datentyp
haben. Zudem gibt es Operatoren, die dir die Speicheradresse einer
Variablen zurückliefern (Adresse= &Variable) oder umgekehrt den Wert an
einer Speicheradresse zurückliefern (Wert = *Adresse). Mit den Adressen
kannst du auch rechnen, aber das ist ein fortgeschrittenes Thema und
bedarf ein wenig Experimentierfreude.

char  sText[] = "Dies ist mein Text";  // String
char* pText = &(sText[0]);    // Ein Pointer auf das erste Zeichen
while (*pText != '\0') {      // Solange das Zeichen nicht 0 ist
  putchar(*(pText++));        // Zeichen ausgeben und zum nächsten
Zeichen springen (Postinkrement!)
}
while (*pText != &(sText[0])) {   // und das ganze nochmal rückwärts
  putchar(*pText--));
}

Wobei putchar() deinem MSP natürlich unbekannt ist.

HTH Tom

PS: Kennt der MSP430 malloc() und free()? Das muss ich ja gleich mal
ausprobieren!

von Herzfrequenz (Gast)


Lesenswert?

Hallo

@Harry nebenbei ne Frage: Wie detektierst Du die Herzfrequenz?
Welcher Sensor? Brustgurt?

MfG
Achim

von Rolf (Gast)


Lesenswert?

Naja, das Goto ist zur Fehlerbehandlung häufig unersätzlich, so wie
longjmp, das auch zum Wechsel in einen früheren Modus (AM, LPM3, ...)
zweckmässig ist.

Und globale Variablen sind besser als malloc, da die allermeisten
Stackcheck-Routinen nicht funktionieren, wenn malloc verwendet wird und
man mit mallock leicht Speicherlecks einbauen kann.

Wenn man unbedingt eine Kapselung um quasi-globale Variablen haben
will, kann man mit Pointern auf Variablen von main arbeiten oder eine
Funktion mit einer static-Variablen nehmen, die auch alle Zugriffe
darauf macht; quasi eine Klasse sozusagen. Sauberer wäre aber in dem
Fall gleich C++ oder Java zu nehmen.

von Harry (Gast)


Lesenswert?

Hi,

wow vielen Dank! Das hilft mir ein ganzes Stück weiter.

Zur Frage, ich greife das Signal eines handelsüblichen Brustgurts mit
einem Schwingkreis ab und filtere das Signal dann mit Bandpässen. Ich
habe volgende Seite zur Hilfe genommen:
http://rick.mollprojects.com/hrm/

von Tenner (Gast)


Lesenswert?

Rolf schrieb:
"Naja, das Goto ist zur Fehlerbehandlung häufig unersätzlich..."

Warum? Ich habe in allen meinen Programmen Fehlerbehanlungen aber noch
nie Goto verwendet!?
Kannst du das bitte genauer erläutern?

Gruß Tenner

von Harry (Gast)


Lesenswert?

Hallo,

ich hab schon wieder ein Problem, denn ich schaffs nicht aus der
Interrupt Routine heraus zu kommen. Schreibe ich den Code
folgendermaßen funzt es, mach ich aber die if-Abfrage in die main, geht
nichts mehr. Im User Guide steht was von RETI (return from an interrupt
service routine). Bei mir geht das aber nicht  :( .

Funktionierender Code:
#include  <msp430xG43x.h>
#include  <stdio.h>

int zaehler = 0;

void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;             // Stop watchdog timer
  P1DIR = 0x01;                        // Set P1.0 to output direction
  P2IE |= 0x01;                         // P2.0 interrupt enabled
  P2IES |= 0x00;                        // P2.0 Hi/lo edge
  P2IFG &= ~0x01;                       // P2.0 IFG cleared

  _BIS_SR(LPM4_bits + GIE);             // Enter LPM4 w/interrupt

while(1)
{

}

}
// Port 2 interrupt service routine
#pragma vector=PORT2_VECTOR
__interrupt void Port_2(void)
{
     zaehler = zaehler + 1;
     P2IFG &= ~0x01;                       // P2.0 IFG clearedf

    if(zaehler >100)
    {
      P1OUT = 0x01;
    }
    else
    {
      P1OUT = 0x00;
    }

}

DieIf Anweisung will ich in die main (innerhalb der Endlosschleife)
machen, geht aber nicht.


Vielen Dank schon mal.

von KoF (Gast)


Lesenswert?

1
void main(void)
2
...
3
while(1)
4
{
5
  if(zahl => 100)   //wenn zahl gleich oder größer 100
6
    P1OUT = 0x01;   // P1.0 high
7
  else if(zahl < 100) // sonst
8
    P1OUT &=~ 0x01;   //P1.0 low
9
}
10
...

du solltest es aber bei der interruproutine lassen, da du ja in den
LPM4 mode gehst...

von 123 (Gast)


Lesenswert?


von Harry (Gast)


Lesenswert?

Hi,
danke. Was ist eigentlich der LMP4 Mode, das hab ich immer noch nicht
verstanden.
Ich wollte in der Interruptroutine nur den Zähler hochzählen und alles
andere wie Berechnungen usw. ausserhalb, z.B. in der main.
Auch deine Lösung klappt nicht. Ich glaube das der Rücksprung aus der
Interrupt Routine nicht klappt. Dies hab ich m it verschiedenen printf
Befehlen kontrolliert.
Ich hab im Datenblatt was mit RETI gelesen, es klappt bei mir aber
nicht??

Hast du ne Idee?

von Rolf (Gast)


Lesenswert?

@Tenner:
Bei einer umfangreichen Funktion kann man dutzende Stellen haben, an
denen ein Feher auftritt, der zu immer demselben Abbruch führen soll;
dafür ist ein goto optimal. Alternativ kann man denselben Code auch
dutzende Male mit Copy+Paste reinkopieren und darf dann bei einer
Änderung dutzende Male ändern.
Wenn man aber nach Code-Zeilen oder Code-Grösse bezahlt wird, braucht
man natürlich kein Goto.

Letztlich ist eine Goto-Phobie aber Unsinn und Scheinheiligkeit, da der
Assembler-Code viele jmps enthält, also Goto in Assembler.

von Tom (Gast)


Lesenswert?

Hi

@Harry
LMP4 ist ein Standby-Modus des Mirkocontrollers. Was dabei genau läuft
und was ausgeschalten ist, erfährtst du im User's Guide oder im
Datenblatt. Versuch's doch mal mit diesem Pseudocode:

- Zähler als globale Variable deklarieren
- main
  - Alles initialisieren und Timer starten
  - Wiederhole
    - Wenn Zähler grösser als 100
      - Wenn LED AN
        - LED AUS
      - Sonst
        - LED AN
     - Zähler auf 0 setzen
    - Gehe in Standby-Mode (Achtung: Der Timer muss weiterlaufen!)

- Interruptvektor
  - Erwache aus Standby
  - Zähler inkrementieren

Du kannst den Zähler auch in einer For-Schleife im main-Loop
inkrementieren, dann musst du den uC nur noch aufwecken mit der
Interrupt-Routine und du hast die maximale Standby-Zeit. RETI würde ich
mal bei Google oder in den Assembler- und C-Hilfen nachschlagen.

@Rolf
Was eine Funktion erledigt sollte immer in nur einem Satz beschrieben
werden können. Wenn man dann auch noch aussagekräftige Bezeichner
verwendet wird der Code sehr gut nachvollziehbar. In solch eher kleinen
Funktionen, max. 2 A4-Seiten als Faustregel, sind normalerweise so
wenige Fehlerfälle abzufangen, dass sich dies auch gut mit den gängigen
Bedingungs-Konstrukten (if-else / switch) abfangen lassen. Im Gegensatz
zu den leicht unübersichtlich werdenden GoTo-Konstrukten stellen
Bedingungen eine klare Entscheidung dar, will heissen der
Programmablauf  ist leichter nachvollziehbar.

Funktions-Monolithe und GoTo-Geschick können performanter sein, jedoch
auf Kosten der Verständlichkeit.

FG

Tom

von KoF (Gast)


Lesenswert?

also ich habe programmieren bei einem ada-menschen gelernt. der kennt
keine fehler im program ;-)
wird der code compiliert, so ist er fehlerfrei (abgesehen von logischen
fehlern).
und von ada habe ich auch strenge typesierung & co gelernt. es ist zwar
wesentlich umständlicher so zu programmieren ( ich tippe mal so auf 3-5
mal mehr code) aber wenn er mla läuft/oft bearbeitet wird/von vielen
bearbeitet wird, so zahlt sich diese art der programmierung wieder
aus.

zu goto.
mir wurde es so beigebracht...
ein sprungbefehl in c/c++ ist unschön, da man ggf nicht nachvollziehen
kann, wohin gesprungen wird. programmierer sollten daher im sinne der
nachvollziehbarkeit darauf verzichten. was der compiler daraus macht
ist in erster linie egal... jeder compiler übersetzt/optimiert anders.

mfg
KoF

von Tenner (Gast)


Lesenswert?

@ Harry, lass mal

  _BIS_SR(LPM4_bits + GIE);             // Enter LPM4 w/interrupt

weg. solange du nicht darauf angewisen bist Strom zu sparen und nur
experimentierst lass die Low Power Modes aussen vor. Damit kann man
sich  immer noch beschäftigen, wenn der Code läuft.

ansonsten sollte es so funktionieren

...
int zaehler = 0;

void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;             // Stop watchdog timer
  P1DIR = 0x01;                         // Set P1.0 to output
direction

  P1DIR &= ~0x01;                       // Set P2.0 to input direction
  P1IES |=  0x00;                       // P2.0 Hi/lo edge
  P1IFG &= ~0x01;                       // P2.0 IFG cleared
  P1IE  |=  0x01;                       // P2.0 interrupt enabled
//  _BIS_SR(LPM4_bits + GIE);             // Enter LPM4 w/interrupt


while(1)
{
    if(zaehler >= 100)
    {
      P1OUT |= 0x01;
    }
    else
    {
      P1OUT &= ~0x01;
    }
}

}
// Port 2 interrupt service routine
#pragma vector=PORT2_VECTOR
__interrupt void Port_2(void)
{
     zaehler = zaehler + 1;
     P2IFG &= ~0x01;                       // P2.0 IFG clearedf
}


@Rolf

eine saubere Lösung ohne GOTO sähe dann zB. so aus

...
#define NO_ERROR 0
#define ERROR_1  1
...

int rc;
int sub() {
  while(1) {
    rc = foo1();
    if( rc )
      break;
    rc = foo2();
    if( rc )
      break;
    ...
    break;
  }

  if( rc <> NO_ERROR ) {
    switch(rc){
      case ERROR_1:
        ...
        break;
      ...
    }
  return rc;
}


Gruß Tenner

von KoF (Gast)


Lesenswert?

@Tenner

er kann ruhig im lpm4 bleiben, wenn er die vergleiche/setzten der pins
in der interruptroutine macht (wie oben schonmal erwähnt)

von Harry (Gast)


Lesenswert?

Hallo,

der Code war OK, ich hatte eine zu alte Version von IAR drauf. Jetzt
funktioniert alles, mit oder ohne lmp4. Ohne muss aber GIE gesetzt
sein.

Danke nochmals.

von Zimmi (Gast)


Lesenswert?

Hiho,
ist vielleicht ein bissl spaet, aber ich muss mich zu dem thema nochmal
aeussern. Das eigentliche Problem, warum:
include  <msp430xG43x.h>
#include  <stdio.h>

int zaehler = 0;

void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;             // Stop watchdog timer
  P1DIR = 0x01;                        // Set P1.0 to output direction
  P2IE |= 0x01;                         // P2.0 interrupt enabled
  P2IES |= 0x00;                        // P2.0 Hi/lo edge
  P2IFG &= ~0x01;                       // P2.0 IFG cleared

  _BIS_SR(LPM4_bits + GIE);             // Enter LPM4 w/interrupt

while(1)
{

}

}
// Port 2 interrupt service routine
#pragma vector=PORT2_VECTOR
__interrupt void Port_2(void)
{
     zaehler = zaehler + 1;
     P2IFG &= ~0x01;                       // P2.0 IFG clearedf

    if(zaehler >100)
    {
      P1OUT = 0x01;
    }
    else
    {
      P1OUT = 0x00;
    }

}
nicht funktioniert, ist das Fehlen des Zauberwortes volatile.
Sofern Du beim Anlegen der Variable:
volatile int zaehler = 0;
verwendest, wird das Programm funktionieren (ohne dass ich das
ausprobiere).
Bei einigen Compilerversionen wird es ohne volatile funktionieren, bei
anderen nicht, insbesondere bei verschiedenen Optimierungsstufen wird
der Compiler einmal die Variable ins Register laden und dann (staendig)
das Register mit dem Wert vergleichen, egal, ob die Variable im
interrupt geaendert wird oder nicht.
Volatile zwingt den Compiler, bei jedem Vergleich die Variable neu
einzulesen und dann zu vergleiche.
Frohes Fest
Zimmi

von Michael (Gast)


Lesenswert?

Auch wenn das ein alter Beitrag ist: fuer den Fall, dass ihn jemand 
ueber Google findet moechte ich noch etwas hinzufuegen:

Wird beim MSP ein low power mode (LPM) aktiviert, so geschieht das ueber 
das Setzen von entsprechenden Bits im Statusregister.
Wird eine Interrupt-routine aufgerufen, so wird zwar der LPM 
deaktiviert, da aber beim Aufruf der Interruptroutine das Statusregister 
auf dem Stack landet und beim Beenden wieder geladen wird, sind auch die 
LPM-Bits wieder gesetzt. Das bedeutet, der Prozessor versinkt 
unmittelbar nach der ISR wieder im Tiefschlaf.

Will man, dass der code in Main nach Auftreten des Interrupts weiter 
ausgefuehrt wird, der Prozessor also dauerhaft aufwacht, so muss man das 
abgespeicherte Statusregister auf dem Stack manipulieren, so dass die 
LPM-Bits nicht mehr gesetzt sind. Das ist leider nicht ganz trivial und 
mit nacktem C nicht zu machen.

Die Beispiele von TI benutzen alle Assembler-Anweisungen wie etwa

BIC #CPUOFF,0(SP)
RETI

Was bedeutet, dass in dem word, auf das der Stackpointer zeigt, das 
CPUOFF bit geloescht wird.

Dummerweise generieren Compiler in der Regel einen Prolog/Epilog und 
einen Stackframe, was einerseits den Stack um einen dem C Code 
unbekannten Betrag vollstopft, andererseits unmittelbar vor der 
Rueckkehr aus der ISR den Code unterbringt, um das wieder zu 
korrigieren. Man hat also als Programmierer in C keine Moeglichkeit, 
direkt vor dem Ruecksprung die Korrektur vorzunehmen.

Beim MSPGCC gibt es fuer diesen Zweck ein paar Macros, die Assemblercode 
in die ISR einbetten, der auf den vom Compiler generierten Assemblercode 
angepasst ist und sich dort die informationen des generierten 
Stackframes besorgt.
Das sieht dann so aus:

#define _BIC_SR_IRQ(x) \
    _asm__ __volatile_ ( \
        "bic %0, .L__FrameOffset_" _FUNCTION_ "(r1)" \
        : : "ir" ((uint16_t) x) \
    )

Das Macro kann dann an jeder Stelle in der ISR verwendet werden und den 
Prozessor auch nur unter bestimmten Bedingungen aufwecken.


p.s.: natuerlich gibt es fuer den MSP auch malloc (schliesslich ist das 
nichts Hardwareabhaengiges sodnern einfach eine Libraryfunktion, die 
sich auch jeder selber schreiben kann).
Und malloc an sich ist nicht gefaehrlicher als ein statisch reservierter 
Speicher. Eher weniger, da der statische immer an derselben Stelle liegt 
und daher fuer Exploits besser auszunutzen ist. Es ist bei malloc nur 
schwieriger, die Bugs zu bemerken (die der Programmierer ohnehin nicht 
machen sollte), da bei statisch reserviertem Speicher bei 
Bereichsueberschreitungen in der Regel andere globale Variablen 
ueberschrieben werden und man sofort merkt, dass es irgendwie nicht 
laeuft.
Wer also behauptet, malloc sei unsicher, der hat offensichtlich keine 
wirkliche Ahnung, wovon er redet. Jegliche Verwendung von Pointern (oder 
Typecasts, was das angeht) ist gleichermassen 'unsicher', das schliesst 
auch statische Arrays und Puffer ein.

Und was das Goto angeht, so geht es durchaus auch ohne, wenn man seine 
Funktionen entsprechend anlegt. In der Regel sollte eine FUnktion bei 
einem Fehler mit einem entsprechenden Rueckgabewert zur Aufrufenden 
zurueckkehren, anstatt die Fehlermeldungen selber vorzunehmen. Dann 
braucht man goto nicht mal ansatzweise.
Aber da sich die meisten darueber keine Gedanken machen wollten, wurden 
in C++ die Exceptions eingefuehrt. Quasi ein Goto mit Parameter.

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.