Forum: Mikrocontroller und Digitale Elektronik STM32 - relativ praezises Delay ohne Timer


von Rene K. (xdraconix)


Lesenswert?

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_t delay_multiplier;
2
3
void InitDelay(void)
4
{
5
  /* Get SysClock */
6
  RCC_ClocksTypeDef RCC_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
void delay(uint32_t wait)
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
volatile uint8_t delay_interrupt = 0;
2
3
uint8_t delay(uint32_t wait)
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) return 1;
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?

: Bearbeitet durch User
von RP6conrad (Gast)


Lesenswert?

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.

von Rene K. (xdraconix)


Lesenswert?

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
void SysTick_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 :-/

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

> 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
void SysTick_Handler(void)
2
{
3
  static uint8_t teiler=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_t milli_seconds;
2
3
void SysTick_Handler(void)
4
{
5
  milli_seconds++; 
6
}
7
8
void delay_ms(uint32_t time)
9
{
10
  uint32_t start=milli_seconds;
11
  while (milli_seconds-start <= time);
12
}
13
14
void measure_time()
15
{
16
  uint32_t start=milli_seconds;
17
  // tu irgendwas hier
18
  uint32_t elapsed=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).

von eagle user (Gast)


Lesenswert?

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.

von eagle user (Gast)


Lesenswert?

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

von eagle user (Gast)


Lesenswert?

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.

von m.n. (Gast)


Lesenswert?

Das exakte Zählen von Zyklen in einer Warteschleife ist sinnlos, sofern 
Interrupts im Programm auftreten können.

von einfügen (Gast)


Lesenswert?

>der Systick wird auch erst wieder gestartet wenn die ISR abgearbeitet ist.

Wo hast Du denn das gelesen?

von eagle user (Gast)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch User
von Nico W. (nico_w)


Lesenswert?

Was ist mit DWT?

von go for gold (Gast)


Lesenswert?

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

von Thomas (kosmos)


Lesenswert?

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.

von Thomas E. (picalic)


Lesenswert?

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.

: Bearbeitet durch User
von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

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

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.