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--;
}
/*----------------------------------------------------------------------------*/
Lesezeichen