Forum: Mikrocontroller und Digitale Elektronik STM32F446 - Timer kommt zu oft?


von Wühlhase (Gast)


Angehängte Dateien:

Lesenswert?

Nachdem ich hier unlängst so viel nützliche Hilfe erhalten habe, möchte 
ich euch gerne noch ein Problem vorlegen-ich hoffe, es gibt eine 
einfache Erklärung.

Ich hab einen Timer (Tim6) auf einem STM32F446 am Laufen, der regelmäßig 
einen Interrupt reinwirft. Die Taktkonfiguration hab ich nicht 
angefaßt-kommt noch. Der Timer soll regelmäßig zur Tasterauswertung 
genutzt werden.

Jetzt stehe ich vor einem Rätsel: Der Timer kommt praktisch sofort und 
dauernd, obwohl er mit den momentanen Einstellungen völlig unbrauchbar 
lahm sein müßte.
1
void initTimer(){
2
  //Initialize timer6 for check user buttons per periodic interrupt
3
  RCC -> APB1ENR |= RCC_APB1ENR_TIM6EN;
4
  TIM6 -> DIER |= TIM_DIER_UIE;
5
  TIM6 -> PSC = 65000;
6
  TIM6 -> ARR = 1;
7
}

Selbst wenn der Timer mit 45MHz (maximaler Takt auf dem jeweiligen 
Peripherie-Bus) angehauen wird, dürfte der Interrupt nur alle 20s oder 
so kommen.

Dafür hab ich noch einen interessanten Effekt in der main:
1
void main(){
2
  initClock();
3
  initNVIC();
4
  initPins();
5
  initTimer();
6
7
  TIM6 -> CR1 |= TIM_CR1_CEN;
8
  int i;
9
  //Test Pin Init
10
  for(;;){
11
    //empty loop
12
    i++;
13
  }
14
}

Eigentlich möchte ich gerne in der Dauerschleife bleiben. Stattdessen 
rauscht er da ein einziges Mal durch-und das wars dann. Ich hab an der 
Stelle i++ einen Haltepunkt gesetzt, der zuverlässig nur ein Einziges 
Mal angesprungen wird. Die Zeile i++ war auch nur um zu vermeiden daß 
der Compiler das möglicherweise wegoptimiert...

Das ganze Projekt ist im Anhang. Ich hoffe jemand kennt das Problem...
Vielen Dank schonmal. :)

von Nico W. (nico_w)


Lesenswert?

Wühlhase schrieb:
> dürfte der Interrupt nur alle 20s oder so kommen.

Nicht ganz. Mindestens das ARR funkt dir aktuell dazwischen. Ich denke 
eher das der Interrupt mit 690Hz freuert.


Bin gerade Unterwegs am Handy und kann dein angehängtes Paket nicht 
ansehen. Den Handler hast du implementiert mit Flag löschen? Sonst 
springt er in den Default Handler und das wars dann.


Und mach ggf. dein i Mal volatile.

von Christopher J. (christopher_j23)


Lesenswert?

Wühlhase schrieb:
> void initTimer(){
>   //Initialize timer6 for check user buttons per periodic interrupt
>   RCC -> APB1ENR |= RCC_APB1ENR_TIM6EN;
>   TIM6 -> DIER |= TIM_DIER_UIE;
>   TIM6 -> PSC = 65000;
>   TIM6 -> ARR = 1;
> }
>
> Selbst wenn der Timer mit 45MHz (maximaler Takt auf dem jeweiligen
> Peripherie-Bus) angehauen wird, dürfte der Interrupt nur alle 20s oder
> so kommen.

Wenn der Prescaler deines Timers mit 45MHz befeuert wird, dann taktet 
dein Timer mit 45MHz/(PSC+1) ~= 45e6/65e3 Hz ~= 692Hz, so wie Nico 
gesagt hatte. Der Timer zählt dann von 0 bis ARR, löst dann einen 
Interrupt aus und fängt dann wieder bei 0 an. Der Interrupt feuert 
demnach mit ca. 350Hz, also etwa alle 3ms.

Bei PSC = 44999 würde dein Timer mit 1kHz takten und ARR = 499 würde 
dann zu einer Interruptfrequenz von 2 Hz führen. Beim Prescaler muss man 
aufpassen, dass der Teiler, der tatsächlich herauskommt PSC+1 ist, weil 
PSC=0 natürlich nicht durch Null teilt ;) Bei ARR ist das Ähnlich, weil 
der bei Null anfängt zu zählen. Bei großen Zahlen spielt das +1 nicht so 
die große Rolle aber wenn man einen Vorteiler von 2 will und PSC=2 setzt 
liegt man schon ordentlich daneben.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Du musst den Timer im Interrupt zurücksetzen, sonst wird der Interrupt 
sofort neu aufgerufen. Das muss direkt am Anfang der ISR passieren, 
sonst wirkt es nicht. Bei der Gelegenheit solltest du auch das UIF 
abfragen, um andere Events oder fälschlich aufgerufene ISR's 
auszufiltern:
1
void TIM6_DAC_IRQHandler(void){ // "void" vergessen!
2
  if ((TIM6->SR & TIM_SR_UIF) == 0) return;
3
  TIM6->SR = ~TIM_SR_UIF;
4
  // ...
5
}

Wenn du nicht möchtest dass der Timer-Interrupt sofort nach dem 
Einschalten (setzen von CEN) kommt, sondern erst nach einmaligem 
Ablaufen, musst du das Einschalten derart ändern:
1
TIM6->CR1 = TIM_CR1_CEN | TIM_CR1_URS;
2
__DSB ();
3
TIM6->EGR = TIM_EGR_UG;
4
__DSB ();
5
TIM6->CR1 = TIM_CR1_CEN;

Btw, "button1isHolded" ist Quatsch; es heißt "held" und nicht 
"holded"...

: Bearbeitet durch User
von Wühlhase (Gast)


Lesenswert?

Es lag tatsächlich am Rücksetzen des Interrupt-Flags. Hach, so langsam 
hab ich das Gefühl ich bekomme den STM32 gezähmt...das Teil macht mir 
immer Spaß. :)

Vielen Dank.

Noch eine Frage...um ein Bit in einem Register zu manipulieren gibt es 
ja gewisse Defines in der Bibliothek...z.B. 'GPIO_BSRR_BR0'

Daneben gibt es auch (fast?) immer sowas wie 'GPIO_BSRR_BR0_Msk' oder 
'GPIO_BSRR_BR0_Pos'.

Was hat es mit denen auf sich? Ich hab mir davon mal die Deklaration im 
stm32f446xx.h-file angesehen, aber ich kann damit nicht viel anfangen...

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Wühlhase schrieb:
> Daneben gibt es auch (fast?) immer sowas wie 'GPIO_BSRR_BR0_Msk' oder
> 'GPIO_BSRR_BR0_Pos'.

- "GPIO_BSRR_BR0_Msk" ist identisch zu "GPIO_BSRR_BR0" (ich glaube 
letzteres ist nur zur Abwärtskompatibilität vorhanden).
- "GPIO_BSRR_BR0_Pos" ist der Index des niedrigsten Bits des jeweiligen 
Bitfelds im Register. Das kann man nutzen um einen Wert an die richtige 
Stelle zu shiften.

Mit diesen Definitionen kann man variable Werte in Register schreiben. 
BSRR ist aber ein schlechtes Beispiel, daher hier mit TIM_CR1_CKD:
1
void setClockDivision (uint8_t ckd) {
2
  TIM2->CR1 = (TIM2->CR1 & ~TIM_CR1_CKD_Msk) | (((uint16_t) ckd) << TIM_CR1_CKD_Pos);
3
}
Oder auch Werte abfragen:
1
uint8_t getClockDivision (void) {
2
  return (TIM2->CR1 & TIM_CR1_CKD_Msk) >> TIM_CR1_CKD_Pos;
3
}

Etwas kürzer gehts mit den vordefinierten Makros:
1
void setClockDivision (uint8_t ckd) {
2
  MODIFY_REG(TIM2->CR1, TIM_CR1_CKD_Msk, ((uint16_t) ckd) << TIM_CR1_CKD_Pos);
3
}
4
uint8_t getClockDivision (void) {
5
  return READ_BIT(TIM2->CR1, TIM_CR1_CKD_Msk) >> TIM_CR1_CKD_Pos;
6
}

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.