Forum: Mikrocontroller und Digitale Elektronik LPC/ARM-Cortex-M0, SysTick für Millisekunden, Timer für Mikrosekunden


von Note (Gast)


Lesenswert?

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
1
volatile uint32_t usTimerCount = Chip_TIMER_ReadCount(LPC_TIMER32_1);

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

von Stefan F. (Gast)


Lesenswert?

Wenn du einen Systemtakt hast, der ein vielfaches von 1µs beträgt, 
kannst du doch einfach den Systick Timer-Counter auslesen.

von Andreas M. (amesser)


Lesenswert?

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.

von Johannes S. (Gast)


Lesenswert?

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.

von Note (Gast)


Lesenswert?

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.
1
uint64_t SysTime_ConvertToMicroSeconds(volatile TIME_T *sysTime){
2
  // atomic block
3
  __disable_irq();
4
  TIME_T tm = *sysTime;
5
  // bits [31:24] - Reserved, [23:0] the current value of the SysTick counter
6
  volatile uint32_t usTimerCounter = (SysTick->VAL);
7
  __enable_irq();
8
9
  // bits [31:24] - Reserved, [23:0] the current value of the SysTick counter
10
  usTimerCounter &= 0x0FFF;
11
  usTimerCounter = (uint32_t) usTimerCounter / 48.0;
12
  usTimerCounter = 1000 - usTimerCounter;
13
14
  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.

von Nop (Gast)


Lesenswert?

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.

von Note (Gast)


Lesenswert?

> 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.

von Note (Gast)


Lesenswert?

Kommando zurück, es hat alles funktioniert, so sieht der atomic-Block 
nun aus:
1
  // disable sysTick
2
  SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
3
4
  __disable_irq();
5
  TIME_T tm = *sysTime;
6
  volatile uint32_t usTimerCounter = 1000 - ((SysTick->VAL)&0x0FFFF) / 48 ;
7
  //volatile uint32_t usTimerCount = Chip_TIMER_ReadCount(SYSTIMER_MICROSECONDS);
8
  // 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.

von Nop (Gast)


Lesenswert?

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.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

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.

von Lutz (Gast)


Lesenswert?

DWT_CYCCNT gibt es leider erst ab M3; die M0 (auch M0+) haben den nicht.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

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.

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.