Fehler im Programm, verstehe es nicht.

OP #7385772
Lesenswert?

Hallo, ich möchte eine neues Projekt starten. hierzu erzeugt ein Timer jese millisekunde ein Interrupt. In diesem Interrupt wird eine volatile varriable um 1 hochgezählt. Dass der Interrupt jede ms ausgeführt wird, habe ich überprüft, indem ich eine LED immer toggle und das Signal mit einem Oszzi überprüft habe. Passt, jede ms ein Flankenwechsel high>low oder low>high.

Dann erfolgt im zyklischen Programmteil eine Logic, die nach 1000 ms eine andere LED toggelt. Was nicht stimmt, dass die Zeit zum Toggeln 2 Sekunden dauert, es sollte aber 1 Sekunde sein. Jetzt könnte man denken, dass irgendwo anders im Zyklus eine Sekunde gewartet wird. Ist es aber nicht, weil wenn ich den Zähler (COUNT) von 1000 auf 500 reduziere, dass beträgt die Toggelzeit exact 1 Sekunde. Also Fehler Faktor 2. Bin für jeden Hinweis dankbar......

Hier ein Teil des Codes:

1
extern volatile int zeit;
2
extern int zeit_backup;
3

4
//********************************************************************
5
// 1ms Timer Interrupt
6
//********************************************************************
7
void TMR_CallbackFn (uint32_t status,uintptr_t context );
8
void TMR_CallbackFn (uint32_t status,uintptr_t context )
9
{
10
     zeit ++ ;
11
     // LED1_Toggle();  // 1 ms gemessen
12
}
13

14
//********************************************************************
15
// main loop
16
//********************************************************************
17
case CLKS_STATE_EVENT:          // Loop forever
18
            
19
        if  (zeit >=1)   
20
            {
21
            TMR2_InterruptDisable();;   
22
            // To avoid interrupts during "zeit" transfer
23
            zeit_backup = zeit;         
24
            // abgelaufene Zeit in ms übertragen
25
            zeit = 0;                   // und zurücksetzen
26
            TMR2_InterruptEnable();
27
            
28
            #define COUNT 500   // ????? Warum 500. Müsste 1000 sein  ????
29
            appData.msec_cnt += zeit_backup ; 
30
            if (appData.msec_cnt >=COUNT)
31
                {
32
                //---------jede Sekunde------------------------------------
33
                appData.msec_cnt =- COUNT;
34
                appData.sec_cnt++;
35
                LED1_Toggle();
36
                }
37
            }
Gast #7385793
Lesenswert?

Beim Lesen deines Codes bekomme ich einen Knoten im Hirn. Aber er ist sicher nicht fehlerhaft.

Die "zeit" wird jede ms um 1 erhöht. Du toggelst die LED nach 500ms. Sie ist also 500ms AN und dann 500ms AUS. Macht zusammen 1s pro Zyklus, oder 1 Hz.

#7385797
Lesenswert?

Dirk F. schrieb:

Istzustand: Bei COUNT 1000 : LED 2s an 2s aus Bei COUNT 100 : LED 200 ms an 200 ms aus

Hmm, das glauben wir einfach mal. Dann läuft irgendwas nur halb so schnell wie es soll. Oder es wird immer ein Takt "verdoppelt" ? Komisch. Möglichwerweise kommt der Timer mit dem dauernden Reset auf 0 aus dem Takt, das kann mit dem Vorteiler zusammen hängen. Wenn der auch einen Reset bekommt, kann sowas passieren. So oder so ist dein Ansatz unsinnig. Pack die Uhr in den Timer-Interrupt und generiere dort Flags für das Hauptprogramm. Dabei läuft der Zähler durch und alles ist gut.

Gast #7385802
Lesenswert?

Schreibe mal ein neues Programm, wo nur die für diesen Effekt relevanten Teile drin sind. Tritt der Fehler dann noch auf, dann zeige und den kompletten Quelltext dieses Testprogramms. Tritt er nicht mehr auf, finde den Unterschied.

Und zeige uns dein Oszilloskop-Bild von den 1ms. Vielleicht sind es doch 2ms und du hast dich nur verguckt.

#7385805
Lesenswert?

1
extern volatile int zeit;
2
extern int zeit_backup;
3
extern volatile int flag_led;
4

5
//********************************************************************
6
// 1ms Timer Interrupt
7
//********************************************************************
8

9
void TMR_CallbackFn (uint32_t status,uintptr_t context );
10
void TMR_CallbackFn (uint32_t status,uintptr_t context )
11
{
12
    zeit ++ ;
13
    // LED1_Toggle();  // 1 ms gemessen
14

15
#define COUNT 1000
16
    appData.msec_cnt++; 
17
    if (appData.msec_cnt >=COUNT)
18
    {
19
        appData.msec_cnt =- COUNT;
20
        appData.sec_cnt++;
21
        flag_led = 1;
22
    }
23
}
24

25
//********************************************************************
26

27
// main loop
28

29
//********************************************************************
30

31
case CLKS_STATE_EVENT:          // Loop forever
32

33
    if (flag_led)
34
    {
35
        flag_led = 0;
36
        LED1_Toggle();
37
    }
#7385823
Lesenswert?

Dirk F. schrieb:

@Falk: Und wie soll ich dann die Zykluszeit in ms ermitteln ???

Welche Zykluszeit? In deinem Beispiel zählst du nur eine Uhr hoch. Und wenn man die Zeitdifferenz zweier Ereignisse messen will, macht man das wie der Rest der Welt.

Differenz = Endzeit - Startzeit.

Und wenn man periodisch ein Ereignis auslösen will, kann man das auch über den Interrupt machen. Anstatt des #define für die 1000ms nutzt man eine Variable. Damit kann man variable Blinkzeiten oder was auch immer generieren.

#7386058
Lesenswert?

Man muss den Interrupt gar nicht sperren wenn man das so macht:

volatile int zeit1; volatile int zeit2; int failsafe_exit = 0;

while (1) { zeit1 = zeit; zeit2 = zeit; if (zeit1 == zeit2) { break; } failsafe_exit++; if (failsafe_exit > 10) { break; } }

failsafe_exit braucht man nicht unbedingt. Ist aber besser. Wobei wenn der greift, dann gibt es keine konsistente Zeit. Da muss man dann richtung Fehlerbehandlung gehen.

Die While schleife sollte max 2 mal laufen, wenn der Interrupt ca 1ms kommt.

#7386193
Lesenswert?

Dirk F. schrieb:

Der wechselt einmalig am Anfang (Statusmaschine) vom State Init in Event.

Wenn der Interrupt jede 1ms triggert, wie auf dem Oszi zu sehen, und also jede 1ms "zeit" um 1 erhöht, und du häufig genug in der Hauptschleife die in "zeit" gezählten 1ms in appData überträgst und auf das Erreichen von 1000 überprüfst, dann sollte dieses Erreichen von 1000 jede Sekunde passieren. Wenn es nur alle zwei Sekunden passiert, bzw. nur jede Sekunde passiert indem du statt auf das Erreichen von 1000 auf das Erreichen von 500 prüfst, dann ist irgendwo tatsächlich der Wurm drin.

Hast du eine Möglichkeit, Testausgaben zu generieren? Dann gib an der Stelle "//---------jede Sekunde------------------------------------" mal appData.msec_cnt aus.

LG, Sebastian

OP #7386208
Lesenswert?

Sebastian W. schrieb:

Hast du eine Möglichkeit, Testausgaben zu generieren? Dann gib an der Stelle "//---------jede Sekunde------------------------------------" mal appData.msec_cnt aus.

Ja, habe ich. Über CDC (USB-Serial) COM Port kann ich Meldungen ausgeben.

Werde dann das Programm anpassen und diese Werte jede Sekunde ausgeben: appData.msec_cnt appData.sec_cnt

Dauert aber noch etwas.....

dann ist irgendwo tatsächlich der Wurm drin.

Sach ich doch......

OP #7386227
Lesenswert?

Peter D. schrieb:

Das Programm ist mir viel zu sehr von hinten durch die Brust ins Auge, da sieht doch keiner durch.

Dann will ich die Programm Logic mal so erklären:

  1. Im Interrupt wird eine Varriable (32 Bit) jede ms um 1 erhöht.
  2. Der normalen Zyklus (Superloop) kann einige us bis einige ms dauern.
  3. Im normalen Zyklus wird die bisher vergangene Zeit vom letzten Aufruf in ms übertragen (= aktuelle Zykluszeit in ms). Wenn diese 0 ist, dann ist noch keine ganze ms vergangen >>> mach nix Wenn diese 1 oder mehr ist, dann addiere die vergangene Zeit in ms auf den ms-Zähler.

Wenn der ms Zähler dann z.B 1003 ist (letzter Zyklus dauerte z.B. 3 ms), dann ziehe 1000 ab, bleibt Rest 3 für nächsten Durchlauf, damit keine 3ms verloren gehen. 4.Erhöhe den Sekundenzähler.

#7386242
Lesenswert?

Dirk F. schrieb:

Versuche -= statt =-

Wo ist der Unterschied ? Ist =- nach C-Standard überhaupt erlaubt ?

Ahhhh.

1
appData.msec_cnt =- COUNT;  // appData.msec_cnt = -COUNT;  feste Zuweisung
2
appData.msec_cnt -= COUNT;  // appData.msec_cnt = appData.msec_cnt - COUNT; Subtraktion

Das erklärt das Problem! Nur merkwürdig, daß das der Compiler mit den Leerzeichen so frißt.

#7386249
Lesenswert?

Trotzdem ist Dein Code maximal ineffizient. Warum nicht alles in der ISR?

1
struct zeit { unsigned int ms, s;};
2

3
volatile struct zeit my_zeit = {};
4

5
void TMR_CallbackFn (uint32_t status,uintptr_t context )
6
{
7
  unsigned int ms = my_zeit.ms; /* avoid double volatile var access */
8

9
  if (ms < 999)
10
  {
11
    my_zeit.ms = ms + 1;
12
  }
13
  else
14
  {
15
    my_zeit.ms = 0;
16
    my_zeit.s++;
17
  }
18
}
19

20
struct zeit get_zeit()
21
{
22
  struct zeit z;
23

24
  do {
25
    z.ms = my_zeit.ms;
26
    z.s  = my_zeit.s;
27

28
    /* if ms field changed, isr interrupted us& readout is invalid -> just repeat */
29
  } while(z.ms != my_zeit.ms); 
30

31
  return z;
32
}

Edit: zu schnell getippt, mus != Vergleich sein.

#7386253
Lesenswert?

Ozvald K. schrieb:

Dirk F. schrieb:

appData.msec_cnt =- COUNT

von -500 bis + 500

Wenn Du das wirklich so: =- in Deinem Programm stehen hast, dann kann Ozvald nur recht geben; das erste Toggeln kommt zwar eventuell noch zum richtigen Zeitpunkt, aber das übersieht man natürlich leicht, alle folgenden Intervalle sind immer das doppelte von COUNT.

Sollte das nur ein Tippfehler hier im Post sein, dann weißt Du jetzt, warum alle nach einem compilierenden Minimalbeispiel verlangen.

#7386257
Lesenswert?

Dirk F. schrieb:

Weil ich dann keine Zykluszeiten messen kann.

Verstehe ich nicht. Klar geht das. Wenn Du die Differenz zwischen zwei Zeitpunkten messen willst wird es sogar noch einfacher. Du brauchst nur eine einzige "ms" Variable (unsigned) im Interrupt hochzuzählen, und zwar ohne auf 0 zurückzusetzen. Du merkst Dir den Stand am Anfang und den Stand am Ende deines Zyklus. Deine Zyklusdauer ist "Ende - Anfang" Und zwar immer, Overflows usw. werden wegen der Subtraktion implizit berücksichtigt.

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

Dirk F. schrieb:

Daher brauche ich diesen ms Tick.

Ein Tickzähler läuft immer nur hoch. Er wird nie zurückgesetzt. Das Zurücksetzen passiert implizit nur dann, wenn der Zähler überläuft.

Mein Tipp: sieh dir die Implementierung von millis() beim Arduino an. Damit können sogar blutige Laien mit Zeitdifferenzen ohne Überlaufprobleme arbeiten.

OP #7387075
Lesenswert?

Also nochmals Danke an alle. Der Programmteil läuft jetzt fehlerfrei. War wirklich ein blöder Fehler.

Falk B. schrieb:

Passiert die Magie der Integerarithmetik. Solange die Differenz kleiner als der Zählumfang des Timers ist, ist die Differenz IMMER korrekt!

Also wenn ich einen 16 Bit Timer, der jede µs um 1 erhöht wird, frei laufen lasse, und ihn an zwei unterschiedlichen Zeitpunkten auslese, dann ist das Ergebnis auch noch richtig, auch wenn zwischendurch ein Überlauf von 0xFFFF auf 0x0000 stattgefunden hat ?

Mal nachrechnen:

  1. Zeitpunkt Zähler = 0xfffe (Dezimal 65534)
  2. Zeitpunkt Zähler = 0x0005 (Dezimal 5)

Zeitdifferenz Dezimal (65536-65534) + 5 = 7 µs

Zeitdifferenz HEX 0xffe - 0x005 = 0xF007 (Dezimal 61447)

OP #7387195
Lesenswert?

Also Jungs, das war der best Hinweis überhaupt. Durch das neue System wird der Prozessor gar nicht mehr mit Interrupts belastet. Und die Messung der aktuellen Zykluszeit hat eine Auflösung von 1 µs.

Hier ein Teil des Codes:

1
void CLKS_Initialize ( void )
2
    {
3
    // Timer 2/3 (32 Bit) läuft endlos mit 25 MHz
4
    // Konfiguriert mit Harmony für PIC32MZ
5
    TMR2_PeriodSet(0xffffffff);
6
    TMR2_Start();           // Start the timer            
7
    LogMsg("The 1 microsecond timer has started");
8
    } 
9

10

11
void CLKS_SM ( void )
12
    {
13
    static uint32_t zeit_alt ;
14
    static uint32_t zeit_neu ;
15
    zeit_neu = TMR2_CounterGet(); // Aktuelle Zeit holen
16
    
17
    // Vergangene Zeit in us * 25, weil Timer2/3 mit 25 MHz läuft
18
    zykuszeit_act = (uint32_t) ( (int32_t) zeit_neu - zeit_alt) / 25;    
19
    zeit_alt = zeit_neu;    // Neue Zeit übertragen
20

21
   
22
    appData.usec_cnt += zykuszeit_act ; 
23
    appData.msec_cnt = appData.usec_cnt/1000; 
24
    
25
    if (appData.usec_cnt >= 1000000)
26
        {
27
        //---------jede Sekunde------------------------------------
28
        appData.usec_cnt -= 1000000;
29
        appData.sec_cnt++;
30
        appData.unixsecs++;
31
        clk1000();
32
        }
#7387197
Lesenswert?

Dirk F. schrieb:

Also Jungs, das war der best Hinweis überhaupt. Durch das neue System wird der Prozessor gar nicht mehr mit Interrupts belastet. Und die Messung der aktuellen Zykluszeit hat eine Auflösung von 1 µs.

Schöner Unfug. Vor allem, da dein Timer mit 25 MHz läuft. Der hat auch einen Vorteiler, den kann man einstellen, dann läuft der direkt mit 1MHz, was immer noch Käse ist, aber etwas weniger Käse.

1
static uint32_t zeit_alt ;
2
static uint32_t zeit_neu ;
3
zeit_neu = TMR2_CounterGet(); // Aktuelle Zeit holen
4

5
// Vergangene Zeit in us * 25, weil Timer2/3 mit 25 MHz läuft
6
zykuszeit_act = (uint32_t) ( (int32_t) zeit_neu - zeit_alt) / 25;

Was soll der cast-Unsinn? Deine Variablen sind 32 Bit, da braucht man nix weiter!

1
zeit_alt = zeit_neu;    // Neue Zeit übertragen
2

3
appData.usec_cnt += zykuszeit_act ;
4
appData.msec_cnt = appData.usec_cnt/1000;
5

6
if (appData.usec_cnt >= 1000000)
7
    {
8
    //---------jede Sekunde------------------------------------
9
    appData.usec_cnt -= 1000000;
10
    appData.sec_cnt++;
11
    appData.unixsecs++;
12
    clk1000();
13
    }

Jaja, warum nicht gleich Nanosekundenauflösung?

#7387209
Lesenswert?

Falk B. schrieb:

Ja und? Dann hättest du 25MHz/128=195,3125kHz (5,12us) Auflösung. Immer noch mehr als sinnvoll, vor allem für solche Messungen.

Woher willst Du das wissen? Wir machen hier z.B. synchrone Automatisierungstechnik, da messe ich die Laufzeit meiner Software in "ns". 2us Jitter liegen bei uns schon außerhalb der Spezifikation. 5us sind ne halbe Ewigkeit.

#7387211
Lesenswert?

Andreas M. schrieb:

Woher willst Du das wissen?

Daher!

"Vielen Dank an alle. Ich habe es kapiert. Bin halt nur Gelegenheitsprogrammierer...."

Das ist nur Hobbyspielerei.

Wir machen hier z.B. synchrone Automatisierungstechnik, da messe ich die Laufzeit meiner Software in "ns". 2us Jitter liegen bei uns schon außerhalb der Spezifikation. 5us sind ne halbe Ewigkeit.

Von dir und deiner professionellen Anwendung war gar nicht die Rede.

OP #7387222
Lesenswert?

Falk B. schrieb:

Und darauf lassen die einen Gelegenheitsprogrammierer los. Sehr professionell!

Falk, ich glaube Du bist etwas frustriert. Es gibt auch Leute, de neben Programmieren auch noch etwas anders beruflich machen. Fachidiot: Kann eine Sache zu 100 %

Guter Mitarbeiter: Kann mehrere Sachen, nicht zu 100 % aber ist bereit sich neuen Aufgaben zu stellen und sich einzuarbeiten....

#7387274
Lesenswert?

dirkf schrieb:

Fachidiot: Kann eine Sache zu 100 %

Auf den kann man sich also verlassen - liefert stets korrekte Ergebnisse.

Guter Mitarbeiter: Kann mehrere Sachen, nicht zu 100 % aber ist bereit sich neuen Aufgaben zu stellen und sich einzuarbeiten....

Kann also nichts richtig - liefert stets zusammengeschusterten Laienpfusch und wird in Kürze von ChatGPT/Copilot ersetzt. Diese Einstellung, dass sowas ein "Guter Mitarbeiter" ist, scheint bei den Personalern leider weit verbreitet zu sein (er ist halt billiger). Es wird übersehen, dass das "sich einarbeiten" in Fachgebiete gerne mal Jahre dauert. Da dafür die Zeit nicht reicht, gibt man sich mit Pfusch zufrieden - entsprechend ist der aktuelle Stand der Technik.

Ich wüsste auf jeden Fall, wen ich also Mitarbeiter haben wollte ...

#7387345
Lesenswert?

Foobar schrieb:

Auf den kann man sich also verlassen - liefert stets korrekte Ergebnisse.

Kein Mensch liefert stets korrekte Ergebnisse.

Foobar schrieb:

Guter Mitarbeiter: Kann mehrere Sachen, nicht zu 100 % aber ist bereit sich neuen Aufgaben zu stellen und sich einzuarbeiten....

Kann also nichts richtig - liefert stets zusammengeschusterten Laienpfusch [Noch mehr Unsinn]

Nö, genau die Leute die breit Aufgestellt sind werden die einzigen sein, die zukünftig noch Arbeit haben werden. Denn Gerade die "Fachidioten" sind die, die man mit KI ersetzen wird. Bisher hat man solche Arbeiten gerne nach Fernost abgeschoben, aber die Leute dort haben auch keinen Bock mehr da drauf.

Die Ergebnisse von Entwicklern, die nur in Ihrer eigenen kleinen Welt leben, darf ich jeden Tag erleben.

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

Dirk F. schrieb:

Guter Mitarbeiter: Kann mehrere Sachen, nicht zu 100 % aber ist bereit sich neuen Aufgaben zu stellen und sich einzuarbeiten....

... und findet nötigenfalls schneller eine Ausrede als eine Maus ein Loch!

Sehr guter Mitarbeiter: kann mehrere Sachen zu 100 %, kann über den Tellerrand hinausblicken und Zusammenhänge herleiten.

Nachdem das geklärt ist gehen wir mal zur Sache...

Falk B. schrieb:

Schöner Unfug. Vor allem, da dein Timer mit 25 MHz läuft.

Hab ich mir sofort auch gedacht, als ich /25 und /1000 gesehen habe. Arg viel ineffizienter kann man nicht prorammieren, denn auch heute noch sind Prozessoren im tiefsten Inneren binär. Eine dezimale Rechnung (oder allgemeiner einer Rechnung jenseits von Zweierpotenzen) macht ihnen Aufwand.

Dirk F. schrieb:

Nee, keinen Vorteiler 100, nur 128

Sag ichs noch: ein µC ist binär. Für eine effiziente Programmierung musst du dich dem Controller anpassen, denn der Witz ist: du schreibst das Programm nur ein einziges Mal, der Controller muss es aber zigtausendmal pro Sekunde ausführen.

Ich mache es so: ein Zeitzähler (Timertic) läuft immer hoch. Er wird niemals von Hand zurückgesetzt. Nur am "Ende" der läuft er implizit über. Und basierend auf diesem Zähler dann werden alle Zeiten per Differenz verwaltet. Ein Tipp dazu: sieh dir die Arduino millis() Funktion und die Arbeit mit Zeitdifferenz mal genauer an.

Andreas M. schrieb:

2us Jitter liegen bei uns schon außerhalb der Spezifikation. 5us sind ne halbe Ewigkeit.

Ja, mag sein. Allerdings bin ich mir absolut sicher, dass daran dann aber auch keine "fortgeschrittenen Laien" arbeiten.

Im Fall hier würde ich mir überhaupt erst mal klar machen, welche Zeitauflösung denn nötig und welcher Jitter erlaubt ist. Und dann eine geeignete Zeitbasis aufbauen, die dem Controller "gut liegt" und mit der er einfach arbeiten kann.

Falk B. schrieb:

Dann hättest du 25MHz/128=195,3125kHz (5,12us) Auflösung

Also könnte ich da einfach einen "hundertstel µs"-Zähler bei jedem Interrupt um 512 hochzählen. Und wenn der dann über 100000 hinausläuft, dann würde ich den ms-Zähler um 1 hochzählen und den "hundertstel µs"-Zähler atomar(!) um 100000 verringern.

Also etwa so:

1
long millis, lasttoggle; 
2
:
3
TimerInterrupt () { // 5.12µs
4
   static long us100cnt;
5
   us100cnt += 512;
6
   if (us100cnt>100000) {
7
      millis++;
8
      us100cnt-=100000;
9
   }
10
}
11
:
12
// Arbeiten mit Zeitdifferenzen
13
if (millis-lasttoggle > 1000) {
14
    toggleLED();
15
    lasttoggle=millis; // Umschaltzeitpunkt merken
16
}
17
:
18
// oder besser so, dann summieren sich Verzögerungen nicht auf, 
19
// sondern "verpasste" Toggles werden "nachgeholt" 
20
if (millis-lasttoggle > 1000) {
21
    toggleLED();
22
    lasttoggle+=1000; // Umschaltzeitpunkte jeweils alle 1000 ms
23
}
24
:

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