Hallo Forum,
ich habe eine Frage zum SysTick. Dieser ist etwas ungenau. Für eine
Delay-Funktion die ich für das Auslesen eines optischen Sensors
(ADNS3090) benötige habe ich folgende delay.c erstellt:
1
#include<delay.h>
2
3
static__IOuint32_tsysTick_zaehler;
4
5
voidSysTick_Init(void)
6
{
7
while(SysTick_Config(SystemCoreClock/1000000)!=0)
8
{
9
}
10
}
11
12
voidtimer_Decrement(void)
13
{
14
if(sysTick_zaehler!=0x00)
15
{
16
sysTick_zaehler--;
17
}
18
}
19
20
voiddelay_ns(uint32_tn)
21
{
22
sysTick_zaehler=n;
23
while(sysTick_zaehler!=0)
24
{
25
}
26
}
27
28
voiddelay_ms(void)
29
{
30
sysTick_zaehler=1000;
31
while(sysTick_zaehler!=0)
32
{
33
}
34
}
35
36
voiddelay_nms(uint32_tn)
37
{
38
while(n--)
39
{
40
delay_ms();
41
}
42
}
und natürlich die delay.h
1
#ifndef DELAY_H
2
#define DELAY_H
3
4
#include<stm32f4xx.h>
5
6
externvoidSysTick_Init(void);
7
externvoidtimer_Decrement(void);
8
externvoiddelay_ns(uint32_tn);
9
externvoiddelay_ms(void);
10
externvoiddelay_nms(uint32_tn);
11
#endif
Nun habe ich eine Funktion geschrieben welche meine "DEBUG"-LED zum
blinken bringt.
1
voidRun_Idle(void)
2
{
3
SysTick_Init();
4
5
delay_nms(1000);
6
GPIOA->BSRRL|=(1<<YELLOW_LED);
7
delay_nms(1000);
8
GPIOA->BSRRH|=(1<<YELLOW_LED);
9
}
Dann natürlich noch die Pin-Konfigurationen zusammengefasst:
1
voidstartUp(void)
2
{
3
SystemInit();
4
SystemCoreClockUpdate();
5
RCC_config();
6
GPIO_config();
7
}
Setze ich jetzt delay(1000) ein, so messe ich mit dem Oscar 988 ms. Ist
das normal das dieser etwas daneben liegt?
vielen lieben Dank und Gruß
Daniel
PS: µC läuft auf 80 MHz mit einem extern Quarz. PLL wurden eingestellt.
Servus,
wo ist dein void SysTick_Handler(void); ?
Sonst musst du ein wenig Kopfrechnen. Dein µC verweilt im Interrupt!
Normalerweise setzt man den systick takt auf 1ms. Um eine delay Zeit von
µs zu erreichen kann man den Counter "SysTick->VAL" zur Hilfe nehmen.
Und wie sicher sind sie dat den F4 auch mit den externen Quarz lauft ?
System_init() alles richtig einestellt ? Den F4 start immer mit den
interne osc, nur wen die software richtig ist und das signal von
externen quarz ist forhanden, schaltet er nach externen quarz.
holger schrieb:> Vieleicht ist dein Oscar auch etwas ungenau;)
Hmmm, doch so eine Messungenauigkeit des Rigols? Okay, morgen auf der
Arbeit mal an einem Agilent hängen ;)
hp-freund schrieb:> Wie immer mein Tip im Falle von Taktproblemen:>> MCO benutzen
Da liegen 80 MHz an. Den SysTick_Handler hatte ich vergessen zu posten,
ist aber drin. Mittlerweile hängt an den Port mein ADNS3090.
PS. Leider hängt mein Sensor dran messe aber um die 80 MHz, bricht aber
immer wieder ein. (vermute mal durch den Sensor)
An anderer Stelle hatte ich den SPI-Takt eingestellt, ausgehend von 80
MHz. Dort hat alles gepasst.
Danke und Gruß
Daniel
Wenn die Zeit zu kurz ist muss die Frequenz irgendwie zu hoch sein oder
es gibt für die Zeitzählung zusätzliche Impulse.
Ist mir jetzt aber zu schwierig das nach zu vollziehen.
Parallel zu Timer-Decrement toggle eine LED.
Wenn Du dann 500kHz misst, ist Dein Timer richtig aufgesetzt.
Moment mal: Du hast einen Interrupt pro µs? Vermutlich hast einen Teiler
von 80 und ein off-by-one-Problem. Brauchst Du diese hohe Auflösung?
Normalerweise incrementiert man den Systicker immer und wartet so:
[c]
typedef uint32_t TTick;
TTick SysTicker;
interrupt Timerx(void)
{
SysTicker++;
}
void delay_ns(unsigned int n)
{
TTick startTick = SysTicker;
while((SysTicker-startTick) <n) {};
}
Der Vorteil: Du kannst in verschiedenen Tasks oder in verschiedenen
Programmteilen (dann if statt while) parallel mit einem Systicker
warten. Oder Zeiten messen. Wichtig ist nur, dass Du immer
(SysTicker-Startwert) geklammert rechnest und dann vergleichst etc.
Meist braucht man für den Systicker-Increment nichtmal ne
Interrupt-Routine.
Achim S. schrieb:> while((SysTicker-startTick) <n) {};
wohl eher nicht. Den meisten fällt sowas eher nicht auf, aber auch ein
32 Bit Zähler läuft mal über. Deshalb eher so:
unsigned long L;
L = Tick;
while (n)
{ if (L!= Tick)
{ L = Tick;
n--;
}
};
Daniel V. schrieb:> ich habe eine Frage zum SysTick. Dieser ist etwas ungenau.
Nein. Stattdessen sind deine geposteten Funktionen grauenhaft. Laß den
eigentlichen SysTick-Counter in Ruhe, schreib dir eine Variable 'Ticks',
die du im SysTick-Handler hochzählst und die die verflossene Zeit in ms
anzeigt. Aber die darfst du nur lesen und nirgendwo anders schreiben als
im SysTick-Handler. Obendrein sollte der Handler 'Ticks' bei 86400000
zurücksetzen und ggf. ne Variable 'Tag' inkrementieren.
Delay-Routinen müssen das natürlich berücksichtgen, indem sie NICHT
einfach if(Ticks>n) benutzen.
W.S.
W.S. schrieb:> wohl eher nicht. Den meisten fällt sowas eher nicht auf, aber auch ein> 32 Bit Zähler läuft mal über.
Mein Konstrukt ist sehr, sehr robust. n darf nur nicht zu groß werden.
Wenn die Zeile im worst case erst nach y Ticks aufgerufen wird, so darf
n nur maximal (MAX_VALUE - y) groß sein. Beispiel: Wenn der Systicker
8bit breit ist und millisekündlich inrementiert wird und die Zeile im
Worst Case erst nach 20ms erneut aufgerufen wird, so muss n<=234 sein.
Dein Konstrukt muss vollständig blockieren, ist also weder task- noch
interruptfest.
Falls Du darauf anspielst, dass der Systicker inkonsistent sein könnte
(also während eines lesens überläuft), das muss tatsächlich vermieden
werden, sollte aber zentral beim incrementieren passieren.
Der Tipp von STLer ist on-the-sport, genauer als mit dem Cycle Counter
geht's eh nicht (auf dem ST kannst Du alternativ auch free running timer
einsetzen und den jeweiligen count abfragen, aber eben Überlauf
beachten!)
Bei deinem Code musst Du auch noch Interruptprioritäten in Betracht
ziehen; läufst Du z.B. unter FreeRTOS, ist zu beachten, dass der SysTick
Interrupt immer auf der niedrigsten Interruptpriorität liegt, also von
höher priorisierten interrupts ausgebremst wird. Für das OS ist sein
Systemtask das Maß aller Dinge (also damit auch der SysTick counter),
aber wenn der in der "absoluten" Zeit off ist, kann das OS nichts
dagegen tun.
Immer das Gesamtsystem im Hinterkopf haben...
Heiß also, die Funktionen wegwerfen (sie habe ich mir geklaut, muss ich
gestehen, da ich Quick&Dirty-mäßig eine Lösung brauchte und ich erst
gerade mich einarbeite in die ARM-Kiste) und W.S. seine Funktionen
implementieren und ggf noch umrechnen auf ms.
Hier habe ich "meine" Lösung her:
http://patrickleyman.be/blog/stm32f407-delay-with-systick/
Gruß
Daniel
Daniel V. schrieb:> Heiß also, die Funktionen wegwerfen...
Nun, du hättest dir von vornherein ein anderes Konzept zurechtlegen
können. Ich hab in eigentlich jeder Firmware eine Systemuhr drin, die
auf dem SystemTick basiert. Dazu arbeite ich auch gern mit Events und
delayed events. Geht einfach, wenn man mal das simple Prinzip versteht:
1. ein Event wird dargestellt durch eine Zahl, je nach Plattform und
Bedarf 8, 16 oder 32 bittig. Beispiel: #define isSekundeUm 47110815
Natürlich kann man auch was Anderes nehmen, z.B. nen kleineren struct
oder was man will.
2. es gibt einen Event-Puffer, der regelmäßig von der Grundschleife in
mail abgefragt wird:
if (EventAvailable() DispatchEvent(GetEvent());
3. Events können von diversen Firmwareteilen in den Puffer eingestellt
werden zwecks späterer Bearbeitung
4. Die Systemuhr schickt z.B. regelmäßig einen Event "isSekundeUm" o.ä.
in den Puffer. Obendrein führt die Event-Verwaltung eine zweite kleine
Liste für verzögerte Events - und diese Liste wird von der Systemuhr
regelmäßig abgeklappert nach abgelaufenen Events. Findet sie einen, dann
schmeißt sie selbigen aus der Liste der verzögerten Events raus und
packt ihn in den normalen Event-Puffer. Dazu gibt es noch ne Funktion
zum Löschen von solchen verzögerten Events, falls man so einen nicht
mehr braucht.
So kann man Zeitüberwachungen und nicht blockierende Warteschleifen
bauen indem man an recht beliebiger Stelle etwa sowas schreiben kann:
AddDelayedEvent(dieZeitIstUm, 1500); // 1.5 Sekunden
und wenn man die Sache erledigt hat, die Zeit noch nicht um ist oder der
Event noch nicht von der Grundschleife gelesen worden ist, schmeißt man
ihn wieder raus mit
KillDelayedEvent(dieZeitIstUm);
ansonsten landet der Event nach 1500 ms im Event-Puffer und wird von der
Grundschleife gelesen und den zuständigen Programmteilen zur Bearbeitung
übergeholfen.
Das Ganze ist recht aufwandsarm und kostet auch kaum Rechenzeit und man
kann - während man auf irgendwas wartet - noch andere Dinge erledigen.
Prinzip verstanden?
W.S.
W.S. schrieb:> in> mail
in main und nicht in mail! Warum muß so eine bescheuerte
Rechtschreibautomatik einem immer wieder sowas einbrocken? Steht die auf
Sanskrit oder was?
W.S.
W.S. schrieb:> Prinzip verstanden?
Hi W.S.,
jetzt gerade fliegt mir diese Geschichte um die Ohren, da ich die
Bilddaten meines ADNS3090 nur tröpfchenweise über die USART übertragen
werden (bei 256000 Baud), da wohl die Timer immer dazwischenschlagen und
ich warten muss.
Kannst Du mir zum Nachvollziehen etwas Beispielcode zeigen?
Wenn ich zuhause bin, zeige ich mal meinen derzeitigen Code.
Danke und Gruß
Daniel
W.S. schrieb:> Das angehängte Zeugs sollte das Prinzip klar genug machen.
Hallo W.S. eine Frage: Warum machst Du den Aufwand mit Millisekunden pro
Tag? Das ist sehr fehlerträchtig und führt bei Dir m.E. zum Problem,
wenn ein Timer 1ms vor dem Tagwechsel abläuft. Da das ganze ja auch
nicht auf reale Tage abgeglichen wird, erschließst sich mir der Aufwand
garnicht. Maximal würde ich die Tagesszähler einfach so mitlaufen
lassen.
Dass man Überlauf durch Klammerung generell behandeln kann und nicht
gesondert, habe ich ja hoffentlich gezeigt.
Achim S. schrieb:> Hallo W.S. eine Frage: Warum machst Du den Aufwand mit Millisekunden pro> Tag? Das ist sehr fehlerträchtig
Erstens ist es auf einem 70..100 MHz Cortex so ziemlich das
Natürlichste, die Tageszeit in Millisekunden zu bemessen und zur
Mitternacht das Ganze um eine Tageslänge zurückzusetzen. Ist dir
eigentlich aufgefallen, daß Ticks signed ist?
Mir ist bei dieser Gelegenheit noch ein Bug aufgefallen:
struct TTimerEvent
{ dword endzeit;
dword aEvent;
};
Man sollte die 'endzeit' ebenfalls signed deklarieren. Bitte beachten.
Zweitens ist das Verfahren durchaus nicht fehlerträchtig. Wie kommst du
auf so eine Aussage? Deine Vermutung "wenn ein Timer 1ms vor dem
Tagwechsel abläuft" verstehe ich nicht, denn der Systick-Timer läuft
genau 1x pro Millisekunde über, also quasi ständig. Von welchem
(anderen) Timer redest du?
Ich habe den Eindruck, daß hier die von Achtbittern her übliche
Denkweise zuschlägt, wo man für alle möglichen Zwecke mal eben die
Interrupts verbietet und gelegentlich damit sich selbst durcheinander
bringt oder sich eine Ewigkeit in irgend einer ISR aufhält und damit
alles Andere blockiert. Sowas sollte man generell besser bleibenlassen.
W.S.
Hallo W.S.:
Du irritierst mich.
W.S. schrieb:> Erstens ist es auf einem 70..100 MHz Cortex so ziemlich das> Natürlichste, die Tageszeit in Millisekunden zu bemessen und zur> Mitternacht das Ganze um eine Tageslänge zurückzusetzen.
nein. Das natürlichste für echte Timer ist ein Free-running-Counter,
weil der die wenigsten Probleme verursacht, vor allem *nicht beim
Überlauf*.
a) ich habe übersehen, wo Dein Ticker auf die Uhrzeit abgeglichen wird.
b) ich habe übersehen, wo und wie Sommer-Winter-Zeit und
Uhrzeitverstellungen gehandhabt werden
> Ist dir eigentlich aufgefallen, daß Ticks signed ist?
nein. Das spielte auch keine Rolle, da endzeit unsigned war und deshalb
Deine Routine den beschriebenen Bug enthält.
> Man sollte die 'endzeit' ebenfalls signed deklarieren. Bitte beachten.
Ja. Damit träte der von mir beschriebene Fehler nicht mehr auf.
Ich verstehe aber nicht, wieso Du dann all die Argumente aufzählst, die
gegen Deine Implementierung sprechen
> Ich habe den Eindruck, daß hier die von Achtbittern her übliche> Denkweise zuschlägt, wo man für alle möglichen Zwecke mal eben die> Interrupts verbietet und gelegentlich damit sich selbst durcheinander> bringt oder sich eine Ewigkeit in irgend einer ISR aufhält und damit> alles Andere blockiert. Sowas sollte man generell besser bleibenlassen.
Genau das machst Du nämlich. Deine Interruptroutine ist zu lang. So
lange der Free-Running-Counter konsistent gelesen wird, gäbe es keine
Gründe, irgendwo einen Interrupt zu sperren und all die Auswertung
könnte dezentral dort geschehen, wo sie notwendig ist. Es entfiele die
"Gott-Timer-Struktur".
Wobei ich Deinen "Achtbitter" nicht verstanden habe: Wenn man Timer und
Überläufe mit 8-Bit-Werten einmal verstanden hat, dann kann man das
robust auf 32-bit skalieren. Wenn man (wie Du) alles mit 32-bit macht,
wo die Probleme beim Debuggen niemals auftreten, dann jagst Du später
"glaubhaft versicherten" Geisterfunktionen hinterher, die irgendwie nur
3 Mal im Jahr auftreten.
W.S. schrieb:> Achim S. schrieb:>> while((SysTicker-startTick) <n) {};>> wohl eher nicht. Den meisten fällt sowas eher nicht auf, aber auch ein> 32 Bit Zähler läuft mal über. Deshalb eher so:>> unsigned long L;> L = Tick;> while (n)> { if (L!= Tick)> { L = Tick;> n--;> }> };
Das ist ja furchtbar unsicher (Stichwort: L==Tick verpassen).
while((SysTicker-startTick) <n)
funktioniert durchaus auch bei Überläufen, wenn SysTicker und startTick
unsigned sind:
Joe F. schrieb:> while((SysTicker-startTick) <n)
auf Dein vollkommen blockierendes Wait möchte ich nicht eingehen. Mit
Free-Running-Counter könntest Du jedezeit noch weitere Aufgaben
ausführen oder wärest Task/Interruptfest.
if(CurrTimers[i].endzeit<Ticks)/* ob abgelaufen */
13
...
14
}
Ich hoffe Du siehtst, dass der Code mit unsigned endzeit nicht
funktiert, wenn die Endzeit 1Tag - 1ms (bzw. bei Dir -1..-9ms) ist. Das
meine ich mit fragil.
Achim S. schrieb:> Joe F. schrieb:>> while((SysTicker-startTick) <n)>> auf Dein vollkommen blockierendes Wait möchte ich nicht eingehen.
whaaat?
Das kam ja nicht von mir.
Ich habe lediglich darauf hingewiesen, dass es hier kein Problem mit
einem Überlauf gibt (was übrigens eine durchaus verbreitete Meinung
ist).
W.S. schrieb:> unsigned long L;> L = Tick;> while (n)> { if (L!= Tick)> { L = Tick;> n--;> }> };
ist ebenfalls blockierend, und eben kritisch, da L==Tick durchaus
verpasst werden kann.
Und was soll ich denn jetzt mit deinem Codeschnippsel anfangen?
Achim S. schrieb:> void __irq SysTick_Handler (void)> {> ++Ticks;> ...> if (Ticks >= 86400000) /* wenn 1 Tag um ist */> {> ...> CurrTimers[i].endzeit = CurrTimers[i].endzeit - 86400000;> ...> }> ...> if (CurrTimers[i].endzeit < Ticks) /* ob abgelaufen */> ...> }
1 Tag 86400000 Ticks? Was hast du denn für eine 1KHz Gurke?
"CurrTimers[i].endzeit = CurrTimers[i].endzeit - 86400000"
Hä?
Also ich habe auch ein Hefe intus, aber das verstehe ich beim besten
Willen nicht.
endzeit = gestern?
Ich bitte um Erläuterung.
Achim S. schrieb:> ++Ticks;> ...> if (Ticks >= 86400000) /* wenn 1 Tag um ist */
Das ist doch schon vollkommen balla.
1. setzt es voraus, dass der Counter bei 0 startet.
2. Und was ist dann, wenn die Bedingung erfüllt ist? Setzt du irgendwo
Ticks auf 0? Nö. Also ist die Bedingung jede ms erfüllt, bis der 32-bit
Counter überläuft (48.7 Tage lang).
Und mit so einem Käse soll ich mich jetzt beschäftigen?
Du kannst ja statt
Achim S. schrieb:> while((SysTicker-startTick) <n)
auch
if (! (SysTicker-startTick) <n)
{
// play with your balls
}
schreiben, wenn dich das glücklich macht.
Joe F. schrieb:> Das kam ja nicht von mir
sorry, das habe ich übersehen. Deine Version
> while((SysTicker-startTick) <n)
ist ja genau, was ich W.S. empfehle. Sein Warte-Code ist ...
suboptimal.
Das Überlaufproblem ergibt sich bei ihm aber beim Zurücksetzen des
Tageszählers, darum habe ich nur die problematischen Zeilen bzw. dessen
reihenfolge zitiert. Passiert zwar nur selten aber ist halt der
unterschied zwischen robustem Code und Bastelei. Und dazu völlig
unnötig, da der direkte Ansatz (wie bei Dir) viel einfacher ist.
ROTFL!
Also Jungs (Achim und Joe), ihr habt weder das Prinzip der Sache
verstanden noch das Prinzip des SysTick's kapiert. Guckt einfach mal in
die Doku von ARM zum Cortex, vielleicht kapiert ihr es dann.
Und nun ratet mal, warum um Mitternacht
"CurrTimers[i].endzeit = CurrTimers[i].endzeit - 86400000;"
gemacht wird...
Und wieso kommt Achim auf "Deine Interruptroutine ist zu lang."? Ich
hatte die mal gemessen und lag je nach Sensorentprellung bei ca. 5..7
Mikrosekunden. Mir ist das schnell genug, kostet weniger als 1% der
Rechenzeit.
Aber ich räume durchaus ein, daß bei einer völlig anderen Hardware auch
völlig anderer Zeitbedarf herauskommen kann.
Jungs, ich sag's mal so:
1. Dem TO ist geholfen, denn er hat nen Denkanstoß bekommen, den er
produktiv nutzen kann.
2. Wenn ihr es trotz (hoffentlich gründlichen) Lesens immer noch nicht
kapiert, dann kann auch ich euch nicht weiter helfen.
So. und nun ist Schluß damit. Lest und begreift oder laßt es bleiben.
W.S.
W.S. schrieb:> dann kann auch ich euch nicht weiter helfen
Das macht nichts.
Du hast das tollste, lesbarste und fehlerfreieste Stück Software in der
Geschichte der Event-Verarbeitung geschrieben, und wir dumme Jungs
werden halt weiterhin ein einfaches Delay durch Differenzbildung von 2
unsigned Werten erledigen.
Joe F. schrieb:> Achim S. schrieb:>> while((SysTicker-startTick) <n)>> auch>> if (! (SysTicker-startTick) <n)> {> // play with your balls> }>> schreiben, wenn dich das glücklich macht.
Bei der Subtraktion von unsigned Zahlen wird das Zweier-Kompliment durch
geführt, sodass man auch schreiben kann:
1
startTick=~SysTicker+1;
2
while((SysTicker+startTick)<n);
Je nachdem wie der Kompiler reagiert wäre ein cast sinvoll:
aSma>> schrieb:> Bei der Subtraktion von unsigned Zahlen wird das Zweier-Kompliment durch> geführt, sodass man auch schreiben kann:startTick = ~SysTicker +1;> while((SysTicker + startTick) <n);
Das mag korrekt sein, aber wozu?
Auf das grundsätzliche signed/unsigned - Problem mit *undefiniertem
Verhalten* (nicht einfach nur implementation defined) bei Überlauf des
einen Typen, möchte ich nicht eingehen.
Ruediger A. schrieb:> dass der SysTick> Interrupt immer auf der niedrigsten Interruptpriorität liegt, also von> höher priorisierten interrupts ausgebremst wird.
Spezis - bitte weghören :-P Ich hab das Ding auch mal auf höchste
Priorität gedreht um (aaarghh) Delays auch in ISR aufrufen zu können
(war für ein Projekt mit Elektromagneten zum testen). Geht also auch...
Das ist übrigens auch gleichzeitig eine Falle. Wer die Priorität des
Systick niedrig lässt und delay() in höher priorisierten ISR aufruft,
fängt sich einen eingefrorenen MC ein.
Wie ist denn das überhaupt mit dem Systick, hab dazu im reference manual
nichts gefunden:
Also das Counter-Register lädt sich den Wert aus dem Reload-Register und
zählt mit jedem Takt abwärts, je nach Einstellung zählt als Clock dann
entweder der CPU-Takt oder CPU-Takt geteilt durch 8.
Wenn der Counter 0 erreicht und man den Interrupt aktiviert hat, wird
der Interrupt ausgelöst.
An welchem Punkt genau macht das Counter-Register jetzt seinen Reload?
Sinnvoll (und das würde ich auch vermuten) wäre ja direkt nach dem
Erreichen der 0. Oder macht er das erst, wenn der Interrupt beendet ist?
Achim S. schrieb:> Das mag korrekt sein, aber wozu?
Für die Unwissenden.
> while((SysTicker-startTick) <n);
Ist ein gutes Konstrukt gegen ein owerflow.
Was hier nicht in die Diskussion eingegangen ist wie man ein delay_us
entwirft.
Der TE hat wohl nicht verstanden, dass
> while (SysTick_Config(SystemCoreClock/1000000) != 0)
seinen µC fast vollständig auslastet.
Ich habe weiter oben zu Anfang schon einen Ansatz genannt.
aSma>> schrieb:> Der TE hat wohl nicht verstanden, dass>> while (SysTick_Config(SystemCoreClock/1000000) != 0)>> seinen µC fast vollständig auslastet.
Allerdings. Für ms ist
1
/* Setup SysTick Timer for 1 msec interrupts */
2
if(SysTick_Config(SystemCoreClock/1000))
3
{
4
/* Capture error */
5
while(1);
6
}
oder so ähnlich deutlich sinnvoller und lässt den anderen Jobs etwas
Luft.
Kleinere delays könnte man mit einem Taktzyklen Ticker machen, wie das
in CMSIS vorgeschlagen wird:
Matthias S. schrieb:> Kleinere delays könnte man mit einem Taktzyklen Ticker machen
Das macht man doch mit dem DWT, dann braucht man keine Taktzyklen zu
zählen, weil der DWT genau das von sich aus schon tut. Muß man halt nur
noch wissen, wie lang ein Takt denn ist, um n Takte abzuwarten. Für
µs-delays aber eine elegante Lösung. Außerdem unabhängig von der
Optimierungsstufe.
Kommt natürlich an seine Grenzen, wenn man extrem weit runtergeht, weil
der Overhead der Schleife dann die Genauigkeit wegreißt. Und außerdem
muß das natürlich inlined werden oder gleich als Makro definiert werden.
Ach ja, und die "Lösung" aus der CMSIS hat den gewaltigen Nachteil, daß
ein optimierender Compiler die komplette Funktion wegoptimieren kann,
weil klar ist, daß sie keine Seiteneffekte hat, keinen Returnwert, und
immer bis 0 zählt.
Wenn man sowas schon macht, dann geht das nur mit einer
volatile-Variablen.
Nop schrieb:> Wie ist denn das überhaupt mit dem Systick, hab dazu im reference manual> nichts gefunden
Für den Fall, daß du hier von einem Cortex sprichst, wirst du die Doku
auch nicht bei den Chip-Herstellern finden, sondern in der Doku von ARM
zum jeweiligen Cortex. Der SysTick-Zähler ist nämlich quasi in der CPU
enthalten. Man lädt ihn mit einem Wert der einem genehm ist (z.B.
Systemfrequenz/1000) und schaltet ihn ein. Das war's. Ab da faßt man ihn
nie wieder an. Er liefert nun zyklisch seinen Interrupt (den man
natürlich im NVIC passend verschalten muß) was ja das Einzige ist, was
man bezwecken will. Der Interrupthandler ist nun quasi der Kern der
Systemuhr, der Stunden und Tage zählen oder weiß der Geier was tun kann
oder bei einem RTOS den Scheduler anstoßen kann und und und.
W.S.
W.S. schrieb:> Für den Fall, daß du hier von einem Cortex sprichst, wirst du die Doku> auch nicht bei den Chip-Herstellern finden, sondern in der Doku von ARM> zum jeweiligen Cortex. Der SysTick-Zähler ist nämlich quasi in der CPU> enthalten. Man lädt ihn mit einem Wert der einem genehm ist (z.B.> Systemfrequenz/1000) und schaltet ihn ein. Das war's. Ab da faßt man ihn> nie wieder an.
Unsinn. Bei ST steht es zum Beispiel im PM0214 Programming Manual
(Cortex M4) Ausgabe September 2012 auf Seite 228.
> Er liefert nun zyklisch seinen Interrupt (den man> natürlich im NVIC passend verschalten muß)
Nochmal Unsinn. Da muss man gar nichts "verschalten". Nach dem
Einschalten per TICKINT Bit ist er aktiv, am NVIC muss man nix mehr tun,
allenfalls die Priorität kann man ändern.
W.S. schrieb:> Man lädt ihn mit einem Wert der einem genehm ist (z.B.> Systemfrequenz/1000) und schaltet ihn ein. Das war's. Ab da faßt man ihn> nie wieder an. Er liefert nun zyklisch seinen Interrupt
Ja wie man ihn benutzt, weiß ich, das ist auch mit ein paar Registern
und der Vektortabelle einfach genug. Allerdings wird der nicht über den
NVIC verschaltet, weil der Systick genaugenommen eine Exception und kein
Interrupt ist, sondern die Prio stellt man in einem der SCB-Register
ein. Konkret bei F4 geht das in SCB_SHPR3.
Nee, meine Frage zielte mehr darauf ab, ob die Dauer der
Interruptroutine die Präzision der Zeitgebung beeinflußt oder nicht.
Wenn der Reload direkt beim assert schon passiert, dann ist es ja egal,
wie lange die Routine dauert - sie muß halt vor allem fertig sein, bevor
das nächste mal null erreicht wird, als obere Schranke.
So, ich habe mal W.S. sein Konzept mal durchgearbeitet und ich muss
sagen... ich verstehe nur Bahnhof. Zu viele Magic Numbers usw.
Wie implementiere ich einen ressourcenschonenden SW - Timer, welcher mir
erlaubt, die SPI-Timings eines ADNS3090 einzuhalten und gleichzeitig die
Daten, welche auf der MISO liegen auf die USART zu legen?
Die o.a. Lösung funktioniert leider nicht, da der Controller damit
beschäftigt ist, die Interrups auszulösen.
Danke euch und Gruß
Daniel
Nop schrieb:> Nee, meine Frage zielte mehr darauf ab, ob die Dauer der> Interruptroutine die Präzision der Zeitgebung beeinflußt oder nicht.
Also:
1. der SysTick liefert einen zeitzylkischen Interrupt.
2. der Interrupt-Handler für diesen Interrupt sollte natürlich nicht so
lange trödeln, bis der nächste SysTick-interrupt ausgelöst wird. Aber
das sollte ja wohl ohnehin klar sein. Kurzum, solange die ISR und alle
anderen höherpriorisierten ISR's nicht länger dauern als die
SysTick-Periode, ist die Präzision der Zeitgebung gewahrt. Aber das
sollte ja ohnehin jedem klar sein, daß man in den Interrupts nicht mehr
Zeit vertrödeln KANN, als tatsächlich zur Verfügung steht.
Daniel V. schrieb:> So, ich habe mal W.S. sein Konzept mal durchgearbeitet und ich muss> sagen... ich verstehe nur Bahnhof.
Seltsam. Eigentlich ist das Konzept doch glasklar. Und wenn du Fragen
gehabt hättest, dann hättest du sie hier stellen können. Hast du denn
wenigstens das Beispiel Katzenklappe verstanden? Da gibt es 3 Zustände:
zu, auf und irgendwo mittendrin. Kann ja passieren, daß beim zu- oder
aufmachen irgendwas blockiert und dann die Klappe auf halb acht hängt.
W.S.
> Daniel V. schrieb:>> So, ich habe mal W.S. sein Konzept mal durchgearbeitet und ich muss>> sagen... ich verstehe nur Bahnhof.>> Seltsam. Eigentlich ist das Konzept doch glasklar. Und wenn du Fragen> gehabt hättest, dann hättest du sie hier stellen können. Hast du denn> wenigstens das Beispiel Katzenklappe verstanden? Da gibt es 3 Zustände:> zu, auf und irgendwo mittendrin. Kann ja passieren, daß beim zu- oder> aufmachen irgendwas blockiert und dann die Klappe auf halb acht hängt.>> W.S.
Das Konzept eines Zustandsautomaten, das habe ich schon verstanden nur
mit Deiner Implementierung tu ich mich etwas schwer :)
Das mit dem Nachfragen im diesem Forum finde ich etwas schwierig. Hab
schon öfters hier einstecken müssen, deshalb überlege ich zweimal ob ich
wirklich nachfragen soll.
Gruß
Daniel
PS: Hmmpf, ich gestehe, ich habe die Txt-Datei nicht gelesen :(, ich
nehme alles zurück. Dennoch harter Tobak
Daniel V. schrieb:> Dennoch harter Tobak
Nö.
Eigentlich nicht.
Also: erstens hat man eine Systemuhr. Die besteht im Wesentlichen aus
der ISR des SystemTick's.
Ich habe sie hier so geschrieben, daß sie alle 1 ms von der HW
aufgerufen wird, um was zeitkritisches zu erledigen. Der Rest geht in
der ISR nur alle 10 ms. Dennoch wird die aktuelle Uhrzeit im 1 ms Raster
gezählt. Units, die die richtige Tageszeit einstellen und einen Kalender
führen, hab ich hier weggelassen. Wer sowas braucht, kann es sich ja
leicht selber schreiben.
Natürlich kann man sich je nach konkreter Anwendung das Zeitintervall
des SysTicks auch anders auswählen. In vielen Fällen reicht alle 10 ms
völlig aus.
Das System, mit Events zu arbeiten, ist offenbar vielen Leuten hier
herzlich fremd. Da wird lieber wie per PAP gearbeitet, also blockierend
irgend ein Algorithmus vom PAP auf nen Quellcode übertragen. Das ist
einfach, aber eben auch ressourcenvergeudend. Wer schon mal eine
Anwendung für Windows geschrieben hat weiß hingegen, daß es anders geht,
nämlich über Events. Die Anwendung (oder der Kommunikations-Thread in
selbiger) tut normalerweise nix, sondern ist stillgelegt, bis ein Event
eintrifft, für den sich die Anwendung als zuständig erklärt hat. So kann
in der Zwischenzeit die Rechenleistung des PC für andere Zwecke
verwendet werden.
Genau so im Ansatz arbeite ich auf dem µC. Ein Sensor (oder etwas
Vergleichbares) arbeitet entweder per Interrupt oder er wird im SysTick
gepollt (sofern dies wirklich zwingend nötig ist).
Aber auf eine erkannte Änderung wird eben NICHT sofort reagiert, sondern
es wird stattdessen ein entsprechender Event erzeugt, der später dann in
der Grundschleife (also im User-Mode) ausgewertet wird.
Das hat viele Vorteile:
1. Man hat eine gute Entkopplung zwischen verschiedenen Ebenen in seiner
Firmware. Insbesondere zwischen den Low-Level-Treibern, die sich mit der
HW herumplagen müssen und den höheren Ebenen, die sich um
aufgabenorientierte Dinge kümmern sollen.
Beispiel Katzenklappe:
Wie der Motor funktioniert, ob dafür ein Portpin benötigt wird oder was
Anderes, ist sache des LowLevel-Treibers. Die algorithmische Ebene (if
Uhrzeit = nach Feierabend then ÖffneKatzenklappe();) soll ganz dediziert
sich nicht drum scheren müssen, WIE die Katzenklappe geöffnet oder
geschlossen wird. Ebenso SOLL es ihr wurscht sein, wie ein anderer
LL-Treiber (der für die Kontakte an der Klappe) es hinbekommt,
mitzuteilen, ob die Klappe nun zu oder auf oder mittendrin verreckt ist.
Sowas wird mit Events gemacht und entkoppelt damit Algo-Ebene,
Motortreiber und Sensortreiber.
2. Man braucht nicht mehr blockierend zu schreiben und sich dann
zähneknirschend zu fragen, wie man zwischendurch noch ein paar andere
nötige Dinge erledigt.
3. Wenn man - wie ich - die in events.h deklarierten Events in sinnvolle
Gruppen einteilt, dann braucht man nicht jeden Event überall hin zu
schicken, sondern nur an die Firmwareteile, die dafür in Frage kommen.
Ein Event aus der Menü- oder Eingabetasten-Gruppe wird sicherlich NUR
für die Menüführung interessant sein und nicht für den Algo, der das
Verhalten der Katzenklappe steuert. Und wenn Ein Event nicht sowas wie
broadcast ist, dann braucht sich auch keine Instanz in der Firmware mehr
drum zu kümmern, sobald eine Instanz ihn verarbeitet und gelöscht hat.
Nun und schlußendlich bleibt die Grundschleife in main.c angenehm
überschaubar.
W.S.
Also, eins vorweg, Deine Lösung mit der parallele Abarbeitung finde ich
sehr charmant und bei einer LabView-Schulung vor ein paar Jahren, wurde
uns auch das Prinzip des endlichen Zustandsautomaten eingetrichtert um
paralleles abarbeiten zu ermöglichen.
Ich bin zwar in C mehr oder weniger noch Anfänger (lerne es gerade im
Unternehmen und auch in meinem Masterstudium an der Fernuni, wobei ich
gerade meine Masterarbeit zum Thema Optical Flow schreibe (hier setzte
ich den Code ein)), aber ich höre sehr viel von den alten
Entwicklerhasen, dass man dies genau so machen soll um robusten Code zu
erzeugen.
Mein nächster Arbeitsschritt ist es, den Code auf meine Kiste zu
implementieren und mit meinen Sensoren (ein BMA020, ein ADNS3090 und
eine USART zur Datenübertragung) anzupassen. Ziel ist es, die Bilddaten
des ADNS3090, die Beschleunigungsdaten des BMA020 an einem
Matlabprogramm zu übergeben. Hier entwickle ich gerade mit der
Imageprocessing-Toolbox einen Algorithmus zu entwickeln.
Danke und Gruß
Daniel
Daniel V. schrieb:> Wie implementiere ich einen ressourcenschonenden SW - Timer, welcher mir> erlaubt, die SPI-Timings eines ADNS3090 einzuhalten und gleichzeitig die> Daten, welche auf der MISO liegen auf die USART zu legen?
Der Begriff Timer wird vielfältig verwendet ;-)
SPI und USART werden normalerweise im dazugehörigen Interrupt behandelt.
Zudem gibt es eine zyklische Interruptroutine, im einfachsten Fall für
SysTicker++, meist in der Größenordnung 1ms.
Mit RTOS wird hier auch der Sceduler angeworfen.
Will man keine Interrupts für USART und SPI, dann verarbeitet man diese
in der SysTicker-Interruptroutine. Beim Senden und beim SPI kein
Problem, da die Daten dann einfach entsprechend länger brauchen. Will
man auch USART-Empfangen, so darf die maximale Baudrate bei
1ms-Zeitraster, 1 Byte Fifo und Parity + 2 Stoppbits nur maximal 9600
Baud betragen.
Die Auf "SysTicker" basierenden Timer realisiert man zweckmäßig als
brotlose Timer, die im Rahmen einer Loop abgefragt werden
"(SysTicker-Timer.Endwert)<0?". Hier werden aber (je nach
Arbeitsaufkommen) nur Zeiten ab mehreren SysTicker-Incrementen
verarbeitet. Je näher der Zyklus an 1 SysTicker liegt, umso eher muss es
in den Interrupt).
Okay, als "Quick&Dirty" - Lösung habe ich folgenden Code implementiert:
delay.c
1
#include<delay.h>
2
3
extern__IOuint32_tTimmingDelay;
4
5
voidSysTick_Init(void)
6
{
7
while(SysTick_Config(SystemCoreClock/1000));
8
}
9
10
voiddelay_ms(__IOuint32_ttime)
11
{
12
TimmingDelay=time;
13
while(TimmingDelay!=0);
14
}
delay.h
1
#ifndef DELAY_H
2
#define DELAY_H
3
4
#include<stm32f4xx.h>
5
6
externvoidTimingDelay(__IOuint32_ttime);
7
externvoidSysTick_Init(void);
8
externvoiddelay_ms(__IOuint32_ttime);
9
10
//extern void timer_Decrement(void);
11
//extern void delay_ns(uint32_t n);
12
//extern void delay_ms(void);
13
//extern void delay_nms(uint32_t n);
14
#endif
In der main toggle ich die "Debug-LED" (Duty cycle 0,5 bei einer
Impulsdauer von 75 ms). Der Controller läuft "smooth", vorher hat der
µC im Debugmodus gehakt und geklemmt. Mit dem Oscar hab ich schnell die
Timings nachgemessen, passt.
Nun, wie bekomme ich einen us-Timer hin? Denn hier lag das Problem.
Achim S. schrieb:> SPI und USART werden normalerweise im dazugehörigen Interrupt behandelt.
Verwende ich ebenfalls. Mit SPI-Timer meine ich eher die Timings die ich
einhalten muss, um den ADNS3090 richtig auszulesen. Dazu habe ich
ebenfalls hier im Forum den Thread erstellt:
Beitrag "STM32F4: Datenübertragung extrem langsam"
Diese eventgesteuerte Lösung von WS finde ich nach wie vor sehr
charmant, nur muss ich möglichst schnell ein paar Pflichtergebnisse
liefern und WS-Lösung fällt in der Kategorie "Kür".
Danke und Gruß
Daniel
@ Mods
Eigentlich könnten beide Threads zusammengefasst werden, da diese
Probleme an der amateurhaften Timerkonfiguration meinerseits entstanden
sind ;)
Ein INT alle 1us ist schon ne ziemliche Belastung für den Controller.
Ich würde einen extra Timer nehmen und den nebenher einfach laufen
lassen (1 Tick us oder 10 Ticks us) und dann in der Delay einfach
das Count-Register abfragen bzw. in der delay den Counter vorher sogar
zurücksetzen, um einen OVF zu vermeiden.
mfg
Daniel V. schrieb:> Mit SPI-Timer meine ich eher die Timings die ich> einhalten muss, um den ADNS3090 richtig auszulesen. Dazu habe ich> ebenfalls hier im Forum den Thread erstellt:
Wenn ich Deinen Thread dort richtig verstehe, ist doch Dein bisheriges
Problem, dass Deine Warte-Routinen einfach nicht das taten, was sie
sollten.
Wenn Du blockierend n Bytes über SPI senden oder empfangen willst,
dann schreibe einmal ein (oder 3) Warteroutinen, die funktionieren,
prüfe dieses und schreibe den Code runter. Mache blockierendes ns-Warten
mit NOPs, wenn Du keine Timer dafür aufsetzen kannst oder willst. Und
wenn er funktioniert, nimm ihn auch für µs.
Wenn Du absolutes Timing haben willst (z.B. LED im 0,5s Takt blinken,
trotz 200ms Kommunikation zwischendurch, oder alle 10ms ein Telegramm
starten) dann löse Dich von W.S.s klein-klein-überlauf-jein und mache
Dich mit dem Sinn eines free-running-counters (SysTicker) vertraut. Und
dass man diesen immer mit einer Differenz auswertet.
Daniel V. schrieb:>> SPI und USART werden normalerweise im dazugehörigen Interrupt behandelt.>> Verwende ich ebenfalls. Mit SPI-Timer meine ich eher die Timings die ich> einhalten muss, um den ADNS3090 richtig auszulesen. Dazu habe ich> ebenfalls hier im Forum den Thread erstellt:
Wenn Du n Bytes SPI überträgst, dann x µ/ns wartest und dann wieder n
Bytes SPI, dann macht Interrupt dabei keinen Sinn. Sende ein Byte,
warte bis es fertig ist und dann sende das nächste oder warte x µ/ns.
Ich denke, Du solltest einmal mit Pseudocode runterschreiben, was Du
wirklich parallel tun willst.
Achim S. schrieb:> Wenn Du n Bytes SPI überträgst, dann x µ/ns wartest und dann wieder n> Bytes SPI, dann macht Interrupt dabei keinen Sinn. Sende ein Byte,> warte bis es fertig ist und dann sende das nächste oder warte x µ/ns.>> Ich denke, Du solltest einmal mit Pseudocode runterschreiben, was Du> wirklich parallel tun willst.
Okay, hier mal der Pseudocode:
1
INITIALISIERUNGEN
2
GPIO;
3
RCC;
4
USART;
5
SPI;
6
BMA020;
7
ADNS3090;
8
9
/*Bis hier in alles okay*/
10
11
SCHREIBE/LESE DATEN VOM ADNS390
12
13
SCHREIBE AUF REGISTER 0x13 eine 0x80
14
WARTE (3 FRAMES + 10 us) /*1/2000 s = 0,5 us * 3 + 10 = 1510 us*/
15
LESE REGISTER 0x40 (Burst Mode)
16
WARTE(50us)
17
18
IST STARTBIT = 0x40
19
{
20
For (n=0;n<900;n++)
21
{
22
LESE WERTE AUS 0x40 /* 900 Werte */
23
WARTE (10us)
24
n++
25
IST n = 900
26
{
27
WARTE (14 us) /*Exit Burst Mode */
28
}
29
}
30
SONST WARTE BIS STARTBIT = 0x40
31
}
32
33
/*Funktioniert, die Daten liegen alle auf der MISO*/
34
35
SCHREIBE/LESE DATEN VOM BMA020
36
/* Funktioniert, die Daten liegen auf der MISO */
37
38
SCHREIBE DATEN AUF DIE USART
39
40
SENDE DATEN VOM ANDS3090
41
SEMDE DATEN VOM BMA020
Matlab übernimmt die Daten. Die Bilddaten werden benutzt um eine
30x30-Matrix zu füllen und die BMA020-Daten für die Berechnung der
Geschwindigkeit verwendet.
Danke und Gruß
Daniel