Beispielhaft habe ich den msTicks als char gewählt.
Wie löst man die Problematik am besten, wenn der (msTicks - curTicks)
durch einen Überlauf (255 + 1 = 0) niemals größer dlyTicks wird?
Bsp: curTicks = 250 und dlyTicks = 10...
Müsste man eine If-Abfrage in den SysTickHandler schreiben?
Gruß
Mit einem nur 8 bit großen msTicks wirst du schwerlich Delays von
32 bit Größe erreichen können.
Andersrum wäre es kein Problem, denn das Überlaufverhalten ist bei
vorzeichenloser Arithmetik definiert.
Der ARM v6 (Cortex M) Systick Zähler ist nach ARM ARM als 24-Bit Zähler
definiert, der abwärts zählt.
Normalerweise, rechne ich den Startwert anhand der gewünschten Periode.
Nach einem Systick Interrupt wird der Timer neu geladen/gestartet und
die gewünschte periodische Tasks bzw. Task Kette gestartet.
cfgardiner schrieb:> Der ARM v6 (Cortex M) Systick Zähler ist nach ARM ARM als 24-Bit Zähler> definiert, der abwärts zählt.
Das war doch gar nicht die Frage bzw. sein Problem: er hat den
SysTick (offenbar) so konfiguriert, dass er jede Millisekunde
überläuft. Im zugehörigen Interrupt zählt er dann die Millisekunden
und will abhängig davon ein Delay steuern. (Inwiefern ein
millisekundenlanges Delay auf einem ARM sinnvoll ist, darüber kann
man sich gewiss vortrefflich streiten …)
StackOverflower schrieb:> Wie löst man die Problematik am besten, wenn der (msTicks - curTicks)> durch einen Überlauf (255 + 1 = 0) niemals größer dlyTicks wird?
Indem man alle beteiligte Variablen (msTick, curTicks und ldyTicks) den
gleichen Typ gibt und diesen Typ groß genug wählt.
StackOverflower schrieb:> Wie löst man die Problematik am besten, wenn der (msTicks - curTicks)> durch einen Überlauf (255 + 1 = 0) niemals größer dlyTicks wird?
Wenn man so an die Sache herangeht wie du, dann kann man das Problem
eigentlich garnicht lösen. Die Möglichkeit des Überlaufes bringt das
eben mit sich.
Du mußt es eben anders machen.
Ich halte das bei einfachen Anwendungen so, daß ich als Zählvariable
eben ein dword nehme ("unsigned long"), welches alle 10 ms vom
Uhrtick-Handler inkrementiert wird und dreisterweise es drauf ankommen
lasse, daß wohl niemand die Kiste SOOO lange eingeschaltet läßt.
Bei "besseren" Anwendungen mache ich das anders: Der Uhrtick-Handler
führt eine Systemzeit in Millisekunden, diesmal in long (also mit
Vorzeichen) und ein Datum. Obendrein kennt er ein kleines Array von
"delayed Events", also Ereigniscodes, wo Routinen aus der
Anwendungsebene ein Ereignis ihrer Wahl nebst einer Uhrzeit eintragen
lassen können. Bei jedem Uhrtick sieht der Uhrtick-Handler diese Liste
durch und wenn er einen Eintrag findet, der abgelaufen ist, läßt er den
zugehörigen Ereigniscode in den Event-Puffer eintragen und löscht den
Eintrag in seiner Liste. Und damit sowas nicht überlaufen kann, wird um
Mitternacht sowohl die Uhrzeit um genau 1 Tag zurückgestellt als auch
alle etwaigen Einträge in der Liste um denselben Betrag rückgestellt.
Somit kommt es prinzipiell nicht zu einem Überlaufs-Fehler.
Ja, auch wenn jetzt wieder Leute mit den Augen rollen.. nachzulesen ist
das u.a. eben auch in den Quellen der Lernbetty. Ich hab euch ne Menge
nützlicher Dinge vorgeturnt und wer dort seine Nase reingesteckt hat,
braucht hier nicht händeringend nach Lösungen nachzufrragen.
W.S.
W.S. schrieb:> daß wohl niemand die Kiste SOOO lange eingeschaltet läßt.
Kommt mir bekannt vor. Windows 95 hörte nach 49,7 Tagen auf, die
Maus zu bewegen …
Dabei ist unsigned overflow sowas von sauber definiert, dass man mit
ein wenig Nachdenken das völlig problemlos in den Griff bekommt.
Warum verwendest du ein 8-bit unsigned für den timer?
Aus Sicht des darunterliegenden Assemblers gibt es keinen Unterschied in
der Laufzeit ob 8 oder 32-bit! Einzig der RAM-Verbrauch ist
möglicherweise um 1, oder 3 byte größer, wobei das aber auch
möglicherweise durch alignments vom Compiler auch noch hinfällig sein
kann!
Fritz schrieb:> Aus Sicht des darunterliegenden Assemblers gibt es keinen Unterschied in> der Laufzeit ob 8 oder 32-bit!
Das stimmt nicht. 8-Bit-Variablen machen das Programm auf einem STM32
wesentlich langsamer(!) als 32-Bit-Variablen, welche die "natürliche
Breite" des Prozessor verwenden. Warum? Bei 8-Bit muss der Prozessor
sämtliche Operationen auf 8-Bit zusätzlich maskieren.
Wenn ich auf einem STM32 tatsächlich nur eine Variable mit dem
Wertebereich 0..255 brauche, verwende ich uint_fast8_t, um auszudrücken,
dass ein uint8_t eigentlich reichen würde. uint_fast8_t ist aber auf dem
STM32 32-Bit breit (identisch mit uint32_t) und läuft daher mit voller
Geschwindigkeit. So kann ich µC-unspezifische Programmteile sowohl für
einen ATmega (uint_fast8_t ist hier identisch mit uint8_t) als auch für
einen STM32 kompatibel halten - bei voller Geschwindigkeit.
grundschüler schrieb:> vorschlag für ARM/AVR-Universal-Timer - frei nach elm chan:
Eine Zumutung. Als Einrückung werden hier 3, 4 und 0 Blanks verwendet.
Das kann man nicht lesen.
Die Preprocessor-Konstante "zucAVR" gibts nicht. Verwende besser eine,
die auf AVRs immer definiert ist. OCR0A = 78 ist abhängig von F_CPU.
Also benutze das auch.
Zu timer_proc_10ms: Warum brauchst Du hier Stunden und Minuten?
Das sieht mir hier sehr nach zusammengeklaubtem Code aus, den Du selbst
nicht verstanden hast.
Frank M. schrieb:> zusammengeklaubtem Code
Das ist zusammengeklaubter Code, den ich offensichtlich verstanden habe,
weil ich ihn mit STMs, AVRs und LPCs zum Laufen gebracht habe.
Deine Arroganz geht mir auf den Senkel.
Frank M. schrieb:> Das stimmt nicht. 8-Bit-Variablen machen das Programm auf einem STM32> wesentlich langsamer(!) als 32-Bit-Variablen,
Völlig übertrieben. Das Ausmaskieren ist nur nötig wenn das Ergebnis
direkt aus dem Register weiterverarbeitet wird, denn wird es in den
Speicher geschrieben (wie hier im SysTick_Handler), geschieht dies
"gratis" automatisch durch die STRB Instruktion. Selbst dann wird nur
ein einziger zusätzlicher Takt benötigt (für UXTB/SXTB). Das macht sich
also nur dann bemerkbar, wenn das Programm ausschließlich aus
8-Bit-Rechnungen besteht, die nicht auf den Speicher zugreifen...
Dr. Sommer schrieb:> Völlig übertrieben.
Lass mal die diese Schleife auf einem STM32 laufen und miss die Zeiten.
1
volatileuint32_tm;
2
volatileuint8_ti;
3
volatileuint8_tj;
4
5
for(m=0;m<1000000;m++)
6
{
7
for(i=0;i<255;i++)
8
{
9
for(j=0;j<255;j++)
10
{
11
;
12
}
13
}
14
}
Anschließend setzt Du den Typ von i und j auf uint_fast8_t und misst
nochmal. Du wirst feststellen, dass die Schleifen nun ca. 3mal so
schnell abgearbeitet weden. Genau das hatte ich mal vor ein paar Monaten
getestet. Leider habe ich den genauen Faktor nicht mehr im Kopf. Schlag
mich nicht, wenn's nur Faktor 2,5 ist. Ähnlich verhält es sich übrigens
bei uint16_t.
Okay, im konkreten Fall mag dies nicht so gravierend sein.
grundschüler schrieb:> Das ist zusammengeklaubter Code, den ich offensichtlich verstanden habe,> weil ich ihn mit STMs, AVRs und LPCs zum Laufen gebracht habe.
Dein Code ist nicht übersetzbar, denn es fehlen u.a. die Definitionen
für sec, min, hou und timer0_neu. Welchen Typ haben sie? volatile oder
nicht volatile? 8-Bit, 16-Bit oder 32-Bit?
Nochmal: Was machen die Minuten und Stunden dort im Code? Die haben für
Deinen Timer nichts beizutragen. Zeig doch mal ein (übersetzbares!)
Beispiel, wie man Deinen Code plattformübergreifend nutzen soll.
> Deine Arroganz geht mir auf den Senkel.
Deine Art, wie Du überall im Forum Code hinrotzt, geht mir ebenso auf
den Senkel und zeugt von einer ganz anderen Art von Arroganz. Man kann
in dieser Form nämlich leider mit Deinen Codefetzen, die so gar nicht
laufen, wenig bis gar nichts anfangen.
Wenn Du den Code aufarbeitest und in eine brauchbare Form bringst,
muss die Arbeit nur einmal gemacht werden. Wenn aber 1000 Leser die
Arbeit machen müssen, dann ist das 999 mal zuviel. Also zeige bitte
etwas mehr Respekt vor Deinen Lesern und gib Dir mehr Mühe.
StackOverflower schrieb:> Beispielhaft habe ich den msTicks als char gewählt.> Wie löst man die Problematik am besten, wenn der (msTicks - curTicks)> durch einen Überlauf (255 + 1 = 0) niemals größer dlyTicks wird?>> Bsp: curTicks = 250 und dlyTicks = 10...>> Müsste man eine If-Abfrage in den SysTickHandler schreiben?
Die Frage ist, warum Du überhaupt einen Zähler msTicks die ganze Zeit
durchlaufen lässt. Eigentlich muss der nur "laufen", wenn auch die Zeit
gestoppt werden soll. Damit vermeidest Du das Überlauf-Problem.
Hier eine delay-Funktion mit SysTick_Handler, wo ein Überlauf überhaupt
gar nicht erst entstehen kann:
1
#if defined (STM32F10X)
2
#include"stm32f10x.h"
3
#include"stm32f10x_rcc.h"
4
#elif defined (STM32F4XX)
5
#include"stm32f4xx.h"
6
#include"stm32f4xx_rcc.h"
7
#endif
8
9
#include<stdint.h>
10
11
staticvolatileuint32_tdelay_counter;
12
13
void
14
SysTick_Handler(void)
15
{
16
if(delay_counter>0)
17
{
18
delay_counter--;
19
}
20
}
21
22
void
23
delay_msec(uint32_tmsec)
24
{
25
delay_counter=msec;
26
27
while(delay_counter!=0)
28
{
29
;
30
}
31
}
32
33
void
34
delay_init(void)
35
{
36
SysTick_Config(SystemCoreClock/1000);
37
}
Das Beispiel arbeitet mit einer Auflösung von 1ms und ist leicht an
andere Auflösungen (z.B. 100us oder gar 1us) in delay_init() anpassbar.
Läuft "Out-of-the-Box" für STM32F1xx und STM32F4xx - vorausgesetzt, die
STM32-PLLs wurden mit SystemInit() und SystemCoreClockUpdate() vorab
korrekt initialisiert.
Beispiel:
Frank M. schrieb:> Du wirst feststellen, dass die Schleifen nun ca. 3mal so> schnell abgearbeitet weden. Genau das hatte ich mal vor ein paar Monaten> getestet.
Liegt vermutlich daran, dass deine Schleifenzähler "volatile" sind
und/oder du ohne Optimierungen kompiliert hast. Ich habe es auch mal
ausprobiert, und uint8_t braucht ca. 25% länger als uint_fast8_t. Das
ist auch viel realistischer, denn die meiste Zeit wird mit addieren und
springen verbracht, der eine Zyklus mehr zum Ausmaskieren macht da
vergleichsweise wenig aus.
Hier mein Test-Code:
1
voidtest1(){
2
uint32_tm;
3
uint8_ti,j;
4
for(m=0;m<1000000;m++){
5
for(i=0;i<255;i++){
6
for(j=0;j<255;j++){
7
asmvolatile("nop");
8
}
9
}
10
}
11
}
12
voidtest2(){
13
uint32_tm;
14
uint_fast8_ti,j;
15
for(m=0;m<1000000;m++){
16
for(i=0;i<255;i++){
17
for(j=0;j<255;j++){
18
asmvolatile("nop");
19
}
20
}
21
}
22
}
Das "nop" (=1 Takt) verhindert unerwünschte Compiler-Optimierungen, und
liefert dennoch realistische Ergebnisse da eine Schleife mit 0 Takten im
Körper unsinnig ist...
Hier trifft übrigens genau meine Bedingung von oben zu:
Dr. Sommer schrieb:> Das macht sich> also nur dann bemerkbar, wenn das Programm ausschließlich aus> 8-Bit-Rechnungen besteht, die nicht auf den Speicher zugreifen...
Selbst in diesem konstruierten Extremfall ist der Unterschied "nur" 25%,
daher würde ich "wesentlich langsamer(!)" immer noch als stark
übertrieben ansehen. Je mehr sinnvoller Code im Schleifencode steckt,
desto insignifikanter wird der uint8_t / uint_fast8_t Unterschied.
Frank M. schrieb:> while (delay_counter != 0)> {> ;> }
Zu Stromsparzwecken könnte man in die while-Schleife noch ein asm
volatile("wfi") einfügen, um den Core beim Warten schlafen zu legen. Das
Aufwachen geschieht automatisch durch den Interrupt.
Hier noch der Code vom Beispiel, die markierten Zeilen entfallen in der
uint_fast8_t Version:
Frank M. schrieb:> Nochmal: Was machen die Minuten und Stunden dort im Code? Die haben für> Deinen Timer nichts beizutragen. Zeig doch mal ein (übersetzbares!)> Beispiel, wie man Deinen Code plattformübergreifend nutzen soll.
Das ist ein code-Ausschnitt und kein vollständiges Programm. Es zeigt,
wie man -uc-abhängig- einen systick von 10 ms definiert und dann
uc-unabhängig mit einem Timer_process auswertet. Für M328 etc, LPC1768
kann man den Systick direkt verwenden. In den Timer_Process muss
anwendungsabhängig alles rein, was dort gebraucht wird. Wenn man z.B.
eine Uhrzeit braucht, sec, min, hou für die Uhrzeit. Wenn man RTC-Zeit
hat , lässt man das natürlich weg. Gezeigt werden sollte wie z.B. ein
Timer für die sd-Karte funktioniert:
#if USE_MMC
if(Timer1)Timer1--;//100Hz f�r sd-karte
#endif
Das versteht sich wohl von selbst ohne dass man USE_MMC (geklaubt von
U.Radig) noch einmal erläutert.
Was ich deinem Beitrag immerhin entnommen habe ist, dass der code besser
wird, wenn man einen systemabhängigen Datentyp ux für Zählvariablen
definiert, der beim AVR ein uint8_t ist und beim ARM ein u32:
#if AVR
#define ux uint8_t
#else
#define ux uint32_t
#endif
Frank M. schrieb:> OCR0A = 78 ist abhängig von F_CPU.> Also benutze das auch.
Der Timer muss sinnvollerweise kalibriert werden. F_CPU ist ungefähr -
bei meinen AVRs - 8MHz. Ich ermittle einmal die Konstante für den Timer
und für das delay. Den weiteren Schritt - Rückrechnung der genauen
CPU-Frequenz - spare ich mir, weil ich die dann nicht mehr brauche. Ich
kann, da die genau CPU-Frequenz nicht bekannt ist, OCR0A nicht mit
ausreichender Genauigkeit aus F_CPU ableiten.
Dr. Sommer schrieb:> Liegt vermutlich daran, dass deine Schleifenzähler "volatile" sind> und/oder du ohne Optimierungen kompiliert hast.
Ja/nein.
> Ich habe es auch mal> ausprobiert, und uint8_t braucht ca. 25% länger als uint_fast8_t.
Auch das wäre für mich schon ein Grund, uint_fast8_t zu verwenden. Auch
wenn die STM32 schon ziemlich flott sind, muss man die Ressourcen ja
nicht zum Fenster rauswerfen ;-)
> Zu Stromsparzwecken könnte man in die while-Schleife noch ein asm> volatile("wfi") einfügen, um den Core beim Warten schlafen zu legen. Das> Aufwachen geschieht automatisch durch den Interrupt.
Das ist ein guter Tipp, danke dafür.
grundschüler schrieb:> Der Timer muss sinnvollerweise kalibriert werden.
Das ist aber für den Leser, dem Du die Brocken vorwirfst, überhaupt
nicht verständlich. Zumindest solltest Du die Stelle dokumentieren. Aber
zu Deinem Kommentar komme ich noch weiter unten.
Da die AVRs auch die Möglichkeit anbieten, einen Quarz anzuschließen,
solltest Du für einen allgemeingültigen Code, den Du der Allgemeinheit
zur Verfügung stellst, F_CPU verwenden. Diejenigen, die Deinen Source
dann verwenden, müssen halt dafür sorgen, dass F_CPU annähernd korrekt
ist - sei es durch einen Quarz oder Kalibrierung des internen
Oszillators.
> F_CPU ist ungefähr - bei meinen AVRs - 8MHz.
Wenn Du hier Code veröffentlichst, dann spielen deineAVRs keine
Rolle. Denn dann geht es um die AVRs des Lesers.
Deshalb ist ein
1
OCR0A=78;
überhaupt nicht angebracht. Was hilft dieser Wert, wenn er doch nur
DeineAVRs betrifft?
Und jetzt kommt der Witz des Ganzen. Hättest Du geschrieben:
1
OCR0A=F_CPU/1024/100;// prescaler is 1024, interrupt every 10msec
Dann kommt heraus:
1
8000000 / 1024 / 100 = 78
Also exakt Dein Wert! Verblüffend, oder? Der Source ist nun abhängig vom
gewählten CPU-Takt des Users und nicht mehr von DeinenAVRs! :-)
P.S.
Dein Kommentar:
1
OCR0A=78;
2
// Timer0 soll bei 250 einen Output Compare Interrupt auslösen ->
3
// 250.000 / 250 = 1000 mal pro Sek gibts einen Interrupt
ist übrigens komplett unsinnig: Der Output Compare Interrupt wird hier
überhaupt nicht bei 250 ausgelöst. Und es gibt auch nicht 1000mal pro
Sekunde einen Interrupt, sondern nur 100mal pro Sekunde.
Also nochmal: hättest Du geschrieben:
1
OCR0A=F_CPU/1024/100;// prescaler is 1024, interrupt every 10msec
Dann sieht man ganz klar die 100 Calls pro Sekunde - einfach, weil es da
steht.
> Ich ermittle einmal die Konstante für den Timer> und für das delay. Den weiteren Schritt - Rückrechnung der genauen> CPU-Frequenz - spare ich mir, weil ich die dann nicht mehr brauche. Ich> kann, da die genau CPU-Frequenz nicht bekannt ist, OCR0A nicht mit> ausreichender Genauigkeit aus F_CPU ableiten.
Das ist Unsinn. Natürlich willst Du Deinen Oszillator möglichst genau
laufen lassen. Aber Du willst ihn möglichst genau mit 8MHz laufen
lassen, denn darauf kalibrierst Du ihn!
Fazit: Die Anwendung von F_CPU wäre immer der korrekte Weg gewesen.
grundschüler schrieb:> #if AVR> #define ux uint8_t> #else> #define ux uint32_t> #endif
Wenn schon eigene Datentypen definieren dann bitte mit typedef und nicht
mit der Textersetzung "#define", erst recht wenn man so einen kurzen
Namen verwendet. Stell dir mal vor jemand kommt auf die Idee eine lokale
Variable mit dem Namen "ux" zu definieren...
Außerdem gibt es für diesen Fall eben uint_fast8_t, kein Grund was
eigenes zu machen.
Frank M. schrieb:> Auch das wäre für mich schon ein Grund, uint_fast8_t zu verwenden. Auch> wenn die STM32 schon ziemlich flott sind, muss man die Ressourcen ja> nicht zum Fenster rauswerfen ;-)
Schon, aber gerade beim Fall mit dem Delay ist der eine Takt völlig
wurst. Da gibt es noch viel bessere Optimierungsmöglichkeiten, zum
Beispiel gar kein Delay zu verwenden sondern "richtige" Timer... Solche
Mikrooptimierungen kommen erst wenn man alle Algorithmen und
Datenstrukturen schon perfekt hat :-)