Forum: Mikrocontroller und Digitale Elektronik STM32F4: µs-Delay


von Daniel V. (voda) Benutzerseite


Lesenswert?

Guten Abend liebes Forum.

Ich sitze hier mal wieder an einem Problem. Für eine SPI-Kommunikation 
benötige ich einen µs-Timer. Folgendes habe ich bisher auf meinem 
STM32F446 implementiert:

delay.c
1
#include <delay.h>
2
3
extern volatile uint32_t ticker;
4
volatile uint32_t us;
5
6
void SysTick_Init(void)
7
{
8
 if (SysTick_Config (SystemCoreClock / 1000)) //1ms per interrupt
9
  while (1);
10
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
11
  NVIC_SetPriority(SysTick_IRQn,0);
12
  ticker = 0;
13
}
14
15
uint32_t generated_ms(void)
16
{
17
  return ticker;
18
}
19
20
uint32_t generated_us(void)
21
{
22
   /* us = ticker*1000+(SystemCoreClock/1000-SysTick->VAL)/80; */
23
   us = ticker * 1000 + 1000 - SysTick->VAL/80;
24
   return us;
25
}
26
27
void delay_ms(uint32_t nTime)
28
{
29
 uint32_t curTime = ticker;
30
  while((nTime-(ticker-curTime)) > 0);
31
}
32
33
void delay_us(uint32_t nTime)
34
{
35
 uint32_t curTime = us;
36
 while((nTime-(us-curTime)) > 0);
37
}

delay.h
1
#ifndef DELAY_H
2
  #define DELAY_H
3
  
4
#include <stm32f4xx.h>
5
#include <misc.h>
6
7
extern void SysTick_Init(void);
8
extern void delay_ms(uint32_t nTime);
9
extern void delay_us(uint32_t nTime);
10
#endif

Zudem habe ich in einer Funktion die SysTick_Handler:
1
void SysTick_Handler(void)
2
{
3
  ticker++;
4
}

Die Variable ticker++ ist natürlich als volatile uint32_t deklariert. 
Die ms-Funktion funktioniert ohne Probleme aber rufe ich in der main die 
us-Funktion auf, naja da passiert gar nichts...

Wo kann hier der Fehler liegen? Wie bekomme ich eine anständige 
us-Delay-Funktion hin?

Danke euch und Gruß
Daniel

von holger (Gast)


Lesenswert?

>Wie bekomme ich eine anständige us-Delay-Funktion hin?

Nimm einen anderen Timer und nicht den Systick.

von Codeleser (Gast)


Lesenswert?

Daniel V. schrieb:
> while (1);

wait forever?

von posti (Gast)


Lesenswert?

Hi

C ist nicht meine Sprache :/

Vll. hilft Dir das Beispiel in dem AVR-Assembler-Makros:
https://www.mikrocontroller.net/articles/AVR_Assembler_Makros
... zumindest auf nem AVR (bisher ATtiny45) macht die Routine, was dran 
steht ;)

Da mir C (oder # oder + oder ++ oder was es noch gibt) nicht viel sagt, 
erscheint mir das Assembler-Listing auch irgendwie logischer - und 
erzeugt erfrischend kurzen Code bei takt-genauer Verzögerung.

Oder wird oben im Code 'nur' abgefragt, ob die Wartezeit bereits um ist?
(der Return-Wert 'return us' lässt mich Das fragen)

MfG

von Daniel V. (voda) Benutzerseite


Lesenswert?

holger schrieb:
> Nimm einen anderen Timer und nicht den Systick.

Du meinst die TIMs welche hier im diesem Projekt verwendet wird:

Beitrag "STM32F4Discovery mit CooCox :: GPIO,SDIO,Timer,SoftTimer,USART,printf"

Das habe ich irgendwie schon befürchtet.

Codeleser schrieb:
>> while (1);

Hmmm, verstehe ich jetzt nicht so ganz wo der Fehler sein sollte, da ja 
die delay_ms-Funktion funktioniert.

posti schrieb:
> Oder wird oben im Code 'nur' abgefragt, ob die Wartezeit bereits um ist?
> (der Return-Wert 'return us' lässt mich Das fragen)

Die Funktion
1
uint32_t generated_us(void)
2
{
3
   /* us = ticker*1000+(SystemCoreClock/1000-SysTick->VAL)/80; */
4
   us = ticker * 1000 + 1000 - SysTick->VAL/80;
5
   return us;
6
}

soll die µs anhängig vom SystemCoreClock sowie dem Zähler ticker 
generieren, daher auch die Berechnung. Die Variable us ist der 
berechnete Wert () und wird mit return übergeben. Gleichzeitig wird die 
Funktion mit diesem Schlüsselwort beendet.

Danke und Gruß
Daniel

: Bearbeitet durch User
von Nico W. (nico_w)


Lesenswert?

Codeleser schrieb:
> Daniel V. schrieb:
> while (1);
>
> wait forever?

Ja, das ist da ja auch richtig. Kannst ja mal gucken wann die 
if-Bedingung wahr wird.

von Nop (Gast)


Lesenswert?

Dann gibt es bei STM32F4 ja auch noch den DWT-Counter. Der zählt 
CPU-Takte. Wenn man weiß, was für einen Takt man eingestellt hat, kann 
man damit ohne weiteres recht genaue Warteschleifen machen.

von Johannes S. (Gast)


Lesenswert?

Wo wird denn generated_us aufgerufen? Zumindest in dem gezeigten 
Ausschnitt wird us doch nicht verändert?

von greg (Gast)


Lesenswert?

Für us-Delays kann man doch einfaches Busy Waiting verwenden, oder? Mach 
eine kleine Schleife mit Assembler und zähl die Takte. Die Anzahl der 
Durchläufe ergibt sich dann aus der Zeit für einen Schleifendurchlauf 
und der Taktfrequenz. Ansonsten der besagte DWT-Counter.

von Daniel V. (voda) Benutzerseite


Lesenswert?

greg schrieb:
> Für us-Delays kann man doch einfaches Busy Waiting verwenden, oder? Mach
> eine kleine Schleife mit Assembler und zähl die Takte. Die Anzahl der
> Durchläufe ergibt sich dann aus der Zeit für einen Schleifendurchlauf
> und der Taktfrequenz. Ansonsten der besagte DWT-Counter.

Kannst Du das mal an einem Beispiel zeigen? Was ist der DWT-Counter? 
Gibt es dazu auch Beispiele?

Danke euch und Gruß
Daniel

von STM-Macker (Gast)


Lesenswert?

Daniel V. schrieb:
> Was ist der DWT-Counter?
> Gibt es dazu auch Beispiele?

- Kennst du gugel?
- kannst du das eventuel bedienen?
- meinst du dass du eventuell mit diesem Werkzeug
auch ein Beispiel finden kannst?

von aSma>> (Gast)


Lesenswert?

Daniel V. schrieb:
> void delay_us(uint32_t nTime)
> {
>  uint32_t curTime = us;                 //us=generated_us
>  while((nTime-(us-curTime)) > 0);       //us=generated_us
> }

Man hat dir doch schon einmal einen Tipp gegeben, dass die Funktion 
generated_us() nicht aufgerufen wird.

von Nop (Gast)


Lesenswert?

Daniel V. schrieb:

> Was ist der DWT-Counter?

Ein Register, welches synchron zum CPU-Takt hochzählt. Ist im Reference 
Manual leider nur schwammig erwähnt. Praxisbeispiel siehe hier:

http://www.carminenoviello.com/2015/09/04/precisely-measure-microseconds-stm32/ 
(etwas runterscrollen)

Daß dessen Version bei 1µs nicht so präzise ist, wundert mich nicht. 
Kommt sicherlich daher, daß er die Division in der Warteroutine macht, 
anstatt den Ausdruck "SystemCoreClock/1000000L" außerhalb nach dem 
Einstellen der Taktfrequenz einmal zu berechnen und in einer globalen 
Variablen (naja, file-static) zu speichern, und außerdem ist das auch 
noch in der falschen Reihenfolge (start müßte vor cycles gefüllt 
werden).

von Daniel V. (voda) Benutzerseite


Lesenswert?

aSma>> schrieb:
> Man hat dir doch schon einmal einen Tipp gegeben, dass die Funktion
> generated_us() nicht aufgerufen wird.

Stimmt, irgenwie untergegangen

Nop schrieb:
> Ein Register, welches synchron zum CPU-Takt hochzählt. Ist im Reference
> Manual leider nur schwammig erwähnt. Praxisbeispiel siehe hier:

Danke Euch. Muss ich jetzt mal prüfen.

von Daniel V. (voda) Benutzerseite


Lesenswert?

So, diese µs-Funktion habe ich aus diesem amerikanischen Blog 
abgeleitet:

http://micromouseusa.com/?p=296

Da könnt ihr mal sehen, was für eine Scheiße im Netz gepostet wird. Nach 
eingehender Untersuchung (und das hätte mir auch bei der Implementierung 
auffallen sollen) wird nie die Funktion generated_us(void) aufgerufen.

Meine Anfängerfrage an euch: Wie repariere ich das jetzt? Oder ist der 
Weg mit dem TIMs oder DWT vielversprechender? Ist das wirklich so 
aufwendig, einen us-Delay zu erzeugen ?

Danke und euch schönen Sonntag
Daniel

: Bearbeitet durch User
von Codechaos (Gast)


Lesenswert?

Daniel V. schrieb:
> Ist das wirklich so aufwendig,
> einen us-Delay zu erzeugen ?

Was ist so schwer zu erkennen dass du mit
1
SysTick_Config (SystemCoreClock / xxxx));

ein nahezu beliebiges Zeitraster erzeugen kannst? Der bereitzustellende

1
void SysTick_Handler(void)
2
{
3
  ticker++;
4
}

zählt dein Zeitraster immer weiter, der Rest ist eine selbst-
geschriebene delay_xxx(...) Funktion die dein Zeitraster abzählt
und eine Differenzabfrage ausführt.

von Daniel V. (voda) Benutzerseite


Lesenswert?

Das ist richtig aber wenn ich den Nenner erhöhe, ist der Controller nur 
noch damit beschäftigt, den Interrupt auszulösen und die entsprechende 
Variable zu inkrementieren, oder verstehe ich das jetzt falsch?

von grundschüler (Gast)


Lesenswert?

Es gab irgendwo mal code von pd für eine arm-delay-funktion.

ich hab den systick auf einem f103 mal durchprobiert. Ging runter bis 
10us aber dann ist die mcu doch sehr mit dem systick beschäftigt. 
Sinnvoll scheint mir systick mit 1ms.

für us-delays ist der systick damit nicht geeignet. Einfach und 
ausreichend genau geht es -wie bereits mehrfach ausgeführt- mit einer 
einfachen Schleife:
1
void _delay_ms(long ms){
2
  //genau auf 100sec
3
  //bei 72Mhz F_CPU=6536
4
  //bei 100Mhz F_CPU=9078
5
uint32_t i,j=xF_DELAY*ms;
6
  for(i=0;i<j;i++);
7
}
8
void _delay_us(long us){
9
uint32_t i,j=xF_DELAY*us/1000;
10
  for(i=0;i<j;i++);
11
}

von Thomas E. (picalic)


Lesenswert?

grundschüler schrieb:
> void _delay_ms(long ms){
>   //genau auf 100sec
>   //bei 72Mhz F_CPU=6536
>   //bei 100Mhz F_CPU=9078
> uint32_t i,j=xF_DELAY*ms;
>   for(i=0;i<j;i++);
> }
> void _delay_us(long us){
> uint32_t i,j=xF_DELAY*us/1000;
>   for(i=0;i<j;i++);
> }

Wenn ich der Compiler wäre, würde ich diesen Unsinn einfach aus dem 
Programm 'rausschmeissen, weil er nichts sinnvolles macht.

von Codechaos (Gast)


Lesenswert?

Daniel V. schrieb:
> Das ist richtig aber wenn ich den Nenner erhöhe,

Ja, wenn du schon so genau Bescheid weisst dürfte es dir doch
nicht schwer fallen einen Timer deines Controllers auf
1us Intervall (ohne Interrupt) zu programmieren und diesen
bei Bedarf zyklich abzufragen bis dein gewünschtes Zeitintervall
erreicht ist.

von aSma>> (Gast)


Lesenswert?

Daniel V. schrieb:
> Meine Anfängerfrage an euch: Wie repariere ich das jetzt? Oder ist der
> Weg mit dem TIMs oder DWT vielversprechender? Ist das wirklich so
> aufwendig, einen us-Delay zu erzeugen ?

Lösche die Variable:
>volatile uint32_t us;

Rufe stattdessen us_generated():
1
uint32_t generated_us(void)
2
{
3
   return ticker * 1000 + 1000 - SysTick->VAL/80;
4
}
5
6
void delay_us(uint32_t nTime)
7
{
8
 uint32_t curTime = generated_us();
9
 while((nTime-(generated_us()-curTime)) > 0);
10
}

Das sind nur ein paar Zeilen Code aber verdammt nochmal sehr elegant 
gelösst. Mit Inlinen und Optimierung kommt man noch bei 24Mhz auf die 
1µs Auflösung.

von Thomas E. (picalic)


Lesenswert?

aSma>> schrieb:
> Das sind nur ein paar Zeilen Code aber verdammt nochmal sehr elegant
> gelösst.

Mag elegant aussehen, ist aber meiner Meinung nach nicht zuverlässig.
Wenn nämlich der Systick-Interrupt mal genau innerhalb der Berechnung 
dieser Zeile:
1
return ticker * 1000 + 1000 - SysTick->VAL/80;
auftritt, kann u.U. ein Wert um 1000 zuwenig zurückgeliefert werden.
Wenn dieser falsche Wert dann curTime zugewiesen wird, hat man 1000µs zu 
kurzes Delay bzw. gar keins.
Es könnte auch ein Wert um 1000 zuviel zurückgeliefert werden, je 
nachdem, ob zuerst ticker oder SysTick->VAL geholt wird.

: Bearbeitet durch User
von Daniel V. (voda) Benutzerseite


Angehängte Dateien:

Lesenswert?

@ aSma,

ich glaube das ist der Durchbruch. Ich habe Deine Lösung implementiert 
und ja, es scheint zu gehen.

Als Kontrolle toggle ich eine Debug-LED:
1
void test_timer(void)
2
{
3
  delay_us(75);
4
   DEBUG_LED_ON;
5
  delay_ms(100);
6
   DEBUG_LED_OFF;
7
}

Die entsprechende Pinansteuerung habe ich in den Header gepackt.
1
#define DEBUG_LED_ON  GPIOA->BSSRL |= (1<<12)
2
#define DEBUG_LED_ON  GPIOA->BSSRH |= (1<<12)

Nun habe ich das Oszi angeschmissen und bei den geforderten 75 µs messe 
ich 77,03 µs.

aSma>> schrieb:
> Das sind nur ein paar Zeilen Code aber verdammt nochmal sehr elegant
> gelösst.

Dann ist der Blogeintrag doch nicht so schlecht.

aSma>> schrieb:
>Mit Inlinen und Optimierung kommt man noch bei 24Mhz auf die
> 1µs Auflösung.

Mit Inlinern kenne ich mich leider noch nicht aus.

von Thomas E. (picalic)


Lesenswert?

aSma>> schrieb:
> return ticker * 1000 + 1000 - SysTick->VAL/80;

Warum überhaupt "SysTick->VAL/80"? Das wäre doch wohl nur bei einer 
Clockfrequenz des Systick-Timers von 80 MHz korrekt, oder sehe ich das 
falsch?

von Daniel V. (voda) Benutzerseite


Lesenswert?

Thomas E. schrieb:
> Warum überhaupt "SysTick->VAL/80"? Das wäre doch wohl nur bei einer
> Clockfrequenz des Systick-Timers von 80 MHz korrekt, oder sehe ich das
> falsch?

Richtig, der Controller läuft auch auf 80 MHz.

: Bearbeitet durch User
von Thomas E. (picalic)


Lesenswert?

Ok,
1
return ticker * 1000 + 1000 - SysTick->VAL/(SystemCoreClock/1000000UL);
wäre etwas universeller.

Nachtrag zu der "eleganten" Lösung aus dem Blog:
Dieser Poster (http://micromouseusa.com/?p=296#comment-532728) hat ja 
auch schon auf das Problem hingewiesen. Ich würde mal behaupten, 
insgesamt ist diese relativ komplizierte Funktion, dann auch noch mit 
der Notwendigkeit, die Interrupts zu sperren, weniger elegant, als 
einfach einen freilaufenden Timer zu benutzen (µs oder CPU-Zyklen).

: Bearbeitet durch User
von Codechaos (Gast)


Lesenswert?

Codechaos schrieb:
> einen Timer deines Controllers auf
> 1us Intervall (ohne Interrupt) zu programmieren und diesen
> bei Bedarf zyklich abzufragen bis dein gewünschtes Zeitintervall
> erreicht ist.

Es scheint sich niemand Gedanken darüber zu machen dass bei solchen
Rechnereien

Thomas E. schrieb:
> return ticker * 1000 + 1000 - SysTick->VAL/(SystemCoreClock/1000000UL);

eine Menge Overhead anfällt. Wozu die Mikrosekunden berechnen wenn
es allein schon durch die Rechnung falsch wird? Dann kann man ja
gleich leerlaufende kalibrierte Schleifen zählen lassen.

von Daniel V. (voda) Benutzerseite


Angehängte Dateien:

Lesenswert?

Thomas E. schrieb:
> Ok,return ticker * 1000 + 1000 -
> SysTick->VAL/(SystemCoreClock/1000000UL);
> wäre etwas universeller.

Jetzt sind es ca. 75 µs.

Damit kann man arbeiten. Leute, ich danke euch!

[EDIT]
Bei 100 µs sind es auch genau 100 µs.

Gruß
Daniel

: Bearbeitet durch User
von Thomas E. (picalic)


Lesenswert?

Achte aber darauf, daß in Deinem System keine Interrupt Service Routinen 
mit Laufzeiten im Bereich 1µs und länger auftreten können, sonst werden 
u.U. aus den 100 µs auch mal > 1 Stunde!

von sajjad yaghoubi (Gast)


Lesenswert?

some bug
change code to this solved problem:

.....................................................................
#include "Delay.h"

extern volatile uint32_t ticker;
volatile uint32_t us;

void SysTick_Init(void)
{
 if (SysTick_Config (SystemCoreClock / 1000)) //1ms per interrupt
  while (1);
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
  NVIC_SetPriority(SysTick_IRQn,0);
  ticker = 0;
}

uint32_t generated_ms(void)
{
  return ticker;
}

uint32_t generated_us(void)
{
   us = (ticker * 1000) + 1000 - SysTick->VAL/72;
   return us;
}

void delay_ms(uint32_t nTime)
{
 uint32_t curTime = ticker;
  while(nTime + curTime > ticker);
}

void delay_us(uint32_t nTime)
{
  uint32_t curTime;
  ticker=0;
  curTime = generated_us();
        while(nTime + curTime > generated_us());
}
...............................................
#ifndef __Delay_H
#define __Delay_H
#include "stm32f10x.h"
#include "stm32f10x_it.h"

void SysTick_Init(void);
u32 micros(void);
u32 millis(void);
void delay_ms(u32 nTime);
void delay_us(u32 nTime);
#endif
.......................................................
volatile uint32_t ticker;
void SysTick_Handler(void)
{
  ticker++;
}

Beitrag #4954367 wurde vom Autor gelöscht.
von aiCis (Gast)


Lesenswert?

// I use some thing simple for millis like that!

int main(void)
{

  while (1)
  {
      // elapsedMillis 1 starts ---------------------
      static const uint32_t interval_1 = 1000;

      static uint32_t timeElapsed_1 = 0;

      if(HAL_GetTick() - timeElapsed_1 > interval_1)
      {
          HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_6);

    timeElapsed_1 = HAL_GetTick();
      }
      // elapsedMillis 1 ends -----------------------


      // elapsedMillis 2 starts ---------------------
      static const uint32_t interval_2 = 500;

      static uint32_t timeElapsed_2 = 0;

      if(HAL_GetTick() - timeElapsed_2 > interval_2)
      {

         HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_7);

         timeElapsed_2 = HAL_GetTick();
      }
      // elapsedMillis 2 ends -----------------------
  }

}

von The Wonderer (Gast)


Lesenswert?

aiCis schrieb:
> // I use some thing simple for millis like that!

Oh how wonderful complicated, this is great news!

von aiCis (Gast)


Lesenswert?

how can this code make more simplest?

von Ingo Less (Gast)


Lesenswert?

Ich habe das so gelöst und bin damit zu frieden:
1
void MyDelayMS( uint32_t DelayInMS )
2
{
3
  MSDelayCounter = (DelayInMS * SYSTICK_IN_HZ) / 1000;
4
  while (MSDelayCounter);
5
}
6
7
void MyDelayUS( uint32_t DelayInUS )
8
{
9
  volatile uint32_t USDelayCounter = (DelayInUS * 168) / 10;
10
  while (USDelayCounter){USDelayCounter--;}
11
}
12
13
/* Timerinitialisierung */
14
void Init_SysTimer ( uint32_t Timerfrequenz )
15
{
16
  SysTick_Config( SystemCoreClock / Timerfrequenz );
17
}
18
19
void SysTick_Handler( void )
20
{
21
  
22
  // Userbutton
23
  if ( USER_BUTTON_PRESSED && !Debounce ){
24
    Debounce = DEBOUNCETIME;
25
    Flags.TorqueReversion = ON;
26
  }
27
28
  if ( MSDelayCounter ) MSDelayCounter--;
29
  if ( Debounce ) Debounce--;
30
}

Da siehst du auch, wie ich einen Taster entprelle ;)
Natürlich ist die MyDelayUS nicht so genau, als wenn sie mit einem Timer 
erzeugt worden wäre, aber das ist ganz oft nicht so schlimm. Die 
Magicnumber 168 ist zufällig die 168MHz, kann aber durch Probieren 
ermittelt werden

von aiCis (Gast)


Lesenswert?

But this code stopped main while loop, from programme free flow is best 
use timer counter switching multiple pins.
1
void MyDelayMS( uint32_t DelayInMS )
2
{
3
  MSDelayCounter = (DelayInMS * SYSTICK_IN_HZ) / 1000;
4
  while (MSDelayCounter);  // <<<---- this while loop stopped main while loop
5
}
6
7
void MyDelayUS( uint32_t DelayInUS )
8
{
9
  volatile uint32_t USDelayCounter = (DelayInUS * 168) / 10;
10
  while (USDelayCounter)  // <<<---- this while loop stopped main while loop
11
  {
12
     USDelayCounter--;
13
  }
14
}

von Ingo Less (Gast)


Lesenswert?

aiCis schrieb:
> But this code stopped main while loop
Of course it does. Thats what a delay does. Other option is using time 
slots....

If you use your version, you can not be sure, when you check your 
routine again. The jitter is massive

von aiCis (Gast)


Lesenswert?

The jittering is coming from system clock?

von Aigars C. (aigars_c)


Lesenswert?

1
//what you think about this solution?
2
3
static inline void waittick(uint32_t step)
4
{
5
static uint32_t tick0;
6
if (!step)
7
{
8
tick0 = SysTick->VAL;
9
return;
10
}
11
tick0 -= step;
12
while ((tick0 - SysTick->VAL) & ((SysTick_VAL_CURRENT_Msk+1)>>1));
13
}
14
15
16
// init SysTick:
17
SysTick->LOAD = SysTick_VAL_CURRENT_Msk;
18
SysTick->CTRL = SysTick_CTRL_ENABLE_Msk;
19
// init waittick
20
waittick(0);
21
// wait 1000 ticks
22
waittick(1000);

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.