Hallo liebe Gemeinde,
ich stehe derzeit mit meinem LPC11C24 mit 48 Mhz etwas auf dem Schlauch.
Und zwar: Ich messe ..., Minuten, Sekunden und Millisekunden mit dem
SysTick Handler (mit 1ms Takt) in folgendem struct:
1
typedef struct {
2
uint16_t msec;
3
uint8_t sec;
4
uint8_t min;
5
uint16_t hour;
6
uint8_t year;
7
} TIME_T;
Das funktioniert soweit. Nun wollte ich die Zeitstempel-Funktion um
Mikrosekunden erweitern. Um nun nicht per Timer 1us-Takt zu messen und
einen Interrupt zu erzeugen (1us bei 48Mhz wäre etwas ungünstig), dachte
ich mir:
Ich initialisiere einen Timer mit einem Prescaler von 48 per
1
// Initialisierung des us-Takt-Zählers
2
Chip_TIMER_Init(LPC_TIMER32_1);
3
Chip_TIMER_Reset(LPC_TIMER32_1);
4
// Prescale 48 -> 1us clock
5
Chip_TIMER_PrescaleSet(LPC_TIMER32_1, 48);
und resette ihn immer im SysTick_Handler(void):
1
Chip_TIMER_Reset(LPC_TIMER32_1);
2
Chip_TIMER_Enable(LPC_TIMER32_1);
In meiner heilen Gedankenwelt würde der Timer genau bei der
Inkrementierung der nächste Millisekunde wieder zurückgesetzt sein und
einen neuen Zyklus anfangen.
Und wenn ich eine us-Genauigkeit brauche, kann ich per
mitten im Code darauf zugreifen. Nun aber funktioniert es nicht so ganz
- das Live-Debugging zeigt an, dass der Timer mal überläuft, mal nicht,
es sind nicht mal die gleichen rekonstruierbaren Zeiten.
Wo könnte mein Denkfehler liegen?
Ich bedanke mich im Voraus
Klar passiert das. Eine ISR hat eine Latenz und einen Jitter. Wenn
andere Interrupte mit höherer oder gleicher Priorität gerade am laufen
sind, dann wird der Systick Handler solange verzögert, bis diese
verarbeitet sind. Dazu kommen noch Einflüsse wie Cache, Waitstates.
Du könntest den Timer stattdessen auf Overflow bei 1ms konfigurieren und
deine Systemzeit an den Timer statt an den Systick hängen.
Note schrieb:> das Live-Debugging zeigt an, dass der Timer mal überläuft, mal nicht,
was heisst 'überläuft'? Bei 1 MHz Takt passiert das doch erst nach 4294
s. Wenn der Systick mal länger als 1 ms braucht dann müsste der Zähler
max. 1 ms + x µs anzeigen, das könnte man ja im Systick auch messen wie
lange der max. gebraucht hat.
Wird der Zähler evtl. noch woanders benutzt? Von der Logik her müsste
das schon gehen.
Vielen Dank für guten Vorschläge. Ich bin erstmal Stefans Tipp gefolgt -
eigentlich simpel, warum kam ich auf einen anderen Timer, aber nicht auf
den SysTick? schulterzuck
Wie dem auch sei, der Fehler besteht noch, ist jedenfalls eingegrenzt
worden: Ab und zu kommt es beim Auslesen vor, dass die ms-Variable noch
nicht inkrementiert wurde, der uc-Counter jedoch bereits einen neuen
Zyklus startete.
Habe den Code wg. Debugging aufgebröselt , anbei die Fkt, die zur
Konvertierung aufgerufen wird.
uint64_t ret = (uint64_t) 1000*(((tm.hour)*3600+(tm.min)*60+(tm.sec))+(tm.msec))
15
+(usTimerCounter);
16
return ret;
17
}
Im atomic-block werden die Zeitvariablen tm und usTimerCounter
ausgelessen.
Der us-Wert wird aus SysTick->VAL (oder wie im Datenblatt bezeichnet:
SYST_CVR) gelesen.Und zwar die ersten 24 (Low)bits. Da der Timer von
47999 bis 0 dekrementiert wird, muss dieser, um Mikrosekunden zu
erhalten, durch 48 dividiert werden und vom max.Wert (1000us)
subtrahiert werden.
Note schrieb:> Ab und zu kommt es beim Auslesen vor, dass die ms-Variable noch> nicht inkrementiert wurde, der uc-Counter jedoch bereits einen neuen> Zyklus startete.
Logisch, weil Du zwar den Systick-IRQ sperrst, aber nicht seinen Timer
anhältst.
> usTimerCounter = (uint32_t) usTimerCounter / 48.0;
Wieso double-Berechnung? Die Nachkommastellen werden eh abgeschnitten,
da kannst Du auch gleich mit 48uL dividieren.
Wozu brauchst Du überhaupt µs-genaue Systemzeit? Das wird typisch nicht
als absolute Zeit benötigt, sondern als Intervalle. Beispielsweise, um
IOs im Mikrosekundenbereich zu toggeln.
Da könnte man auch mit der Debug-Unit arbeiten, falls es die im M0 gibt.
Einfach auf 0 setzen, die zählt mit CPU-Frequenz hoch, und entsprechend
viele Takte abwarten. Sonst so ähnlich mit irgendeinem Timer.
> Logisch, weil Du zwar den Systick-IRQ sperrst, aber nicht seinen Timer> anhältst.
Hm, danke für den Einfall. Wobei der Timer sich irgendwie nicht per
1
// disable sysTick
2
SysTick->CTRL&=~(1UL<<SysTick_CTRL_ENABLE_Pos);
3
4
5
// enable sysTick again
6
SysTick->CTRL|=(1UL<<SysTick_CTRL_ENABLE_Pos);
ein-/ausschalten lässt. Das Live-Debugging zeigt, dass der fröhlich
weiter dekrementiert. Und dabei steht im Datenblatt
>Remark: When the processor is halted for debugging the counter does not
decrement.
Daher gehe ich mal davon aus, dass ich etwas übersehen habe.
// bits [31:24] - Reserved, [23:0] the current value of the SysTick counter
9
__enable_irq();
10
11
// enable sysTick again
12
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;
Kaum habe ich die Berechnung de usTimerCounter direkt in den
atomic-Block eingefügt, wurde die Zeit mikrosekunden-genau berechnet.
Vorher war nur der Zugriff auf die Zählervariable SysTick->VAL atomar,
die Berechnung folgte hinterher. Und das hat nicht funktioniert.
Ich danke euch vielmals für die Hilfestellung, Leute :)
>Wieso double-Berechnung? Die Nachkommastellen werden eh abgeschnitten,> da kannst Du auch gleich mit 48uL dividieren.
Es war vorher eine double-Berechnung drin, ist nun veraltet und deshalb
korrigiert worden.
>Wozu brauchst Du überhaupt µs-genaue Systemzeit? Das wird typisch nicht>als absolute Zeit benötigt, sondern als Intervalle. Beispielsweise, um>IOs im Mikrosekundenbereich zu toggeln.
Die Bibliothek wurde ein Mal vor Jahren gemacht und wird seitdem einfach
nur rüberkopiert. Der einzige Befehl, den ich dann im Code brauche, ist
der Befehl zum Abgriff der aktuellen Systemzeit in Millisekunden. Also
einfach nur aus Bequemlichkeitsgründen, da aufgrund von CMSIS vom Cortex
zu Cortex sonst nichts (i.d.F.) geändert werden muss.
Note schrieb:> Kommando zurück, es hat alles funktioniert, so sieht der> atomic-Block nun aus:
Cool! :-) Du könntest noch versuchen, ob das ohne IRQ-Sperre auch geht.
Wenn der Systick nicht runterzählt, kann er ja eigentlich auch einen IRQ
nicht auslösen.
Die genaueste Möglichkeit für das was Du willst besteht darin, einen
cycle counter periodisch auszulesen (z.B. DWT_CYCCNT)und das Delta mit
Hilfe der (bekannten) Taktfrequenz des Controllers in Mikrosekunden
umzurechnen.
Wie allerdings schon angemerkt wurde, ist der Zeitpunkt des Auslesens
kritisch. Der Systick Timer ist nur dann "genau," wenn er die höchste
Priorität hat, sonst kann sich der Zeitpunkt seines "Feuerns" durch
höher priorisierte ISRs verzögern.
Lutz schrieb:> DWT_CYCCNT gibt es leider erst ab M3; die M0 (auch M0+) haben den nicht.
Deswegen schrieb ich ja auch "z.B."
Im prinzip kannst Du jeden freien Timer benutzen. Die funktionieren
eigentlich sehr primitiv; mit jedem Prozessortaktzyklus werden die um 1
erhöht. Oft werden sie so genutzt, dass sie beim Erreichen eines
programmierbaren Schwellwertes auf 0 zurückgesetzt werden und einen
Interrupt auslösen. Das muss man aber nicht. Man kann ihn einfach frei
laufen lassen, also ihn bis zum Rollover durchzählen lassen, wann immer
man will während des laufenden Betriebes auslesen und durch die
Differenz zwischen zwei Zählerständen die vergangene Zeit berechnen.
Solange das Delta zwischen zwei Auslesezyklen nie grösser wird als die
Registerbreite, stört es noch nicht mal, wenn der Zähler überläuft, die
Differenz ist dann trotzdem akkurat.