Ich würde gerne ein "relativ" präzises Delay, ohne Timer, für meine
Biblio bauen.
Da ich für das Delay ein Rückgabewert für eventuelle Abbruchbedingungen
benötige stellen sich für mich einige Fragen. Ich habe mir da so einige
Überlegungen gemacht - kleines Beispiel für ein einfaches Delay ohne
Rückgabe:
1
uint32_tdelay_multiplier;
2
3
voidInitDelay(void)
4
{
5
/* Get SysClock */
6
RCC_ClocksTypeDefRCC_Clocks;
7
RCC_GetClocksFreq(&RCC_Clocks);
8
9
/* While Schleife braucht 4 cycles */
10
/* Für 1 ms delay, müssen wir durch 4K teilen, */
11
/* da die while Schleife 4 Cycles benötigt. */
12
delay_multiplier=RCC_Clocks.HCLK_Frequency/4000;
13
14
}
15
16
17
voiddelay(uint32_twait)
18
{
19
/* wait * multi, und dann -10 für diese Berechnung */
20
wait=(wait*delay_multiplier)-10;
21
22
while(delay--);
23
}
Für einen Rückgabewert benötige ich ja natürlich eine Abfrage in meinem
delay - die Frage ist nun - wie viele Cycles benötige ich da nun genau
für solch eine Abfrage eine volatilen Variable? Ist die von der
Optimierungsstufe des Compilers abhängig? Ware es besser dies mit Inline
ASM zu bewerkstelligen um den Compiler "festzunageln"? Mein
Gedankengang:
1
volatileuint8_tdelay_interrupt=0;
2
3
uint8_tdelay(uint32_twait)
4
{
5
/* wait * multi, und dann -10 für diese Berechnung */
6
wait=(wait*delay_multiplier)-10;
7
8
while(delay--)
9
{
10
if(delay_interrupt)return1;
11
}
12
}
Wo finde ich Assembly in SW4STM (Eclipse) - damit ich mir die Cycles
berechnen kann? Wie würde ein Inline ASM dafür aussehen?
Jemand eine Idee?
Diese ARM processoren haben dafur eine eingebaute "Sys_tick". Das ist
ein timer die dafur ideal ist : nach einstellung haben sie auf feste
intervallen einen interrupt, wo dan ihre delay gezahlt wird :
Beispiel fur F407 an 168 MHz :
/* SysTick end of count event each 1ms */
SysTick_Config(168000);
Dan die ISR :
void SysTick_Handler(void)
{
if (Delay != 0x00) Delay--;
milli_seconds++;cal_timer++;speed_timer++;watchdog_gps++;
MPU_print++;timer_0++;mpu_timer++;Tx_timer++;
}
Wie sie sehen verwende ich das dan für alle gewunschte 1ms timers.
RP6conrad schrieb:> Diese ARM processoren haben dafur eine eingebaute "Sys_tick". Das> ist> ein timer die dafur ideal ist : nach einstellung haben sie auf feste> intervallen einen interrupt, wo dan ihre delay gezahlt wird :> Beispiel fur F407 an 168 MHz :> /* SysTick end of count event each 1ms */> SysTick_Config(168000);> Dan die ISR :> void SysTick_Handler(void)> {> if (Delay != 0x00) Delay--;> milli_seconds++;cal_timer++;speed_timer++;watchdog_gps++;> MPU_print++;timer_0++;mpu_timer++;Tx_timer++;> }> Wie sie sehen verwende ich das dan für alle gewunschte 1ms timers.
Ja den Systick habe ich auch in Verwendung, das Problem beim Systick
Interrupt ist ja folgender:
1
voidSysTick_Handler(void)
2
{
3
if(Delay!=0x00)Delay--;// 10 cycles
4
milli_seconds++;// RMS: 3 cycles
5
cal_timer++;// RMS: 3 cycles
6
speed_timer++;// RMS: 3 cycles
7
watchdog_gps++;// RMS: 3 cycles
8
MPU_print++;// RMS: 3 cycles
9
timer_0++;// RMS: 3 cycles
10
mpu_timer++;// RMS: 3 cycles
11
Tx_timer++;// RMS: 3 cycles
12
// ----------
13
// SUM 34 cycles
14
// ==========
15
}
Das sind dann in der Summe 34 Cycles welche in einer Atomaren ISR
(Systick ist ja non-blockable) stehen - der Systick wird auch erst
wieder gestartet wenn die ISR abgearbeitet ist. Das dezimiert den
gesamten Busclock um genau diese Cycles. 34 Cycles hört sich ja im
ersten Moment nicht "viel" an, macht aber in der Summe mehrere Mhz am
gesamten Takt aus.
DAS ist ja genau das Problem welches ich umgehen will, da ich momentan
ebenfalls den Systick als Taktquelle nutze :-/
> Ist die von der Optimierungsstufe des Compilers abhängig?
Ja ist es. Und je nach Taktfrequenz musst du Wait States für den Flash
Speicher nutzen, daher geht es nicht ganz so einfach (die Taktfrequenz
durch x teilen).
> Wo finde ich Assembly in SW4STM (Eclipse) - damit ich mir die> Cycles berechnen kann?
Ich bin unsicher, was du mit der Frage meinst. Wenn du sehen willst,
welchen Assembler-Code der Compiler erzeugt, dann trage in in das
Textfeld unter Properties/C/C++ Build/Settings/Tool Settings/MCU GCC
Linker/Miscellaneous/Linker Flags das ein:
-Wa,-adhlns="$(@:%.o=%.lst)"
Du findest dann für jede Quelll-Datei eine *.lst Datei im Verzeichnis
Debug oder Release.
> 34 Cycles hört sich ja im ersten Moment nicht "viel" an, macht aber> in der Summe mehrere Mhz am gesamten Takt aus.
Ich gehe davon aus, dass nicht alle Counter so schnell hochzählen
müssen. Dann bietet sich folgendes Konstrukt an:
1
voidSysTick_Handler(void)
2
{
3
staticuint8_tteiler=0;
4
5
if(Delay!=0x00)Delay--;
6
milli_seconds++;
7
8
if(++teiler==100)
9
{
10
teiler=0;
11
cal_timer++;
12
speed_timer++;
13
watchdog_gps++;
14
MPU_print++;
15
timer_0++;
16
mpu_timer++;
17
Tx_timer++;
18
}
19
}
Das würde die CPU Last deutlich senken.
Eine andere Möglichkeit ist, nicht viele Variablen hochzuzählen, sondern
nur eine einzige. Vermutlich setzt dein Programm die vielen Zähler auf 0
und macht dann irgend etwas bis (oder wenn) sie einen gewissen Wert
erreicht haben. Das kann man auch anders machen:
1
uint32_tmilli_seconds;
2
3
voidSysTick_Handler(void)
4
{
5
milli_seconds++;
6
}
7
8
voiddelay_ms(uint32_ttime)
9
{
10
uint32_tstart=milli_seconds;
11
while(milli_seconds-start<=time);
12
}
13
14
voidmeasure_time()
15
{
16
uint32_tstart=milli_seconds;
17
// tu irgendwas hier
18
uint32_telapsed=milli_seconds-start;
19
}
Der Trick hierbei liegt in der Subtraktion. Sie liefert sogar das
korrekte Ergebnis, wenn milli_seconds zwischendurch einmal übergelaufen
ist. Das Zurücksetzen auf 0 entfällt, und das wiederum ermöglicht es
Dir, den EINEN milli_seconds Counter in mehreren Threads parallel und
überlappend zu verwenden.
Du kannst damit Intervalle messen, die maximal 2x so lang sind, wie ein
kompletter Durchlauf. Also in diesem Fall maximal 98 Tage.
Von Delays halte ich übrigens nicht viel. In sehr einfachen Programmen
sind sie noch praktisch, doch für so einfache Programme würde keinen
"fetten" 32bit Controller verwenden. Früher oder später soll dein
Programm mehrere Threads parallel abarbeiten (lerne: Endlicher Automat)
und dann sind Delays praktisch verboten (außer im µS Bereich).
Rene K. schrieb:
> /* wait * multi, und dann -10 für diese Berechnung */> wait = (wait * delay_multiplier) - 10;
und
> Das sind dann in der Summe 34 Cycles welche in einer Atomaren> ISR (Systick ist ja non-blockable) stehen
Wenn man sooo genau werden will, muss man mindestens noch die
Flash-Wait-Zyklen berücksichtigen, oder nicht, wenn die Schleife im RAM
läuft. Dazu kommt, ob prefetch eingeschaltet ist und die Wirkung von
Cache/ART. Das Alignment der delay-Routine macht auch 1 Zyklus hin oder
her oder evt. auch nicht. Selbst wenn man die Routine selbst in
Assembler schreibt oder die Optimierung festnagelt, spielt immer noch
die Optimierung des Aufrufs eine Rolle.
Wenn du sooo genau werden willst und die Bibliothek sogar auf
verschiedenen Chips nutzen willst: nimm einen Timer.
Man könnte den systick-_Counter_ abfragen, aber der ist nur 24 Bit breit
und kann mit HCLK oder HCLK/8 laufen. Man sollte den DWT Cycle Counter
abfragen. Der läuft immer mit HCLK und ist 32 Bit breit, macht also
weniger (keine) Probleme beim Überlauf.
Der Trick, die Division für den Takt in eine Init auszulagern, ist
allerdings sehr gut. Der Div-Befehl kann je nach Daten unterschiedlich
lange dauern. Beim Mul-Befehl muss man nur wissen, ob ST den großen oder
nur den kleinen Multiplizierer eingebaut hat (1 bzw. ca. 32 Zyklen).
Noch was (aber dann gebe ich Ruhe). Ein normaler Timer TIMx ist im
I/O-Adressbereich angesiedelt während systick und DWT am Private
Peripheral Bus hängen. Ein Thread mit CONTROL_TPL = 1 hat da evt. keine
Zugriffsrechte.
m.n. schrieb:
> Das exakte Zählen von Zyklen in einer Warteschleife ist sinnlos,> sofern Interrupts im Programm auftreten können.
Ohne Hardware-Zähler ist es auf größeren Chips sowieso sinnlos, mit wird
die Unterbrechung ja in gewissen Grenzen kompensiert. Eine ISR sollte ja
nicht so lange dauern. Und oft braucht man nur eine minimale
Verzögerung. Und notfalls kann der Aufrufende die Priorität anpassen.
einfügen schrieb:
>>der Systick wird auch erst wieder gestartet wenn die ISR> abgearbeitet ist.>> Wo hast Du denn das gelesen?
Naja, die systick ISR wird sich nicht selbst unterbrechen. Auf cortex-m
geht das nichtmal mit Gewalt, glaube ich.
Ich hatte mal aus Spaß ein taktgenaues Delay ganz ohne Timer (in
Assembler) geschrieben:
https://github.com/Erlkoenig90/stm32delay
Das wird im RAM ausgeführt, damit keine Waitstates berücksichtigt
werden. Die Funktion rechnet den Overhead raus, um somit immer exakt die
richtige Anzahl an Takten zu warten (ab einer bestimmten Mindestanzahl).
Die Berechnung muss man je nach Controller ggf. anpassen.
Nur als Beispiel, die Sinnhaftigkeit von Delays ohne Timer/Interrupt
wurde ja oben schon besprochen.
> uint32_t delay_multiplier;
Hier fehlt das volatile.
In BS gibt es einen Zähler, der mit konstantem Takt inkrmentiert wird.
Der Zählerwert kann von jedem gelesen werden und es lassen sich
beliebig viele Referenzen ableiten, ohne (fast!) die Genauigkeit zu
beeinflussen.
D. h. in der ISR wird nur ein Inkrement ausgeführt.
kannst du deine Delay Anforderungen genauer beschreiben? Also kürzestes,
längstes benötigtes Delay und die benötigte Abstufung.
Man könnte z.B. die Netzfrequenz hinter einem Trafo mit AC Opokoppler
oder nach dem Gleichrichter mit einem normalen Optokoppler abgreifen und
in einer ISR mitzählen.
Rene K. schrieb:> void SysTick_Handler(void)> {> if (Delay != 0x00) Delay--; // 10 cycles> milli_seconds++; // RMS: 3 cycles> cal_timer++; // RMS: 3 cycles> speed_timer++; // RMS: 3 cycles> watchdog_gps++; // RMS: 3 cycles> MPU_print++; // RMS: 3 cycles> timer_0++; // RMS: 3 cycles> mpu_timer++; // RMS: 3 cycles> Tx_timer++; // RMS: 3 cycles> // ----------> // SUM 34 cycles> // ==========> }
Warum machst Du das so? Normalerweise kenne ich das so, daß im
Systick-Interrupt nur genau ein einziger Zähler hochgezählt wird, und
zwar in der Regel "milli_seconds" - für alle Delays im
Millisekundenbereich oder länger wird nur diese Variable genutzt.
Thomas E. schrieb:> Warum machst Du das so? Normalerweise kenne ich das so, daß im> Systick-Interrupt nur genau ein einziger Zähler hochgezählt wird, und> zwar in der Regel "milli_seconds" - für alle Delays im> Millisekundenbereich oder länger wird nur diese Variable genutzt.
Und beim Arduino gibt's dafür sogar eine hübsche Anleitung, die ganz
ohne Überlaufproblem mit Zeitdifferenzen arbeitet:
http://playground.arduino.cc/Learning/BlinkWithoutDelayDe