- Akku Tests und Balkonkraftwerk Speicher         
Ergebnis 1 bis 10 von 23

Thema: sehr Kurze Warteschleifen für das korrekte Timing

Hybrid-Darstellung

Vorheriger Beitrag Vorheriger Beitrag   Nächster Beitrag Nächster Beitrag
  1. #1
    Erfahrener Benutzer Roboter Genie
    Registriert seit
    05.11.2007
    Beiträge
    1.076
    Guten Morgen, Ihr wart ja fleissig , erstmal vielen Dank
    für Eure Informationen und Ideen.

    @ Sisor:
    Ja, Du hast recht, so kann ich den Timerüberlauf ignorieren. Gefällt mir sehr gut.


    @Klebwax:

    Ich hab die "2ms" nur zum Testen/Bestätigen das dort das Problem liegt eingebaut.
    Das ist natürlich völlig "überdosiert"


    Die Idee, dass der Compiler an bestimmten Stellen NICHT optimiert, wäre in meinem Falle
    sogar auch eine Lösung, find ich aber etwas unschön.


    @Witkatz:
    Ich habe so etwas in der Art auch schon implementiert.
    Sieht fast aus wie dein Code :

    Code:
    typedef struct 
    {
      void (*function)(); /* do this function */
      U32   microSec;     /* do next function after xxx micro second */
    } t_TimerFunc;
    
    /* shift out time for one byte is 17,92 microsec. we calc with 25 for every byte */
    /* the latch signal are set to 5 microsec. active phase and 5 microsec. recovery time */
    
    const t_TimerFunc functable[] = {
    
    {fn_shift_out_leds,100},       /* shift out 32 Bit LED code, after 100 microsec. */
    {fn_DISP_ENABLE_LEDS_HIGH,5},  /* generate the latch signal for 5 microsec. high*/     
    {fn_DISP_ENABLE_LEDS_LOW,5},   /* reset the latch signal to low */
    
    {fn_shift_out_pressure,50},    /* shift out 16 Bit data for pressure display, after 50us*/
    {fn_DISP_ENABLE_P_HIGH,5},     /* generate the latch signal for 5 microsec. high */
    {fn_DISP_ENABLE_P_LOW,5},      /* reset the latch signal to low */
    
    {fn_DISP_ENABLE_Q_LOW,5},      /* enable data for the flow display */
    {shift_out_flow,100},          /* shift out, 5 Bytes, data for flow display */
    {fn_DISP_ENABLE_Q_HIGH,5},     /* disable data for the flow display */
    
    {fn_DISP_ENABLE_V_LOW,5},      /* enable data for the volume display */
    {fn_shift_out_volume,125},     /* shift out data, 5 Bytes, for volume display */
    {fn_DISP_ENABLE_V_HIGH,5},     /* disable data for the volume display */
    
    . . . . 
    
    Wie dem obigen Codeausschnitt zu entnehmen ist, wurde eine spezielle Struktur angelegt um die Vorgehensweise 
    möglichst übersichtlich zu gestalten. Der Timer Interrupt ruft jeweils die entsprechende Funktion auf und 
    startet dann eine neue Zeitzähleinheit. Nach Ablauf der Zeit wird die nächste Funktion aufgerufen. 
    Wurden alle Funktionen durchlaufen, wird wieder mit der ersten Funktion in der Tabelle begonnen. 
    Die entsprechende Timer Interrupt Funktion konnte so kurz und übersichtlich implementiert werden.
    Für dieses Vorgehen  wurde der Prozessorinterne Timer 1 verwendet.
    fn_xxxxxx sind die entsprechenden Funktionen die aufgerufen werden.
    
    void TMR1_IRQHandler (void)
    {
      functable[timer1FuncNo].function();  /* do the selected function */
      LPC_T1TCR.bits.CR = 1;               /* set reset STOP and HOLD Timer */
     
      /* set the new time for the next interrupt */
      LPC_T1MR0 = functable[timer1FuncNo].microSec;
    
      /* selects the next function which want to call from this interrupt */
      if (++timer1FuncNo >= ((sizeof(functable) / sizeof(t_TimerFunc))))
            timer1FuncNo = 0; 
    
      LPC_T1IR.bits.MR0INT = 1;    /*clear the timer match interrupt */
      LPC_T1TCR.bits.CR    = 0;    /* release reset START Timer */
    }
    
    
    
    Um Sicherzustellen, dass alle Funktionen weniger Zeit benötigen, als angegeben, wurden die Laufzeiten mit einem 
    Oszilloskop gemessen. Dazu wurde am Anfang der Funktion ein Portbit gesetzt und am Ende der Funktion wieder gelöscht. 
    Dieses Portbit konnte so ohne Probleme mit einem Oszilloskop abgegriffen werden und die Laufzeiten anhand der 
    High Phase des Signal gemessen werden. Die Laufzeiten wurden mit verschiedenen C-Compiler Optionen gemessen 
    um sicherzustellen, daß unabhängig von den Compiler Einstellungen, die Zeiten eingehalten werden. 
    Es wurde auf Speed, Size und Balanced mit hoher, niedriger und völlig ohne Optimierung gemessen. 
    Die langsamste Variante war die völlig ohne Optimierung. Diese maximalen Zeiten wurden in der Software, 
    in den entsprechenden Funktionen, als Kommentar mit aufgenommen.
    
    Es wurde jedoch aus Sicherheitsgründen noch ein Rekursionstest eingeführt. Sollte sich die 
    Software innerhalb einer Funktion der Tabelle befinden und es erfolgt, aus welchen Grund auch immer, ein erneuter Eintritt 
    in diese Timerfunktion, liegt ein Fehler vor und das Gerät wird sofort angehalten. Dadurch könnte sonst ein Stacküberlauf 
    generiert werden und dies soll natürlich abgeblockt werden. Normalerweise ist dieses Vorgehen nicht nötig, da der Interrupt
    erst am Ende des Interrupt Handlers bestätigt wird und damit das momentan anstehende Interruptbit gelöscht wird. 
    Ich habe mich aber aus Sicherheitsgründen trotzdem für diese Vorgehensweise entschieden.
    Ich werde mich für die Timer Variante entscheiden, ein paar Mikrosekunden warten, spielt in dieser, meiner Anwendung keine Rolle.
    Habe ein 16 Bit Timer übrig, der dafür ausreicht.

    Meine While Schleife für den Timerwert blockiert ja nur kurzzeitig das Hauptprogramm,alle Interrupts laufen ja weiter. ADU, RS232 usw.

    Siro

    - - - Aktualisiert - - -

    Ich habe noch einen wichtigen Grund, warum ich der Delay Funktion niemals eine "1" übergebe:
    Es könnte passieren, dass garnicht gewartet wird.
    Wenn nach dem Setzten der globalen Variablen DelayCount der Timerinterrupt schon auftritt,
    wird der Wert gleich um eins runtergezählt und die While Schleife sofort wieder verlassen.
    Wir haben also generell eine Abweichung von ca. -1 Millisekunde.
    Code:
    /* EXTREM wichtig: volatile, sonst optimiert der Compiler Code weg */
    volatile static U32 DelayCount;  /* used for Delay_ms function */
    
    /*----------------------------------------------------------------------------*/
    /* DelayCount wird im Interrupt kontinuierlich um 1 runtergezählt */
    /* sofern er nicht schon 0 ist.
    
    void Delay_ms(U32 ms)
    {
      DelayCount = ms;
    
      while (DelayCount) ;
    }
    /*----------------------------------------------------------------------------*/
    /* this Interrupt handler is called every Millisecound */
    
    void SysTick_Handler(void)
    {
      if (DelayCount) DelayCount--;
    }
    /*----------------------------------------------------------------------------*/
    Geändert von Siro (24.06.2016 um 08:06 Uhr)

  2. #2
    Erfahrener Benutzer Roboter-Spezialist Avatar von witkatz
    Registriert seit
    24.05.2006
    Ort
    NRW
    Alter
    54
    Beiträge
    553
    Blog-Einträge
    17
    Zitat Zitat von Siro Beitrag anzeigen
    @Witkatz:
    Ich habe so etwas in der Art auch schon implementiert.
    Coole Idee mit der Funktionstabelle. Hab ich mir gemerkt

    Zitat Zitat von Siro Beitrag anzeigen
    Ich habe noch einen wichtigen Grund, warum ich der Delay Funktion niemals eine "1" übergebe:
    Es könnte passieren, dass garnicht gewartet wird.
    Wenn nach dem Setzten der globalen Variablen DelayCount der Timerinterrupt schon auftritt,
    wird der Wert gleich um eins runtergezählt und die While Schleife sofort wieder verlassen.
    Ich nehme für solche ms-Timings lieber einen 250µs Basistakt. Damit können mit einem char-Zähler (8bit PIC -> char wo es nur geht) Wartezeiten von 1ms bis 1s ziemlich gut realisiert werden, wenn eine Abweichung von 250µs keine Rolle spielt.

  3. #3
    Erfahrener Benutzer Robotik Einstein
    Registriert seit
    07.03.2011
    Beiträge
    1.899
    Zitat Zitat von Siro Beitrag anzeigen
    Die Idee, dass der Compiler an bestimmten Stellen NICHT optimiert, wäre in meinem Falle
    sogar auch eine Lösung, find ich aber etwas unschön.
    C ist in seiner ursprünglichen Form für die embeded Programmierung eigentlich nicht geeignet. Ohne einen Mechanismus, der die Optimierung steuert, geht nichts. Der Compiler würde sonst jede Abfrage von Funktionsregistern bis zur Unbrauchbarkeit optimieren. Stichworte sind volatile und INTRINSIC FUNCTIONS. Es mag für dich zwar unschön sein, ist aber unumgänglich.

    Code:
    void strobeE(){
        LCD_E = 1;
        __delay_us(1);               // delay 1us
        LCD_E = 0;
    }
    Wenn hier nicht ein solcher Mechanismus greifen würde, würde der Compiler das Setzen von LCD_E auf 1 rausschmeißen. Da niemand LCD_E liest, während es 1 ist, kann es auch gleich auf 0 gesetzt werden. Toter Code. LCD_E muß also volatile sein, damit das funktioniert.

    @Peter

    Ich habe deine Antwort nur bis --aktualisiert-- lesen können, als ich schrieb.

    MfG Klebwax
    Strom fließt auch durch krumme Drähte !

  4. #4
    Erfahrener Benutzer Roboter Genie
    Registriert seit
    05.11.2007
    Beiträge
    1.076
    @Klebwax:
    Das Problem Optimierung ist mir schon recht oft zum Verhängnis geworden.
    Genau in solchen Situationen wie Du eben beschrieben hast.
    Habe immer öfter bemerkt, dass ein "volatile" unumgänglich ist, damit der "schlaue" Compiler es auch "richtig" versteht.
    Am Anfang hab ich immer gemerkert warum der Compiler meinen Code klaut,
    Er hat ja recht würd ich aus heutiger Sicht sagen, woher soll er wissen dass......


    Ich hab grad noch etwas rumprobiert und da stellt sich gleich noch eine Frage:

    Zuvor der Code, der schien zu laufen.....
    Beim Timerüberlauf stimmt aber die Berechnung nicht mehr. Doch dazu später....

    Code:
    /*--------------------------------------------------------------------------*/
    /* static volatile U16 t_old; */
    
    inline void wait_us(U16 us)
    { U16 t_old;
    
      t_old = TIMER1_TC;
      while ((TIMER1_TC - t_old) < us) ;
    }
    /*--------------------------------------------------------------------------*/
    Nun zur Frage:

    Ich habe das Schlüsselwort "inline" benutzt, damit darf der Compiler den Code ja direkt einsetzen ohne einen Call.
    Ob er das macht bleibt glaube ich ihm überlassen.
    Damit ich meine Funktion evtl. sogar aus verschiedenen threads bzw. Interrupts aufrufen könnte, müsste mein Variable t_old ja Dynmsich auf dem Stack liegen, also Lokal zum Funktionsaufruf.
    Aber wie funktioniert das mit dem Inline, ist das überhaupt erlaubt ? Inline und lokale Variable ?

    Der Compiler könnte den Call nun sparen aber was macht er mit der Variable t_old ? die müste er ja dynamsich auf dem Stack verwalten.
    Wenn es um Geschwindigkeit geht und ich nur aus einem Thread die Funktion aufrufe, kann ich die Variable ja Global anlegen,
    ich vermute hier wird er den besten Code dafür erzeugen können.
    Geändert von Siro (24.06.2016 um 13:18 Uhr)

  5. #5
    Erfahrener Benutzer Roboter Genie
    Registriert seit
    05.11.2007
    Beiträge
    1.076
    Warum meine Berechnung nach Timerüberlauf nicht mehr funktioniert, habe ich unn auch ermittelt.


    while (TIMER1_TC - t_old < us) ; /* geht nicht */

    /* Also "komplette" explizite Typwandlung auf allen Werten und Ergebnissen angewendet: */

    while ((U16)((U16)(TIMER1_TC) - (U16)(t_old)) < us) ; /* siehe da, es funktioniert */

    /* nun Typwandlungen gekürzt */

    while ((U16)(TIMER1_TC - t_old) < us) ; /* auch OKAY */


    Durch die Integer Promotion des Standard Typs int bei 32 Bit muss hier anscheinend das Ergebnis der Subtraktion
    explizit wieder in einen unsigned 16 Bit gewandelt werden, sonst geht es schief, jedoch nur manchmal, je nach Timerstand.

    Ich bin begeistert und kann nun das Wochenende geniessen...
    Habt auch ein schönes, warmes WE.

    Siro

  6. #6
    Erfahrener Benutzer Robotik Einstein
    Registriert seit
    27.08.2013
    Ort
    Region Basel
    Alter
    67
    Beiträge
    2.435
    Hallo Siro,
    Zitat Zitat von Siro Beitrag anzeigen
    Ich habe das Schlüsselwort "inline" benutzt, damit darf der Compiler den Code ja direkt einsetzen ohne einen Call.
    Ob er das macht bleibt glaube ich ihm überlassen.
    Genau so ist es.

    Zitat Zitat von Siro Beitrag anzeigen
    Damit ich meine Funktion evtl. sogar aus verschiedenen threads bzw. Interrupts aufrufen könnte, müsste mein Variable t_old ja Dynmsich auf dem Stack liegen, also Lokal zum Funktionsaufruf.
    Aber wie funktioniert das mit dem Inline, ist das überhaupt erlaubt ? Inline und lokale Variable ?
    Man kann in jedem Block heute eine lokale Variable festlegen, OK, bei K&R war das noch nicht erlaubt.

    Code:
    void function(void)
      {
          int i;
          // mach was mit i
            {
                int i2;
                // mach was mit i und i2
            }
          // ab hier gibt es i2 nicht mehr
          // mach noch was mit i
      }
    Zitat Zitat von Siro Beitrag anzeigen
    Der Compiler könnte den Call nun sparen aber was macht er mit der Variable t_old ? die müste er ja dynamsich auf dem Stack verwalten.
    Wenn es um Geschwindigkeit geht und ich nur aus einem Thread die Funktion aufrufe, kann ich die Variable ja Global anlegen,
    ich vermute hier wird er den besten Code dafür erzeugen können.
    Vermutlich wird der Compiler die einzelne Variable gar nicht auf dem Stack ablegen, sondern in einem Register behalten.
    Kommt aber auf die CPU und den restlichen Code an.

    Es gibt noch das Keyword register, damit konnte man dem Compiler nahelegen, welche Variablen, wenn möglich, in einem Register abgelegt werden sollen. Das war so eine "per Hand" Optimierung. Heute hat register keinen praktischen Wert mehr, der Compiler optimiert dies automatisch.

    MfG Peter(TOO)
    Manchmal frage ich mich, wieso meine Generation Geräte ohne Simulation entwickeln konnte?

  7. #7
    Erfahrener Benutzer Roboter Genie
    Registriert seit
    05.11.2007
    Beiträge
    1.076
    Guten Morgen und Danke Peter.

    Nanu, da bin ich jetzt echt erstaunt, mit den "Block Lokalen" Variablen.
    Das ist mir völlig neu... Ist das Ansi/C99 konform ?
    Muss ich mal probieren, der Compiler müsste ja dann bei einem Verstoß meckern.

    Ich denke auch, bei solchen Ein/Zweizeilern wird der Compiler das automatisch in den Registern halten.

    Siro

  8. #8
    HaWe
    Gast
    ja, das mit den Blockvariablen ist 100% ANSI C99.

    Der C-Compiler wird die Variablen aber nur in den exe-Code übernehmen, wenn du mit ihren Werten auch irgendetwas machst (z.B. printf() Ausgabe). Wenn du nur rechnest, ohne mit den Ergebnissen was zu machen, fliegen sie komplett raus beim Kompilieren.
    Nur das Zauberwörtchen
    volatile
    vor der Variablendefinition kann dich vor dieser Optimierung (egal welches Optimierungs-Level) schützen, also wundere dich nicht, wenn du sie eventuell nicht im exe-Code wiederfindest!

Ähnliche Themen

  1. Lipo-Ladegerät auf korrekte Funktion prüfen
    Von Che Guevara im Forum Elektronik
    Antworten: 2
    Letzter Beitrag: 17.03.2013, 10:51
  2. Korrekte Beschaltung eines Relais an einem AVR
    Von Björn im Forum AVR Hardwarethemen
    Antworten: 51
    Letzter Beitrag: 10.08.2009, 17:20
  3. Korrekte Schallmessung
    Von Dolfo im Forum Sensoren / Sensorik
    Antworten: 3
    Letzter Beitrag: 15.09.2008, 22:54
  4. Antworten: 3
    Letzter Beitrag: 18.01.2007, 21:10
  5. Warteschleifen bei ATMEL
    Von monoton im Forum C - Programmierung (GCC u.a.)
    Antworten: 8
    Letzter Beitrag: 08.09.2004, 00:15

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •  

Solar Speicher und Akkus Tests