Forum: Mikrocontroller und Digitale Elektronik STM32 Timer nutzen


von Olli Z. (z80freak)


Lesenswert?

Ich bräuchte mal etwas Hilfe beim Thema Timer im Umfeld der 
STM32CubeIDE. Habe nun schon mehrere Lehrvideos und Tutorials durch, 
aber irgendwie bekomme ich nicht die gewünschte Funktion.

Eigentlich klingt es ganz einfach:
- "TIM2" "Clock Source" auf "Internal Clock" stellen
- In den NVIC-Settings "TIM2 global interrupt" auf "Enabled" stellen
- In den "Parameter Settings" einen Prescaler und Counter Period 
einstellen (ich habe 32000 und 1000 gewhält um ein hoffentlich 1 
Sekunden Intervall zu erhalten)
- Code-Generator anwerfen, das erzeugt in "main.c" dann
1
TIM_HandleTypeDef htim2;
2
MX_TIM2_Init();
3
static void MX_TIM2_Init(void)
4
{
5
6
  /* USER CODE BEGIN TIM2_Init 0 */
7
8
  /* USER CODE END TIM2_Init 0 */
9
10
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
11
  TIM_MasterConfigTypeDef sMasterConfig = {0};
12
13
  /* USER CODE BEGIN TIM2_Init 1 */
14
15
  /* USER CODE END TIM2_Init 1 */
16
  htim2.Instance = TIM2;
17
  htim2.Init.Prescaler = 8000;
18
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
19
  htim2.Init.Period = 1000;
20
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
21
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
22
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
23
  {
24
    Error_Handler();
25
  }
26
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
27
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
28
  {
29
    Error_Handler();
30
  }
31
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
32
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
33
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
34
  {
35
    Error_Handler();
36
  }
37
  /* USER CODE BEGIN TIM2_Init 2 */
38
  /* USER CODE END TIM2_Init 2 */
39
}
- Einen ISR hinzugefügt:
1
/* USER CODE BEGIN 4 */
2
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
3
{
4
  HAL_GPIO_TogglePin(BP_LED_GPIO_Port, BP_LED_Pin);
5
}
6
/* USER CODE END 4 */
- Und schließlich
1
HAL_TIM_Base_Start(&htim2);
 vor der while(1) Schleife hinzugefügt um den Timer zu starten.

Dennoch wird mein ISR nie aufgerufen :-(

Ausgeführt wird der Code auf einem Blue Pill board.

: Bearbeitet durch User
von erklehr behr (Gast)


Lesenswert?

Olli Z. schrieb:
> Eigentlich klingt es ganz einfach:

Eigentlich ist es ganz einfach:

- Ganze minimalistische Soure posten die das Problem aufzeigt.
- *.ioc posten
- warten was da kommt.

von Johannes S. (Gast)


Lesenswert?

So auf den ersten Blick fehlt da noch ein HAL_TIM_Base_Start_IT().

von Olli Z. (z80freak)


Lesenswert?

Johannes S. schrieb:
> So auf den ersten Blick fehlt da noch ein HAL_TIM_Base_Start_IT().

AAAAARGH!!! Das wars, ich hatte nur ein "HAL_TIM_Base_Start();" drin, 
das "_IT" dahinter fehlte. Und schwubs - schon läufts! :-)

DANKE!

von Christopher J. (christopher_j23)


Lesenswert?

Olli Z. schrieb:
> In den "Parameter Settings" einen Prescaler und Counter Period
> einstellen (ich habe 32000 und 1000 gewhält um ein hoffentlich 1
> Sekunden Intervall zu erhalten)

Noch so als Hinweis:
Bei Prescaler 0 wird nicht durch Null "geteilt" und der "Counter" fängt 
bei 0 an zu zählen und nicht bei 1.

Bei Werten von 32000 und 1000 fällt das vielleicht nicht so auf, bei 
Werten von 12 und 3 aber schon ganz erheblich.

von Harry L. (mysth)


Lesenswert?

Olli Z. schrieb:
> Einen ISR hinzugefügt:/* USER CODE BEGIN 4 */
> void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
> {
>   HAL_GPIO_TogglePin(BP_LED_GPIO_Port, BP_LED_Pin);
> }
> /* USER CODE END 4 */

Da es nur einen Callback für alle Timer gibt, bei den Callbacks IMMER 
testen, ob auch tatsächlich dein Timer gemeint ist.
das gilt auch für UART, SPI, I2C usw.

Also so:
1
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
2
 {
3
   if(htim == &htim2)
4
     {
5
       HAL_GPIO_TogglePin(BP_LED_GPIO_Port, BP_LED_Pin);
6
     }
7
 }

: Bearbeitet durch User
von W.S. (Gast)


Lesenswert?

Olli Z. schrieb:
> - Einen ISR hinzugefügt:

Nein, das ist keine ISR (Interrupt Service Routine), sondern eine 
Callback-Funktion, die von irgendwo anders aufgerufen wird, aber es ist 
nicht die eigentliche ISR.

Ich würde sowas direkt machen und mich dabei nur nach dem RefManual 
richten. Das ist über alles gesehen sowohl übersichtlicher als auch 
einfacher.

Bedenke mal, was da bei dir überhaupt so abgeht: Anstatt daß du die ISR 
selber schreibst, mußt du zuerst dein Ansinnen in irgend eine 
Callback-Liste oder so eintragen lassen und später, wenn tatsächlich ein 
Interrupt kommt, muß die eigentliche ISR nachschauen, ob sie deine 
Callback-Funktion findet, ob selbige auch gültig ist, und und und - und 
dann wird erst deine Funktion aufgerufen. Mir wäre dieser Haufen an 
Indirektionen und Verkomplizierungen überhaupt nicht recht.

W.S.

von Harry L. (mysth)


Lesenswert?

W.S. schrieb:
> Anstatt daß du die ISR
> selber schreibst, mußt du zuerst dein Ansinnen in irgend eine
> Callback-Liste oder so eintragen lassen

Du hast offenbar absolut keine Ahnung, wie das bei HAL läuft.

NEIN!

Er muß keine Callback-Funktionen registrieren.
Die sind als __WEAK bereits vorhanden, und der Callback des TO 
"überschreibt" diese Funktion. (macht der Linker)

Und nochmal NEIN!

Er muß das NICHT selber neu schreiben.
Das funktioniert ganz hervorragend, wenn man es richtig macht.

von Johannes S. (Gast)


Lesenswert?

W.S. schrieb:
> mußt du zuerst dein Ansinnen in irgend eine Callback-Liste oder so
> eintragen lassen und später, wenn tatsächlich ein Interrupt kommt, muß
> die eigentliche ISR nachschauen, ob sie deine Callback-Funktion findet,
> ob selbige auch gültig ist, und und und

Nein, hier ist die Funktion weak deklariert und wird direkt in der ISR 
aufgerufen. Der Linker kennt die Adresse und da muss nix zur Laufzeit 
überprüft werden.
Die STM sind hier nur komplex mit ihren umfangreichen Sammelinterrupts, 
da muss erstmal der Auslöser gefunden werden.
Und eine generische Lösung über den Haufen zu werfen, nur um einige zig 
ns  pro Sekunde zu sparen, ist ja wohl eine sehr unsinnige Optimierung.
Und magic Numbers machen das auch nicht schneller.

von Olli Z. (z80freak)


Lesenswert?

Harry L. schrieb:
> Da es nur einen Callback für alle Timer gibt, bei den Callbacks IMMER
> testen, ob auch tatsächlich dein Timer gemeint ist.

Richtig, da hast Du natürlich völlig recht.

von Olli Z. (z80freak)


Lesenswert?

Christopher J. schrieb:
> Bei Prescaler 0 wird nicht durch Null "geteilt" und der "Counter" fängt
> bei 0 an zu zählen und nicht bei 1.
Hä? Mein Prescaler ist 8000.

von nico_2010 (Gast)


Lesenswert?

Hi, if you want to get 1 s interval you have to set prescaler to 7199 
(because SysClock is 72 MHz) and period to 9999.
Best regards

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Olli Z. schrieb:
> Hä? Mein Prescaler ist 8000.

Der wahre Prescaler ist immer Registerwert +1.
In deinem Falle teilst du den Takt daher durch 8001.
Seidenn der HAL hat intern ein -1, da muss man in den Code gucken.

W.S. schrieb:
> Nein, das ist keine ISR (Interrupt Service Routine), sondern eine
> Callback-Funktion, die von irgendwo anders aufgerufen wird, aber es ist
> nicht die eigentliche ISR.

Hach W.S. die wandelnde VOllpanne in diesem Forum.
Du kommst mir vor wie ein alter verbitterter Kerl mit nem Sprung in der 
Platte.
Schreibst immer das gleiche falsche Zeuchs und lässt Argumente von 
anderen nicht gelten.

Johannes S. schrieb:
> Und magic Numbers machen das auch nicht schneller.

Das hat W.S. genausowenig verstanden wie DMA.
Wer so exorbitant Magic Numbers nutzt sollte sich eh zurück halten wenn 
es um Code Arcitektur geht, weil er damit schon mitgeteilt hat, dass er 
nicht ordentlich programmieren kann.

von Nico_2010 (Gast)


Lesenswert?

"The true prescaler is always register value +1. In your case you divide 
the clock by 8001. Because the HAL has a -1 internally, you have to look 
into the code."

Nope! Because values starts from 0 you have to write "value_x - 1" to 
obtain the right prescaler value.
On the other hand, the user WS is right, the callback function is call 
from TIM_IRQ_Handler (located in file "stm32f1xx.it" so...

von Christopher J. (christopher_j23)


Lesenswert?

Mw E. schrieb:
> Der wahre Prescaler ist immer Registerwert +1.
> In deinem Falle teilst du den Takt daher durch 8001.
> Seidenn der HAL hat intern ein -1, da muss man in den Code gucken.

Nene, so obfuscated ist STs HAL dann doch wieder nicht ;)

Olli Z. schrieb:
> Christopher J. schrieb:
>
>> Bei Prescaler 0 wird nicht durch Null "geteilt" und der "Counter" fängt
>> bei 0 an zu zählen und nicht bei 1.
>
> Hä? Mein Prescaler ist 8000.

Das mit Prescaler 0 war eine Denksportaufgabe um dich darauf zu bringen, 
dass du als Prescaler-Wert 7999 einstellen muss, damit er durch 8000 
teilt. Beim Counter ists ähnlich, weil der bei 0 anfängt zu Zählen musst 
du N-1 ins ARR-Register schreiben, damit er N Timertakte pro Zyklus 
durchläuft.

von Cristi P. (nico_2010)


Lesenswert?

First of all, thank you for your -1 vote, truth hurts!
To be clear: The prescaler is off by 1 because it’s 0-based: a PSC value 
of “0” means to use a prescaler (clock divider) of 1.
On the other hand, you will find "void 
HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim)" in file "stm32f1xx_tim.c" 
and is called from "stm32f1xx_it.c" by "void TIM_IRQHandler(void)"
For OP: if you want to obtain 1 second pulse you may use this, for 
SYSCLK = 72MHz:
1
 htim1.Init.Prescaler = 7199;
2
    htim1.Init.Period = 9999;
Also you have to activate NVIC interrupt for e.g. TIM1, I mean "TIM1 
update interrupt" and start the timer with
1
HAL_TIM_Base_Start_IT(&htim1);
Best regards

von W.S. (Gast)


Lesenswert?

Harry L. schrieb:
> Du hast offenbar absolut keine Ahnung, wie das bei HAL läuft.
>
> NEIN!
>
> Er muß keine Callback-Funktionen registrieren.
> Die sind als __WEAK bereits vorhanden, und der Callback des TO
> "überschreibt" diese Funktion. (macht der Linker)
>
> Und nochmal NEIN!

Ach wo, du irrst hier gewaltig.

Du willst mir jetzt nicht wirklich einreden, daß das da:
1
- Einen ISR hinzugefügt:
2
/* USER CODE BEGIN 4 */
3
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
4
{..
5
}

eine ISR sei, gelle? Oder gibt es bei den µC von ST mittlerweile auch 
ISR, die einen Parameter abkriegen?
Nein, natürlich nicht.
Keine ISR dieser Welt hat irgendwelche Parameter, die sind alle
  void isrname(void);
und nicht anders.

So, ihr aufgeregten Pappnasen. Nebenbei gesagt, steht die Wahrheit auch 
direkt im Namen dieser Funktion, die der TO da als ISR ansieht. Es ist 
eben eine Callback-Funktion und da diese eben von woanders zurückgerufen 
werden soll (mit Parameter), muß besagte andere Stelle sie in 
irgendeiner Liste führen. Eben ganz GENAU DAS, was ich weiter oben 
bereits geschrieben habe.

W.S.

von W.S. (Gast)


Lesenswert?

Johannes S. schrieb:
> W.S. schrieb:
>> mußt du zuerst dein Ansinnen in irgend eine Callback-Liste oder so
>> eintragen lassen und später, wenn tatsächlich ein Interrupt kommt, muß
>> die eigentliche ISR nachschauen, ob sie deine Callback-Funktion findet,
>> ob selbige auch gültig ist, und und und
>
> Nein, hier ist die Funktion weak deklariert und wird direkt in der ISR
> aufgerufen. Der Linker kennt die Adresse und da muss nix zur Laufzeit
> überprüft werden.

HÄ?
Du schreibst wirr. Also nochmal zum nachlesen: Es gibt eine ISR, die 
wird niemals aufgerufen, sondern von der HW gestartet, wobei sie den 
gewöhnlichen Programmablauf unterbricht. Das mit dem WEAK ist was 
anderes, nämlich der Linker-Mechanismus, um bei im Programm unbenutzten 
Interrupts die als WEAK gekennzeichnete Default-ISR in die Vektortabelle 
einzutragen.

Was also soll dein Satz "Nein, hier ist die Funktion weak deklariert und 
wird direkt in der ISR aufgerufen." eigentlich sagen? Er ist in allen 
Teilen wirr.

W.S.

von Johannes S. (Gast)


Lesenswert?

das du keine Ahnung hast, das hast du schon oft genug bewiesen. Das es 
so schlimm ist hätte ich nicht gedacht. Schau doch einfach mal in den 
Quellcode. Es ist genauso wie Harry und ich geschrieben haben.
Und es können auch andere Funktionen als ISR mit weak gekennzeichnet 
werden.

von Harry L. (mysth)


Lesenswert?

W.S. schrieb:
> HÄ?
> Du schreibst wirr.

Nein, dir fehlen nur sämtliche Grundlagen zum Thema ST-HAL, und da du 
das auch nicht wissen willst, macht es auch wenig Sinn, dir das in 
epischer Breite zu erklären.
Die, die damit arbeiten, haben das verstanden.

Ok, ein le. Versuch:
Callback-Funktionen sind in der HAL so definiert:
1
__weak void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
2
{
3
  /* Prevent unused argument(s) compilation warning */
4
  UNUSED(hspi);
5
6
  /* NOTE : This function should not be modified, when the callback is needed,
7
            the HAL_SPI_TxCpltCallback should be implemented in the user file
8
   */
9
}

Und diese Funktionen werden aus der eigentlichen Interrupt-Routine 
aufgerufen. (direkt!)

Indem ich eine Funktion
1
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
2
{
3
  blablabla;
4
}

schreibe, wird ab sofort meine Funktion direkt aus der Interrupt-Routine 
aufgerufen.

Daß du "__weak" nicht kennst, wundert mich allerdings nicht wirklich....

von Johannes S. (Gast)


Lesenswert?

W.S. schrieb:
> Was also soll dein Satz "Nein, hier ist

Mit 'hier' war der Code vom TO gemeint. Man kann in der HAL per define 
noch eine andere Variante einstellen, in der wird eine Sprungtabelle 
benutzt. Damit ist es einmal indirekt, aber auch das ist keine Liste in 
der ISR wo erst ein Funktionszeiger gesucht und validiert werden muss. 
Der Vorteil ist dabei, das die (Callback)Funktion zur Laufzeit gesetzt 
werden kann ohne gleiche eine neue ISR bauen zu müssen. Die HAL 
Programmierer sind lange nicht so dumm wie du denkst.

von dunno (Gast)


Lesenswert?

Harry L. schrieb:
> Indem ich eine Funktion
> void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
> .............
> schreibe, wird ab sofort meine Funktion direkt aus der Interrupt-Routine
> aufgerufen.

Zwischenfrage: und wer generiert das Argument für den Callback?

von Harry L. (mysth)


Lesenswert?

dunno schrieb:
> Zwischenfrage: und wer generiert das Argument für den Callback?

Die Interruptroutine übergibt das DeviceHandle an die Callbacks.
Das wird gebraucht, um die Interrupt-Quelle eindeutig zu identifizieren.
Ansonsten wüsste ich bei meinem Beispiel zwar, daß der Callback vom SPI 
ausgelöst wurde, aber nicht von Welchem (wenn es mehr als einen gibt)

: Bearbeitet durch User
von dunno (Gast)


Lesenswert?

Harry L. schrieb:
> Die Interruptroutine übergibt das DeviceHandle an die Callbacks.

Und ich dachte die ISR ist ein "fleischloses" Wesen das nur
markiert dass ein spezifischer Interrupt aufgetreten ist aber
nichts über Pointer auf die entsprechende Hardware weiss. (?)

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.