Forum: Mikrocontroller und Digitale Elektronik Timerberechnung STM32


von Elton (Gast)


Lesenswert?

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:
1
void MX_TIM3_Init(void){
2
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
3
  TIM_MasterConfigTypeDef sMasterConfig = {0};
4
5
  htim3.Instance = TIM3;
6
  htim3.Init.Prescaler = 44999;
7
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
8
  htim3.Init.Period = 1999+1;
9
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
10
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
11
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
12
  {
13
    Error_Handler();
14
  }
15
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
16
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
17
  {
18
    Error_Handler();
19
  }
20
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
21
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
22
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
23
  {
24
    Error_Handler();
25
  }
26
}

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?

von Elton (Gast)


Lesenswert?

Vergessen zu schreiben: Bei der obigen Timereinstellung ist die Zeit 
6,60 ms.

von m.n. (Gast)


Lesenswert?

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.
1
#include "stdint.h"
2
#include "stm32f4xx.h"
3
#include "stm32f4xx_rcc.h"
4
5
#define TIMER_CLOCK     90000000
6
#define TEILER_10kHz       10000
7
#define VORTEILER_10kHz (TIMER_CLOCK/TEILER_10kHz)
8
#define PRIO_TIM3       15    // ganz klein
9
10
volatile uint32_t sekunden_takt;
11
12
// 1 s ISR mit T3 erzeugen
13
void init_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->PSC = VORTEILER_10kHz - 1;        // Vorteiler auf 10 kHz
20
  TIM3->ARR = TEILER_10kHz - 1;           // Teiler für 1 Hz
21
  TIM3->DIER = TIM_DIER_UIE;              // Interrupt freigeben
22
  TIM3->CR1 = TIM_CR1_CEN;                // und starten
23
} 
24
25
void TIM3_IRQHandler(void)                // Aufruf mit 1 Hz
26
{
27
  TIM3->SR = 0;                           // alles flags löschen
28
  sekunden_takt++;                        // beispielhafter Zähler
29
}

von Kevin M. (arduinolover)


Lesenswert?

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?

: Bearbeitet durch User
von Ben S. (bensch123)


Lesenswert?

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.

: Bearbeitet durch User
von Elton (Gast)


Lesenswert?

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?

von m.n. (Gast)


Lesenswert?

Ganz einfach:
1
#include "stdint.h"
2
#include "stm32f4xx.h"
3
#include "stm32f4xx_rcc.h"
4
5
#define TIMER_CLOCK     (SystemCoreClock/2) // typischer APB1-Takt
6
#define TEILER_10kHz     10000
7
#define VORTEILER_10kHz (TIMER_CLOCK/TEILER_10kHz)
8
#define PRIO_TIM3        8                // mittlere Priorität wegen 10 kHz
9
10
volatile uint32_t sekunden_takt;
11
12
// 10 kHz ISR mit T3 erzeugen
13
void init_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
void TIM3_IRQHandler(void)                // Aufruf mit 10 kHz
25
{
26
static uint32_t teiler;                   // 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.

von Elton (Gast)


Lesenswert?

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

von m.n. (Gast)


Lesenswert?

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
void T3_delay_us(uint32_t us)
5
{
6
  RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;     // Takt für TIM3 einschalten
7
  TIM3->CR1 = 0;                          // T3 erst stoppen
8
  TIM3->ARR = MHZ_TEILER - 1;             // für 1 µs
9
  TIM3->CR1 |= TIM_CR1_CEN;               // T3 starten
10
  TIM3->CNT = CALL_DELAY;                 // Funktionsaufruf kompensieren
11
  TIM3->SR = 0;                           // Überlauf flag löschen 
12
13
  while(us--) {                           // Warteschleife in µs
14
    while(!(TIM3->SR));                   // auf Überlauf warten
15
    TIM3->SR = 0;                         // und Flags wieder löschen
16
  }  
17
}
'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.

von Elton (Gast)


Lesenswert?

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.

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.