Hallo zusammen,
entweder bin ich doof und übersehe etwas, aber ich bekomme es nicht hin,
vernünftig die Timer beim STM32 zu berechnen.
Kleines Beispiel wie ich vorgegangen bin:
Ich möchte gerne einen Sekundentimer konfigurieren. Dafür nehme ich auf
einen STM32F446 den TIM3. Das Updateevent berechnet sich wie folgt:
UPDATEEVENT = F_CLOCK/((PSC+1)*(PERIOD+1))
Der TIM3 liegt auf ABP1, welcher mit 90 MHz versorgt wird. Um also ein
Updateevent auszulösen ist meine Berechnung wie folgt:
UPDATEEVENT = 90000000/((44999+1)*(1999+1)) = 1 s
So habe ich es programmiert:
Okay und nun meine delay-funktion
[c]
void delay_s(volatile uint32_t seconds){
__HAL_TIM_SetCounter(&htim3,0);
while(__HAL_TIM_GET_COUNTER(&htim3)<seconds);
}
[\c]
In der main habe ich den Timer eingeschaltet
(HAL_TIM_Base_Start(&htim3))
Was ich verstanden habe:
Prescaler ist der Divisor mit welcher dieser heruntergeteilt wird. Ist
dieser 16 Bit ist der Maximalwert 2^16 also 65536. Die Periode ist der
Maximalwert bevor der Timer reloaded wird.
Alle anderen Timer stimmen, auch die Funktion HAL_Delay(1) gibt exakt 1
ms aus.
Wie werden die Timer berechnet?
Ich zeige Dir ein Beispiel, wie das ohne HAL-Gegurke aussehen kann.
Dabei wird zunächst der Vorteiler so eingestellt (PSC), daß T3 mit 10
kHz läuft. Dann noch der Nachladewert (ARR) so, daß die 10 kHz auf 1 Hz
reduziert werden.
Dein Timer läuft schon richtig deine Delay Funktion is Murks. Dein Timer
inkrementiert mit 2KHz und zählt bis 2000. Somit läuft er alle 1s über
und löst sofern aktiviert einen Interrupt aus.
Du gehst hin und setzt seinen Zählerstand auf 0 und watest bis er
"seconds" erreicht hat was beispielsweise bei 20 als wert für "seconds"
ca. 10ms dauert.
Deine Funktion macht jetzt auch nicht soviel Sinn da kannst du auch
gleich HAL_Delay benutzen. Und mal ehrlich wer will denn im
Sekundenbereich seine CPU blocken, da gibts doch schönere Varianten?
Ich würde auch den CNT immer gleichzeitig auf Maximum setzen, wenn ich
den Prescalerwert ändere - denn dieser wird nur bei einem Überlauf
übernommen soweit ich weiß.
Es ist hier immer einfacher einen Hardwaretimer als Clocksource zu
definieren, z.B. jede mS oder µS inkrementierend. Dann kann eine
Softwareklasse den CNT Wert als Zeitstempel benutzen, wovon du dann so
viele nicht blockierende Softwaretimer ableiten kann wie du möchtest.
Erst einmal vielen Dank!
Ja, wie gesagt, es ging mir in erster Linie um die Berechnung des Timers
und um das Verständnis. Eine Sekunde war nur ein Beispiel.
Es ist doch so, wenn der Überlauf, welche ich mit der Periode mit dem
Wert 2000 eingestellt habe, läuft dieser mit Erreichen von 2000 über und
beginnt mit 0 und die Variable im Handler wird hochgezählt, oder?
Wenn ich im Handler die Variable hochzähle, Wie muss denn so eine
Funktion aussehen?
#define PRIO_TIM3 8 // mittlere Priorität wegen 10 kHz
9
10
volatileuint32_tsekunden_takt;
11
12
// 10 kHz ISR mit T3 erzeugen
13
voidinit_T3(void)
14
{
15
RCC->APB1ENR|=RCC_APB1ENR_TIM3EN;// Takt für TIM3 einschalten
16
NVIC_EnableIRQ(TIM3_IRQn);// ISR freigeben
17
NVIC_SetPriority(TIM3_IRQn,PRIO_TIM3);// mit geringer Prioritaet
18
19
TIM3->ARR=VORTEILER_10kHz-1;// T3 erzeugt ISR-Aufrufe mit 10 kHz
20
TIM3->DIER=TIM_DIER_UIE;// Interrupt freigeben
21
TIM3->CR1=TIM_CR1_CEN;// und starten
22
}
23
24
voidTIM3_IRQHandler(void)// Aufruf mit 10 kHz
25
{
26
staticuint32_tteiler;// lokale Hilfsvariable
27
TIM3->SR=0;// alle flags löschen
28
teiler++;
29
if(teiler>=TEILER_10kHz){// teiler >= Endwert
30
teiler=0;// wieder auf Startwert
31
sekunden_takt++;// und Sekunde zählen
32
}
33
}
Es gibt noch eine weitere Möglichkeit, die eigentlich die beste ist.
Dabei läuft der Timer frei durch und die ISR wird von einem
Compare-Register ausgelöst, welche ständig inkrementiert wird. Der
Vorteil ist, daß der Timer noch für andere Funktionen nutzbar bleibt.
Super vielen Dank. Eine Frage noch, die Funktion rufe ich in der main
auf?
Also in der Form von delay_s(1) für ne Sekunde? Wie gesagt, die Sekunde
soll alle anderen Zeiten (µs, ms) repräsentieren :-)
So ganz habe ich nicht mitbekommen, was Du meinst.
Ich bin davon ausgegangen, daß primär ein Sekundenzähler per ISR laufen
soll. Dafür ist das Programm gedacht. Aufgerufen wird einmalig
'init_T3()' und danach läuft alles per ISR.
Das kleinste Raster bei meiner Lösung ist 100 µs. Wenn man die Konstante
'TEILER_10kHz' variabel macht, kann man im 100 µs-Raster kürzere oder
längere Intervalle erzeugen.
Aber das reicht Dir noch nicht?
Meist Du vielleicht ein blockierendes delay? Das würde so aussehen:
1
#define MHZ_TEILER 90 // für 90 MHz Takt
2
#define CALL_DELAY 0 // Für Anpassung der 1. µs
3
4
voidT3_delay_us(uint32_tus)
5
{
6
RCC->APB1ENR|=RCC_APB1ENR_TIM3EN;// Takt für TIM3 einschalten
'CALL_DELAY' ist der Vorgabewert für TIM3->CNT und muß so angepaßt
werden, daß die 1. µs genau ist und muß immer < 'MHZ-TEILER' sein!
Danach werden die restlichen µs abgezählt. Es wird kein Interrupt
benötigt, aber der Programmablauf ist blockiert. Maximal wird ca. 4000
Sekunden gewartet.
Ahh natürlich! Ich stand auf den Schlauch. Deine erste Lösung war eher
eine Zeitscheibe oder? Das heißt, alles was genau diese Zeit (z.B.
Pausenzeiten für die Abfrage eines Sensorwertes) benötigt, kommt in die
ISR, oder?
Blockierende Delays würde für ich vllt nur bei die Initialisierung von
irgendwelchen Peripherien (z.B. einen HD4478 oder DS18B20 o.Ä) benutzen.