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
externvolatileuint32_tticker;
4
volatileuint32_tus;
5
6
voidSysTick_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_tgenerated_ms(void)
16
{
17
returnticker;
18
}
19
20
uint32_tgenerated_us(void)
21
{
22
/* us = ticker*1000+(SystemCoreClock/1000-SysTick->VAL)/80; */
23
us=ticker*1000+1000-SysTick->VAL/80;
24
returnus;
25
}
26
27
voiddelay_ms(uint32_tnTime)
28
{
29
uint32_tcurTime=ticker;
30
while((nTime-(ticker-curTime))>0);
31
}
32
33
voiddelay_us(uint32_tnTime)
34
{
35
uint32_tcurTime=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
externvoidSysTick_Init(void);
8
externvoiddelay_ms(uint32_tnTime);
9
externvoiddelay_us(uint32_tnTime);
10
#endif
Zudem habe ich in einer Funktion die SysTick_Handler:
1
voidSysTick_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
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
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_tgenerated_us(void)
2
{
3
/* us = ticker*1000+(SystemCoreClock/1000-SysTick->VAL)/80; */
4
us=ticker*1000+1000-SysTick->VAL/80;
5
returnus;
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
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.
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.
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.
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
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?
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.
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).
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.
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
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
voidSysTick_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.
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?
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:
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.
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.
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_tgenerated_us(void)
2
{
3
returnticker*1000+1000-SysTick->VAL/80;
4
}
5
6
voiddelay_us(uint32_tnTime)
7
{
8
uint32_tcurTime=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.
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
returnticker*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.
@ 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
voidtest_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.
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?
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.
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).
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.
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
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!
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
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