Ich habe einige Software auf einem STM32F103 (ich möchte auch F4 verwenden können), bei der ich Ereignisse (z besser).
Da die Software einige Zeit laufen wird, möchte ich einen 64-Bit-Timer verwenden, und für mich war es sinnvoll, den eingebauten SysTick-Timer zu verwenden. Ich habe SysTickMajor (einen 64-Bit-Zähler) erstellt, der jedes Mal erhöht wird, wenn SysTick überläuft, und eine Funktion getSystemTime, die diesen Zähler und den aktuellen Wert von SysTick kombiniert, um eine genaue 64-Bit-Zeit zu erhalten:
#define SYSTICK_RANGE 0x1000000 // 24 bit
volatile long long SysTickMajor = SYSTICK_RANGE;
voif onInit(void) {
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
SysTick_Config(SYSTICK_RANGE-1);
/* Super priority for SysTick - is done over absolutely everything. */
NVIC_InitStructure.NVIC_IRQChannel = SysTick_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void SysTick_Handler(void) {
SysTickMajor+=SYSTICK_RANGE;
}
long long getSystemTime() {
long long t1 = SysTickMajor;
long long time = (long long)SysTick->VAL;
long long t2 = SysTickMajor;
// times are different and systick has rolled over while reading
if (t1!=t2 && time > (SYSTICK_RANGE>>1))
return t2 - time;
return t1-time;
}
Das einzige Problem ist, dass dies nicht 100% genau zu sein scheint - zum Beispiel, wenn ich Folgendes mache:
bool toggle = false;
while (true) {
toggle = !toggle;
LED1.write(toggle);
long long t = getSystemTime()() + 1000000;
while (getSystemTime() < t);
}
Ich bekomme keine gute Rechteckwelle - gelegentlich kommt es zu Störungen (obwohl der SysTick-Interrupt sehr schnell beendet wird), weil die von getSystemTime zurückgegebene Zeit an einigen Stellen völlig falsch ist.
UPDATE: Wenn dies passiert (ich zeichne den Zeitwert bei einem Interrupt auf, der durch ein externes Ereignis ausgelöst wird), speichere ich die letzten 3 Werte auf seriell. Wiederholt erhalte ich Dinge wie:
>0x278-E2281A
0x278-E9999A
0x278-0001E5
>0x5BE-F11F51
0x5BE-F89038
0x5BE-0000FB
Ich habe Striche eingefügt, um darzustellen, welche Bits von SysTick stammen.
Um Zweifel zu vermeiden, habe ich den Code von starblue unten verwendet. Ich habe es auch geändert, um einen 32-Bit-Wert für SysTickMajor zu verwenden, und ich habe SysTick-> VAL mit AND verknüpft, nur für den Fall.
Es scheint, dass der SysTick_Handler den anderen Interrupt nicht vorwegnimmt!
Jetzt habe ich das überprüft und hatte vorher die Preemption-Prioritäten falsch, aber jetzt habe ich NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
die Interrupts eingerichtet und eingerichtet - SysTick-Preemption-Priorität 0 und der andere Interrupt ist 7.
Muss ich noch etwas tun, um die Präemption zu erhalten?
UPDATE2: Ich habe einen winzigen Testfall für genau dieses Problem erstellt (SysTick verhindert keine anderen Interrupts) und habe ihn hier abgelegt:
Probleme mit STM32-Interrupt-Priorität (Präemption).
Was ist also falsch an dem obigen Code? Gibt es einen besseren Weg, um einen guten hochauflösenden Timer zu bekommen? Es scheint etwas Offensichtliches zu sein, das die Leute tun möchten, aber ich habe Mühe, Beispiele zu finden.
Wie Sie festgestellt haben, ist die Handhabung des Zählerüberlaufs mit einem Interrupt-Handler schwierig und rennanfällig. Selbst mit Ihrer Prioritätserhöhung erhalten Sie die falsche Antwort, wenn Interrupts deaktiviert sind, während Sie den Zähler ablesen. Es gibt eine viel einfachere Technik, die funktioniert, wenn Sie die Uhr nicht aus dem Interrupt-Kontext lesen (beachten Sie, dass ich hier den DWT-Zykluszähler verwende, der so schnell und einfach in den Prozessor integriert ist):
uint64_t last_cycle_count_64 = 0;
// Call at least every 2^32 cycles (every 59.6 seconds @ 72 MHz).
// Do not call from interrupt context!
uint64_t GetCycleCount64() {
last_cycle_count_64 += DWT->CYCCNT - (uint32_t)(last_cycle_count_64);
return last_cycle_count_64;
}
Wenn Sie es aus dem Interrupt-Kontext aufrufen müssen (oder mit einem präemptiven Betriebssystem):
volatile uint64_t last_cycle_count_64 = 0;
// Call at least every 2^32 cycles (every 59.6 seconds @ 72 MHz).
uint64_t GetCycleCount64() {
uint32_t primask;
asm volatile ("mrs %0, PRIMASK" : "=r"(primask));
asm volatile ("cpsid i"); // Disable interrupts.
int64_t r = last_cycle_count_64;
r += DWT->CYCCNT - (uint32_t)(r);
last_cycle_count_64 = r;
asm volatile ("msr PRIMASK, %0" : : "r"(primask)); // Restore interrupts.
return r;
}
Möglicherweise müssen Sie zuerst den Zykluszähler aktivieren:
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; // Set bit 0.
Ihr Code hat zwei Probleme:
Sie wissen nicht, ob der Interrupt, der sich erhöht hat, SysTickMajor
vor oder nach dem Einlesen des Timer-Werts in aufgetreten ist time
.
Operationen auf 64-Bit-Werten sind auf einem 32-Bit-System nicht atomar. Der Wert für SysTickMajor
könnte also durch den Interrupt zwischen dem Lesen der beiden 32-Bit-Wörter aktualisiert werden.
Ich würde es wie folgt machen:
major0 = SysTickMajor;
minor1 = SysTick->VAL;
major1 = SysTickMajor;
minor2 = SysTick->VAL;
major2 = SysTickMajor;
if ( major0 == major1 )
{
time = major1 + minor1;
}
else
{
time = major2 + minor2;
}
Hier gehen Sie davon aus, dass sich SysTickMajor
eine Änderung im ersten Aufgabenblock im zweiten Block nicht mehr ändert. (Diese Annahme könnte falsch sein, wenn dieser Code während dieser Routine unterbrochen wird und längere Zeit nicht ausgeführt werden kann.)
Siehe auch diese Frage zu Stackoverflow .
SysTick->VAL
von begonnen hat . Der Wert könnte einen umgebrochenen Timer widerspiegeln, obwohl dies nicht der Fall wäre. Bei der Verwendung von Low/High/Low werden solche Dinge nicht passieren, es sei denn, der relative Zeitunterschied zwischen High- und Low-Word-Aktionen überschreitet die Zeit zwischen den beiden Low-Word-Lesevorgängen.
SysTickMajor
major1
minor1
major1
Ich habe gerade die Antwort von einem sehr hilfreichen Poster im STM32-Forum gefunden:
Folgendes ist nicht korrekt. SysTick ist ein 'System Handler', und als solcher wird die Priorität überhaupt nicht auf diese Weise festgelegt:
NVIC_InitStructure.NVIC_IRQChannel = SysTick_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // Highest priority
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
Es ist eigentlich eingestellt mit:
NVIC_SetPriority(SysTick_IRQn, 0);
Das Aufrufen dieses Codes löst das Problem stattdessen!
Meine Version von getSystemTime könnte also ein Problem gehabt haben, und die Methode von starblue ist besser als meine - die eigentliche Ursache der Probleme war jedoch die oben genannte.
Ich dachte nur, ich würde getSystemTime eine weitere mögliche Verbesserung hinzufügen, die ich auch ausprobiert habe:
do {
major0 = SysTickMajor;
minor = SysTick->VAL;
major1 = SysTickMajor;
while (major0 != major1);
return major0 - minor;
Dadurch wird das Problem, bei dem Sie (irgendwie) dazu führen könnten, dass der SysTick zweimal in sehr schneller Folge implementiert wird, effektiv negiert.
Die vorherigen Antworten sind großartig. Dieses hier gefällt mir am besten:
do {
major0 = SysTickMajor;
minor = SysTick->VAL;
major1 = SysTickMajor;
while (major0 != major1);
return major0 - minor;
Sie können es noch ein bisschen optimieren, da Sie wissen, dass Sie bei einem Rollover von SysTickMajor „lange Zeit“ keinen weiteren Rollover mehr bekommen werden. Sie sollten sicher sein, dies zu tun:
major = SysTickMajor;
minor = SysTick->VAL;
if (major != SysTickMajor)
return SysTickMajor - SysTick->Val;
return major - minor;
Das Obige setzt natürlich voraus, dass SysTickMajor und VAL als flüchtig deklariert sind. Vor einigen Jahren habe ich bei einem früheren Job festgestellt, dass das Obige leider immer noch nicht auf allen Prozessoren perfekt ist. Ich habe einen STM32F407 verwendet, der über einen Cache verfügt und über eine Pipeline verfügt. Manchmal kam der Abruf des zweiten SysTickMajor vor dem VAL an, obwohl sie in der richtigen Reihenfolge ausgegeben wurden. Geben Sie die Speicherbarriere ein (dmb-Anweisung):
major = SysTickMajor;
minor = SysTick->VAL;
__asm__ volatile("dmb");
if (major != SysTickMajor)
return SysTickMajor - SysTick->Val;
return major - minor;
In meinem Fall habe ich ein 32-Bit-TIM2 und ein TIM2-Rollover-ISR verwendet, das die oberen 32 Bits meines 64-Bit-Zykluszählers erhöht, aber ich denke, es sollte sich genauso verhalten wie das, was Sie mit SysTick tun.
Eddy_Em
Gordon Williams
Eddy_Em
while (getSystemTime() < t);
: Diese Funktion sehnt sich nach einiger Zeit, deshalb werden Sie nicht in der Lage sein, die Zeit perfekt zu bekommen. Arbeiten Sie an Interrupts oder führen Sie eine atomare Operation der Zeitberechnung durch (SysTick-Interrupt würde einelong long
Variable durch DMA oder einen anderen Mechanismus ändern, und zuletzt könnte Ihre Zeile so aussehenwhile(SystemTime - SystemTime0 < 1000000)
Gordon Williams