MSP430F2012 usigned long Timertakte Zaehlen

#7737602
Lesenswert?

Hallo zusammen,

ich bin gerade dabei ein Relais in eimen zyklisch Zeitabstand zu schalten. Hierzu zähle ich Timerinterruptimpulse. Als Zählvariable verwende ich eine unsigned Long Variable. Hierbei gibt es aber Probleme. Wenn ich zwischen 5m-30m Zähle gibt es keine Probleme (Abweichung <100ms). Bei 4h funktioniert es nicht. Hier schaltet das Relais erst nach 8h. Den Code habe ich etwas gekürzt, damit es nicht so überfrachtet ist. rest_spuelzeit wird über einen Timerinterrupt alle 100ms decrementiert. zeit_nicht_gespuelt wird über einen Timerinterrupt alle 100m incrementiert.

Wenn ich die Zeiten 3m-30m einstelle, dann funktioniert dies. max_Zwangsspuelzeit=18000L; min_Zwangsspuelzeit=1800L;

Bei 25m-250m klappt es mit 25m aber die 250m funktionieren nicht max_Zwangsspuelzeit=150000L; min_Zwangsspuelzeit=15000L;

Hierbei verhält es sich, als wäre die Zähler Variable irgendwann negativ. Wenn ich das Poti nach 4h auf 0 drehe, dann bleibt das Relais trotzem aus. Gibt es bei Code Composer Studio irgendwelche Bugs, die dieses erklären würden?

Festlegung des Intervalls: P2 wird über ein Poti verstellt. Ist getestet und liefert Werte zwischen 0-1023.

1
intervall_zwangsspuelung=(((max_Zwangsspuelzeit-min_Zwangsspuelzeit) * P2value)/1023L)+min_Zwangsspuelzeit;

Abfrage ob die Zeit wo nicht gespült wurde, größer als der Intervall ist.

1
if(zeit_nicht_gespuelt>intervall_zwangsspuelung)
2
{
3
   rest_spuelzeit = 300L;
4
}

Wenn Spuelzeit großer 0, dann soll der Ausgang geschaltet werden.

1
if (rest_spuelzeit>0)
2
          {
3
            P2OUT |= 0x40;
4
            zeit_nicht_gespuelt=0L;
5
          }
6
          else
7
          {
8
            P2OUT &= ~0x40;
9
            rest_spuelzeit=0L;
10
          }

Vielen Dank im voraus. Sebastian

#7737675
Lesenswert?

Sebastian R. schrieb:

Hallo zusammen,

ich bin gerade dabei ein Relais in eimen zyklisch Zeitabstand zu schalten. Hierzu zähle ich Timerinterruptimpulse. Als Zählvariable verwende ich eine unsigned Long Variable. Hierbei gibt es aber Probleme.

Hast du das Thema volatile und atomar beachtet? Siehe Interrutpt.

Bei 4h funktioniert es nicht. Hier schaltet das Relais erst nach 8h. Den Code habe ich etwas gekürzt, damit es nicht so überfrachtet ist.

Irrtum! Deine Fragmente sind nichtssagend, der Fehler steckt oft nicht im gezeigten Ausschnitt.

Zeige uns deinen GESAMTEN Quelltext als Anhang.

Moderator (Firma: Titel) Persönliche Seite #7737767
Lesenswert?

Sebastian R. schrieb:

Den Code habe ich etwas gekürzt, damit es nicht so überfrachtet ist.

Du solltest einen Code posten, der den Fehler zeigt. Dazu solltest du den Code im Idealsfall auf einen "Dreizeiler" zusammenkürzen, also den kürzestmöglichen Code, der den Fehler noch zeigt.

Der Witz dabei: ganz oft findet man den Fehler bei deratigem Vorgehen schon selber.

Hierzu zähle ich Timerinterruptimpulse.

Womit? Was ist die Zählervariable?

Warum nachst du es nicht so wie die Arduinos beim millis() Zähler? Denn die zeigen, wie Zeitvergleiche auch mit einem Zählerüberlauf zuverlässig funktionieren: der millis-Zähler läuft dauernd hoch, die zugehörigen Vergleichswerte sind Zeitstempel dieses millis-Zählers und die Differenz zwischen beiden ist die inzwischen abgelaufene Zeit. Und der Überlauf des millis-Zählers wird durch eine gechickte Berechnung im Vergleich elegant abgefangen:

#7737845
Lesenswert?

Ich hab gerade paar Sachen rausgelöscht. ADC ist uninteressant hier habe ich einfach den vollausschlag von 1023 eingegeben. Ich habe volatile beachtet, aber nicht atomar. Dieses sollte aber eigentlich keinen effekt haben oder?

Die Bedingung sollte ja normalerweise, wenn P2value auf 0 gedreht wird.

zeit_nicht_gespuelt>intervall_zwangsspuelung

1
#include <msp430.h>
2
#include <msp430f2012.h>
3
void io_init(void);
4

5
volatile unsigned long rest_spuelzeit;
6
volatile unsigned long zeit_nicht_gespuelt;
7

8
unsigned int P2value = 0;
9

10
int main(void)
11
{
12
    int jumper_Spuelzeit;
13
   
14
    volatile unsigned long intervall_zwangsspuelung;
15
    volatile unsigned long max_spuelzeit;
16
    volatile unsigned long min_Zwangsspuelzeit;
17
    volatile unsigned long max_Zwangsspuelzeit;
18

19
    WDTCTL = WDTPW + WDTHOLD;               
20
    //IO initialisieren
21
    io_init();
22

23
    //Variablen initialisieren
24
    rest_spuelzeit=0L;
25
    zeit_nicht_gespuelt=0L;
26
    intervall_zwangsspuelung=150000L;
27
    P2value=1023;
28

29
  __enable_interrupt();                    
30
  TACCR0 = 12500;                            
31
  TACCTL0 |= CCIE;                          
32
  TACTL = TASSEL_2 | MC_1 | ID_3;          
33

34
  _BIS_SR(GIE);                 // Global interrupt enable
35

36
  while (1)
37
      {
38
          //Eingaenge Einlesen und mappen
39
          jumper_Zwangsspuelen = ((0xC0 & (~P1IN))>>6);
40

41
          if(jumper_Zwangsspuelen != 0x03)
42
              {
43
                  max_Zwangsspuelzeit=150000L;
44
                  min_Zwangsspuelzeit=15000L;                        
45
                  intervall_zwangsspuelung=(((max_Zwangsspuelzeit-min_Zwangsspuelzeit) * P2value)/1023L)+min_Zwangsspuelzeit;
46
              }
47
          else
48
          {
49
              zeit_nicht_gespuelt = 0L;
50
              intervall_zwangsspuelung = 0L;
51
          }
52
 
53
          if (zeit_nicht_gespuelt>intervall_zwangsspuelung)
54
                   {
55
                     if (rest_spuelzeit==0L)
56
                     {                   
57
                       rest_spuelzeit=300L;
58
                     }
59
                   }
60

61
          if (rest_spuelzeit>0L)
62
          {
63
            P2OUT |= 0x40;
64
            zeit_nicht_gespuelt=0L;
65
          }
66
          else
67
          {
68
            P2OUT &= ~0x40;
69
            rest_spuelzeit=0L;
70
          }
71
      }
72
}
73

74
void io_init(void)
75
{
76
    P2SEL &= ~0x40;
77
    P2DIR |= 0x40;                           
78
    P2OUT &= ~0x40;                           
79

80
    P1SEL |= 0x03;                            
81
    P1DIR &= ~0xEB;   //E8                        
82
    P1REN |= 0xE8;                           
83
    P1OUT |= 0xE8;                            
84
}
85

86

87
#pragma vector=TIMER0_A0_VECTOR
88
__interrupt void ta0_isr(void)
89
{
90
  //Restspuelzeit reduzieren
91
  if (rest_spuelzeit>0L)
92
      {
93
          rest_spuelzeit-=1L;
94
      }
95
  zeit_nicht_gespuelt +=1L;
96
}
#7737854
Lesenswert?

Sebastian R. schrieb:

Ich habe volatile beachtet, aber nicht atomar. Dieses sollte aber eigentlich keinen effekt haben oder?

Nun 32Bit dürfte auf nem 16Bitter wohl 2 Zugriffe benötigen, wo natürlich ein Interrupt zwischen grätschen kann. Auch lustiges mehrfaches Lesen und Setzen im Main ist zutiefst unbestimmt. Die komplette Sequenz von ersten bis zum letzten Zugriff müßte dann atomar sein.

Ich setze mir daher im Interrupt nur ein 100ms Flag, was dann an kontrollierter Stelle im Main die Variablen ändert.

#7737890
Lesenswert?

Peter D. schrieb:

Sebastian R. schrieb:

Ich habe volatile beachtet, aber nicht atomar. Dieses sollte aber eigentlich keinen effekt haben oder?

Nun 32Bit dürfte auf nem 16Bitter wohl 2 Zugriffe benötigen, wo natürlich ein Interrupt zwischen grätschen kann. Auch lustiges mehrfaches Lesen und Setzen im Main ist zutiefst unbestimmt. Die komplette Sequenz von ersten bis zum letzten Zugriff müßte dann atomar sein.

Ich setze mir daher im Interrupt nur ein 100ms Flag, was dann an kontrollierter Stelle im Main die Variablen ändert.

Also das der Interrupt zwischen der Bearbeitung des 32Bit Wertes aufgerufen wird, kann ich glauben. Das es für einen Zyklus dann ggf. falsche Werte hat auch, aber spätestens bei der nächsten Abarbeitung müsste der Zähler wieder synchron sein. Es ist ja nur ein lesender Zugriff an der Stelle. Der Schreibzugriff findet ja nicht statt.

Ich werde mein Programm kurz abändern und ein Flag benutzen um die Werte zu bearbeiten. Das Programm ist ja schnell genug und wird nicht länger als 100ms brauchen. Dementsprechen wird es zu keinem doppelten Aufruf kommen.

#7737900
Lesenswert?

Sebastian R. schrieb:

Ich hab gerade paar Sachen rausgelöscht.

Ob das so gut ist?

ADC ist uninteressant hier habe ich einfach den vollausschlag von 1023 eingegeben.

Ist der Effekt noch da?

Ich habe volatile beachtet, aber nicht atomar. Dieses sollte aber eigentlich keinen effekt haben oder?

Oder.

Einfach will alles volatile zu deklarieren ist Unsinn. Nur Variablen, welche sowohl in einer ISR als auch im Hauptprogramm verwendet werden.

Die Bedingung sollte ja normalerweise, wenn P2value auf 0 gedreht wird.

zeit_nicht_gespuelt>intervall_zwangsspuelung #include <msp430.h> #include <msp430f2012.h> void io_init(void); volatile unsigned long rest_spuelzeit; volatile unsigned long zeit_nicht_gespuelt; unsigned int P2value = 0; int main(void) { int jumper_Spuelzeit;

1
volatile unsigned long intervall_zwangsspuelung;
2
volatile unsigned long max_spuelzeit;
3
volatile unsigned long min_Zwangsspuelzeit;
4
volatile unsigned long max_Zwangsspuelzeit;

Die volatile sind hier Unsinn, auch wenn sie nicht schaden.

Du greifst auf rest_spuelzeit und zeit_nicht_gespuelt mehrfach in deinem Hauptprgramm zu, außerdem in der ISR. Deine Hauptprogramm rast wie ein Wahnsinniger ohne Bremse im Kreis und wird schätzungsweise 10.000mal und mehr pro Sekunde ausgeführt. Die Chance, einen nichtatomaren Zugriffskonflikt zu erzeugen ist sehr hoch, um nicht zu sagen sicher. Sowas macht man nicht. Peter hat das deutlich bessere Konzept genannt. Der Timer aktiviert alle x ms ein Flag, welches in der Hauptschleife abgefragt und ausgewertet wird. Dann wird deine Funktion EINMAL aufgerufen. Dann kann sie CPU sonstwas machen oder wieder schlafen gehen. Damit vermeidet man u.a., daß in der ISR große Variablen verändert werden müssen. Dein Programm ist soooo langsam, daß das nicht nötig ist.

Alternativ muss der Durchlauf deines Programmabschnitts atomar gemacht werden, dann ist es sicher. Siehe Interrupt, jetzt mit korrekter Schreibweise ;-)

#7737901
Lesenswert?

Die Einrückungen deines Programm sind sehr schlecht, vermutlich TABs, welche hier in der Forumssoftware vermurkst wurden. Formatiere die Einrückungen sauber mit Leerzeichen.

1
      if (rest_spuelzeit>0L)
2
      {
3
        P2OUT |= 0x40;
4
        zeit_nicht_gespuelt=0L;
5
      }
6
      else
7
      {
8
        P2OUT &= ~0x40;
9
        rest_spuelzeit=0L;
10
      }

Sowas ist Unsinn. Im else Zweig ist rest_spuelzeit == 0, denn das wurde in der Bedingung geprüft. Das nochmal zuzuweisen ist Unsinn und irreführend.

#7737956
Lesenswert?

Loco M. schrieb:

Was ich irreführend finde, dass überall im Program 'signed long' Werte benutzt werden um 'unsigned long' Variablen zu initialisieren. Z.B. 'rest_spuelzeit=300L' anstelle von 'rest_spuelzeit=300UL'.

Hast du dir die Compiler Warnings mal genauer angeschaut?

Ich habe den Code übernommen und dort waren die schon mit einem L initialisiert. Es gibt keine Warnung, sonst hätte ich diese schon geschrieben.

Mein Fehler war definitiv, dass ich keinen Atomaren Datenzugriff verwendet habe. Ich verwende jetzt ein Timepulse Flag und ich schalte Global die Interrupts aus, wenn ich auf Daten worauf die Interupts schreiben. Test lasse ich gerade laufen.

Die Unordnung im Programm ist ein wenig der Versuche geschuldet. Ich hatte mit dem Volatile schon gelesen gehabt und habe irgendwann gesagt, driss drauf lieber vor allem als irgendwo was zu vergessen.

Das gleiche gilt dem "L".

Kann man beim Code Composer Studio die Einrückungen Global auf Leerzeilen stellen?

Obwohl ich mit Code Composer Studio absolut nicht zufrieden bin. Könnt ihr einen anderen IDE bzw. Compiler empfehlen? Ich würde sonst schauen ob ich nicht auf Eclispe gehe.

Macht es Sinn die stdint.h zuverwenden und mit den int32_t generell zu arbeiten? Prinzipiell ist es ja nichts anderes als meine declarationen.

#7738465
Lesenswert?

Hallo Zusammen, ich habe noch etwas weiter getestet. An dem Interrupt liegt es definitiv nicht, aber ich habe das trotzdem alles Atomar getrennt. Wenn ich mit kleinen Zahlen arbeite funktioniert es, aber sobald ich z.B. 18000 als intervall verwende geht es nicht mehr.

Könnt ihr ein gutes Board inkl. Programmer fürs debuggen empfehlen?

Mit 1800UL klappt es:

1
intervall_zwangsspuelung = 1800UL;

Wenn ich das Poti direkt mit einbinde, dann kommt die passende Zeit.

1
intervall_zwangsspuelung = ((1800UL*(uint32_t)P2value/1023UL)+200UL);

Mit 18000UL klappt es nicht

1
intervall_zwangsspuelung = 18000UL;
1
if((uint32_t)zeit_nicht_gespuelt > (uint32_t)intervall_zwangsspuelung)
2
{
3
 if (rest_spuelzeit==0)
4
 {
5
  rest_spuelzeit=100UL; 
6
 }
7
}
Moderator (Firma: Titel) Persönliche Seite #7738470
Lesenswert?

Sebastian R. schrieb:

1023UL

Das ist falsch. Richtig wären 1024.

Aber ich habe das trotzdem alles Atomar getrennt. rest_spuelzeit=100UL;

Das hier ist aber noch nicht atomar. Da kann irgendwas in die Zuweisung reinfunken.

Mit 1800UL klappt es

Mit 18000UL klappt es nicht

Mach mal eine sukzessive Approximation(**) an den Wert, der grade noch geht und den, mit dem es dann nicht mehr geht. Würde mich nicht arg wundern, wenn da 16383 und 16384 (oder irgendeine andere Zweierpotenz zwischen 1800 und 18000) herauskommen würden.

(**) nächster Test mit (18000+1800)/2 = 9900. Wenn der Wert 9900 noch funktioniert, dann mit (18000+9900)/2 = 13950 weiter. Wenn der 9900 nicht funktioniert, dann mit (9900+1800)/2 = 5850 weiter. Und dann immer weiter mit dem Mittelwert bis hin zum Übergang von "gut nach schlecht".

Sebastian R. schrieb:

Aber ich habe das trotzdem alles Atomar getrennt.

rest_spuelzeit=100UL;

Das hier ist aber noch nicht atomar. Da kann irgendwas in die Zuweisung reinfunken.

#7738479
Lesenswert?

Lothar M. schrieb:

Sebastian R. schrieb:

1023UL

Das ist falsch. Richtig wären 1024.

Aber ich habe das trotzdem alles Atomar getrennt. rest_spuelzeit=100UL;

Das hier ist aber noch nicht atomar. Da kann irgendwas in die Zuweisung reinfunken.

Mit 1800UL klappt es

Mit 18000UL klappt es nicht

Mach mal eine sukzessive Approximation(**) an den Wert, der grade noch geht und den, mit dem es dann nicht mehr geht. Würde mich nicht arg wundern, wenn da 16383 und 16384 (oder irgendeine andere Zweierpotenz zwischen 1800 und 18000) herauskommen würden.

(**) nächster Test mit (18000+1800)/2 = 9900. Wenn der Wert 9900 noch funktioniert, dann mit (18000+9900)/2 = 13950 weiter. Wenn der 9900 nicht funktioniert, dann mit (9900+1800)/2 = 5850 weiter. Und dann immer weiter mit dem Mittelwert bis hin zum Übergang von "gut nach schlecht".

Sebastian R. schrieb:

Aber ich habe das trotzdem alles Atomar getrennt.

rest_spuelzeit=100UL;

Das hier ist aber noch nicht atomar. Da kann irgendwas in die Zuweisung reinfunken.

Mit dem Annähern bin ich schon dran. hatte vorhin schon einmal mit 16382 getestet gehabt. Das hat noch funktioniert.

ich habe den Zugriff auf die Zähler Variablen nur noch im Main. Das sollte doch ausreichen sein. Die TimerPulse Variable wird von der Interrupt Variable auf 1 gesetzt. Wenn nur die Main Variable auf "zeit_nicht_gespuelt" zugreift, dann kann doch die Bearbeitung nicht manipuliert werden. Oder kann es passieren, dass der Interrupt die Register in die die Variablen geladen wurde manipuliert und ich davon nichts mitbekomme?

1
if(TimerPulse >0)
2
 {
3
  TimerPulse =0;
4
  //Restspuelzeit reduzieren
5
  if (rest_spuelzeit>0)
6
   {
7
    rest_spuelzeit--;
8
   }
9
   zeit_nicht_gespuelt += 1UL;
10
  }
#7738851
Lesenswert?

Hab den letzten Fehler gefunden. In einem Beitrag hatte ich gelesen, dass man die Optimierungen abschalten sollte. Dieses hatte ich getestet, aber vergessen wieder einzuschalten. Nachdem die Optimierung wieder eingeschaltet war, funktionierte auch der Rest vom Programm wieder.

geändert habe ich von der Ausgangssituation: Interrupt Routine gekürzt. Hier wird nur noch ein Flag gesetzt was im Main ausgewertet wird. ADC Interrupt setzt ebenfalls ein Flag und wird im Main umkopiert. Volatile vor Variablen geschrieben die im Interrupt und im Main verwendet werden.

Vielen Dank für die Unterstützung.

Moderator (Firma: Titel) Persönliche Seite #7738856
Lesenswert?

Sebastian R. schrieb:

Nachdem die Optimierung wieder eingeschaltet war, funktionierte auch der Rest vom Programm wieder.

(M)ein Programm muss mit und ohne Optimierung funktionieren.

Denn wenn die einen Unterschied macht, der sich nicht erklären lässt, dann hat man noch einen Fehler im Programm, der einen sicher wieder einholt.

Und wie schon gesagt: die Verwaltung von Zeiten wird viel einfacher, wenn man nicht zig Zähler verwalten muss, sondern nur 1 einzigen, der pünktlich und garantiert jede ms um 1 hochzählt. Wie das geht und sicher kein Überlaufproblem bringt, habe ich schon erwähnt.

#7738890
Lesenswert?

Sebastian R. schrieb:

Hab den letzten Fehler gefunden. In einem Beitrag hatte ich gelesen, dass man die Optimierungen abschalten sollte. Dieses hatte ich getestet, aber vergessen wieder einzuschalten. Nachdem die Optimierung wieder eingeschaltet war, funktionierte auch der Rest vom Programm wieder.

Das ist eine große Warnlampe! Wenn das Ein oder Ausschalten der Optimierung solche Änderung in der Reaktion eines Programms erzeugt, ist grundlegend was faul!

geändert habe ich von der Ausgangssituation: Interrupt Routine gekürzt. Hier wird nur noch ein Flag gesetzt was im Main ausgewertet wird. ADC Interrupt setzt ebenfalls ein Flag und wird im Main umkopiert. Volatile vor Variablen geschrieben die im Interrupt und im Main verwendet werden.

Bla Bla Bla!

Bis du so ignorant oder was ist sonst mit dir los? Warum haben MEHRERE Leute geschrieben, daß du den aktuellen, VOLLSTÄNDIGEN Quelltext hier hochladen sollst?

Du hast weder das Problem verstanden noch gelöst, nur solange planlos rumgefummelt bis es SCHEINBAR funktioniert.

#7738954
Lesenswert?

Lothar M. schrieb:

Sebastian R. schrieb:

Nachdem die Optimierung wieder eingeschaltet war, funktionierte auch der Rest vom Programm wieder.

(M)ein Programm muss mit und ohne Optimierung funktionieren.

Denn wenn die einen Unterschied macht, der sich nicht erklären lässt, dann hat man noch einen Fehler im Programm, der einen sicher wieder einholt.

Und wie schon gesagt: die Verwaltung von Zeiten wird viel einfacher, wenn man nicht zig Zähler verwalten muss, sondern nur 1 einzigen, der pünktlich und garantiert jede ms um 1 hochzählt. Wie das geht und sicher kein Überlaufproblem bringt, habe ich schon erwähnt.

Wie schon erwähnt habe ich das schon so abgeändert. Es gibt nur einen der per Flag aus dem Interrupt einmal im Main inkrementiert wird. Auch das Zurücksetzen findet nur einmal im Main statt. Problem gab es mit der Berechnung der Zwangsspülzeit.

#7738962
Lesenswert?

Falk B. schrieb:

Sebastian R. schrieb:

Hab den letzten Fehler gefunden. In einem Beitrag hatte ich gelesen, dass man die Optimierungen abschalten sollte. Dieses hatte ich getestet, aber vergessen wieder einzuschalten. Nachdem die Optimierung wieder eingeschaltet war, funktionierte auch der Rest vom Programm wieder.

Das ist eine große Warnlampe! Wenn das Ein oder Ausschalten der Optimierung solche Änderung in der Reaktion eines Programms erzeugt, ist grundlegend was faul!

geändert habe ich von der Ausgangssituation: Interrupt Routine gekürzt. Hier wird nur noch ein Flag gesetzt was im Main ausgewertet wird. ADC Interrupt setzt ebenfalls ein Flag und wird im Main umkopiert. Volatile vor Variablen geschrieben die im Interrupt und im Main verwendet werden.

Bla Bla Bla!

Bis du so ignorant oder was ist sonst mit dir los? Warum haben MEHRERE Leute geschrieben, daß du den aktuellen, VOLLSTÄNDIGEN Quelltext hier hochladen sollst?

Du hast weder das Problem verstanden noch gelöst, nur solange planlos rumgefummelt bis es SCHEINBAR funktioniert.

Daumen hoch für die Freundlichkeit.

Ich habe jetzt noch draus gelernt, dass wenn was nachher funktioniert/was gefunden hat, man am besten die Klappe hält.

Ich habe ein paar Fehler gefunden und behoben. Nur zur Korrektur, dass das Problem nicht verstanden wurde. Es waren leider mehrere Fehler.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren