Forum: Mikrocontroller und Digitale Elektronik STM32 Timer: Interrupt bei jedem Inkrement/Dekrement


von Solocan Z. (solocan)


Angehängte Dateien:

Lesenswert?

Ich möchte ein Rotary encoder mit einem STM32F7 betreiben. Es soll 
einfach z.B. bis 100 zählen und bei jeder Wertänderung (Hoch- oder 
Runterzählen) ein Interrupt auslösen. Das Zählen funktioniert. Aber so 
eine Interrupt bei jedem Schritt kriege ich nicht hin. Ich kriege nur 
eine Interrupt beim Update (Overflow) hin.

Wie löse ich das denn am Besten?

Meine Konfig.:
1
static void MX_TIM1_Init(void)
2
{
3
4
  TIM_Encoder_InitTypeDef sConfig;
5
  TIM_MasterConfigTypeDef sMasterConfig;
6
7
  htim1.Instance = TIM1;
8
  htim1.Init.Prescaler = 0;
9
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
10
  htim1.Init.Period = 99;
11
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
12
  htim1.Init.RepetitionCounter = 0;
13
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
14
  sConfig.EncoderMode = TIM_ENCODERMODE_TI1;
15
  sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
16
  sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
17
  sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
18
  sConfig.IC1Filter = 0;
19
  sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
20
  sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
21
  sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
22
  sConfig.IC2Filter = 0;
23
  if (HAL_TIM_Encoder_Init(&htim1, &sConfig) != HAL_OK)
24
  {
25
    _Error_Handler(__FILE__, __LINE__);
26
  }
27
28
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
29
  sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
30
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
31
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
32
  {
33
    _Error_Handler(__FILE__, __LINE__);
34
  }
35
36
}

Danke voraus und viele Grüße!

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

Das wird sehr, sehr zittrig.

Einfachste Variante: Pin Change Interrupt auf beide Pins.

von Falk B. (falk)


Lesenswert?

Walter T. schrieb:
> Das wird sehr, sehr zittrig.
>
> Einfachste Variante: Pin Change Interrupt auf beide Pins.

Nein, siehe Drehgeber.

von Falk B. (falk)


Lesenswert?

Solocan Z. schrieb:
> Ich möchte ein Rotary encoder mit einem STM32F7 betreiben. Es soll
> einfach z.B. bis 100 zählen und bei jeder Wertänderung (Hoch- oder
> Runterzählen) ein Interrupt auslösen.

Warum? Das klingt nicht sonderlich sinnvoll. Weder bei langsamen 
Drehgebern mit manueller Bedienung und erst recht nicht bei schnellen 
maschinengetriebenen.

Für einen manuellen macht man das alles per Software und Polling in 
einem hinreichend schnellen Timer-Interrupt, siehe Drehgeber. Da 
braucht es keine spezielle Dekodierhardware im uC.

Bei einem schnellen maschinengetriebenen läßt man den Hardwaredekoder 
und Timer im uC die Arbeit machen und liest periodisch über einen 
anderen Timer
den Zählerstand aus. Mehr nicht. der Zäherl/Timer löst keinerlei 
Interrupts aus, bestenfalls beim Überlauf, der aber nie eintreten 
sollte.

von Walter T. (nicolas)


Lesenswert?

Falk B. schrieb:
> Nein, siehe Drehgeber.

Doch, siehe Drehgeber :-)

Daß das bei 99% der Drehgeber keine gute Idee ist, steht völlig außer 
Zweifel. Aber tatsächlich entspricht jedes Inkrement/Dekrement genau 
einem Zustandwechsel an einem Pin, und bei voller Abtastung entspricht 
jeder Zustandswechsel an einem Pin genau einem Inkrement/Dekrement. QED.

von Solocan Z. (solocan)


Lesenswert?

Ich habe gerade Schwierigkeiten, euch zu verstehen. Ihr meint, es meint 
keinen Sinn, Interrupts bzw. Hardware Unterstützung zu verwenden und ich 
soll alles per Software bzw. Polling machen. Das bei mehreren Encodern? 
Das müsst ihr mir aber genauer erklären...

In dem Link, sowie in euren Posts sind von "pin change interrupts" die 
Rede. STM32F7 hat allerdings Timer, die m.V. nur mit Hardware die 
Encoder zählen. Das Register ist da und der Wert wird hoch und 
runtergezählt. Auf Encoder ist auch Hardware-Prellung darauf. Ich sehe 
in LA saubere Flanken, es kommt auch schön rein in die Software, wenn 
ich den Wert per Polling lese.

Das einzige was mir fehlt ist Interrupt bei jedem Inkrement/Dekrement. 
Wie ich den Interrupt softwaretechnisch gestalte, ist noch völlig offen. 
Ich kann auch welche bei Bedarf vernachlässigen.

Schlimmstenfalls muss ich mit einem anderen Timer periodisch die 
Encoderwerte auslesen, bzw. überprüfen ob sich da was geändert hat. Ist 
besser als Hardcore-Polling aber immer noch deutlich schlechter als ein 
Interrupt, der mir meldet, wenn da was war...

von Falk B. (falk)


Lesenswert?

Solocan Z. schrieb:

> Ich habe gerade Schwierigkeiten, euch zu verstehen. Ihr meint, es meint
> keinen Sinn, Interrupts bzw. Hardware Unterstützung zu verwenden und ich
> soll alles per Software bzw. Polling machen.

Das kommt auf deine Anwendung an.

> Das bei mehreren Encodern?

Wenn die manuell bedient werden ist das kein Problem, die Abtastraten 
sind eher gering.

> in LA saubere Flanken, es kommt auch schön rein in die Software, wenn
> ich den Wert per Polling lese.

Super!

> Das einzige was mir fehlt ist Interrupt bei jedem Inkrement/Dekrement.

Wozu denn? Der Zähler zählt doch allein.

> Wie ich den Interrupt softwaretechnisch gestalte, ist noch völlig offen.
> Ich kann auch welche bei Bedarf vernachlässigen.

???

> Schlimmstenfalls muss ich mit einem anderen Timer periodisch die
> Encoderwerte auslesen, bzw. überprüfen ob sich da was geändert hat.

Das ist der Normalfall!

 Ist
> besser als Hardcore-Polling aber immer noch deutlich schlechter als ein
> Interrupt, der mir meldet, wenn da was war...

Nö.

Lies meinen Beitrag noch einmal und denk drüber nach.

von pegel (Gast)


Lesenswert?

Falk B. schrieb:
> Wozu denn? Der Zähler zählt doch allein.

Genau. Habe vor Kurzem auch mit Encodern experimentiert.
Auf Flanken Interrupt habe ich dann verzichtet, weil beim Pin Wechsel 
der Zähler noch nicht Auf/Ab gezählt hat und damit der Wert falsch ist.

Habe es dann wie bei der Tastenabfrage gemacht, einfach bei Systick bzw. 
einem Vielfachen davon den Zähler auswerten.

von UweBonnes (Gast)


Lesenswert?

STM32 kann Drehencoder direkt auswerten. Warum Klimmzuege?

von pegel (Gast)


Lesenswert?

UweBonnes schrieb:
> STM32 kann Drehencoder direkt auswerten. Warum Klimmzuege?

Falls Du mich meinst, ich habe den Timer in Encoder Mode benutzt, wollte 
aber wie der TE auch erst auf jeden Schritt ein Interrupt auslösen, da 
der Encoder eher selten gedreht wird, um den µC nicht unnütz zu 
belasten.

Lohnt aber nicht wirklich, da die zyklische Zähler Abfrage die 
einfachste und sauberste Lösung ist und auch nicht wirklich Rechenzeit 
kostet.

von Deph (Gast)


Lesenswert?

Heyho,

ich hab derzeit ein ähnliches Anliegen, dass ich aus einem STM32-µC bei 
jeder Timer-CNT-Änderung einen IR haben möchte.

Hintergrund:
Ich habe mir eine Uhr gebaut die ich Manuell per DiskEncoder stellen 
können möchte. Da ich allerdings mal mit Sleep-Modes rumspielen wollte 
(da vorher noch nie gemacht und interessant), wird nun mittels RTC-Alarm 
einmal zum Minutenwechsel ein Display-Update durchgeführt. Ich möchte 
daher keinen weiteren Timer ständig mitlaufen lassen, der mir die µC 
dann im 20-30 ms Takt weckt. Mit nem geringeren Wake-Takt (sagen wir 
einfach mal 1s) bekomme ich halt eine merkliche Verzögerung zwischen 
Encoder-Eingabe und Display-Ausgabe.

Der Einfachheit halber habe ich den CNT-Overflow auf 1440 (Minutenanzahl 
eines Tages) gesetzt. Deshalb kann ich auch nicht mit kleinen Steps 
einen Overflow erzeugen, den ich dann nutze um eine interne Zählvariable 
zu manipulieren. Daher macht es also in meinen Augen Sinn, dass bei 
jedem CNT-Change ein Interrupt fliegt. Dabei möchte ich dann nur mehr 
die aktuelle CNT-Value auslesen und auf Stunden und Minuten umrechnen. 
Die wiederrum brauch ich dann nur noch in die RTC schreiben, bevor ich 
mittels Display-Update-Flag den Sleep für einen Display-Update-Zyklus 
(Main-Durchlauf) aussetze.

Falls jemand weiß wie man das so hinbekommt, wäre ich äußerst dankbar. 
An der Stelle haben mir Datasheets und AppNotes von ST nicht wirklich 
weiter geholfen, da die auch nicht wirklich gut/übersichtlich 
geschrieben sind.

Gruß,
Deph

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Deph schrieb:
> Hintergrund:
> Ich habe mir eine Uhr gebaut die ich Manuell per DiskEncoder stellen
> können möchte. Da ich allerdings mal mit Sleep-Modes rumspielen wollte
Ich mache das so, dass ein Pinchange Interrupt den Controller erst mal 
aufweckt. Und der dann mit einem Timeout von einigen Sekunden in diesem 
Einstellmodus aktiv bleibt. Solange der Einstellmodus aktiv ist (der 
"Timeout-Watchdog" muss dazu natürlich immer wieder zurückgesetzt 
werden, wenn noch am Rad gedreht wird) , ist der PC Interrupt 
deaktiviert. Und erst, wenn die Einstellprozedur um und somit das 
Timeout abgelaufen ist, wird der PC Interrupt scharf geschaltet und der 
uC wieder schlafen geschickt.

: Bearbeitet durch Moderator
von Deph (Gast)


Lesenswert?

Hi Lothar,

erstmal vielen Dank für deine Antwort.

[code]... Pinchange Interrupt den Controller erst mal
aufweckt. Und der dann mit einem Timeout von einigen Sekunden in diesem
Einstellmodus aktiv bleibt.[\code]
Das Timeout machst du dann also direkt via Watchdog oder extra Timer. 
Das ist mir tatsächlich zu umständlich, weil ich 2 zusätzliche 
Interrupts/Hardwarebaugruppen implementieren muss die dann auch noch 
entsprechend ineinander greifen.

Ich hab mir aber beim ins Bett gehen überlegt, dass ich einfach 2 
CaptureCompare(CC)-Interrupts nutze für die ich jeweils die nächste 
Position (+- 1 Position) vorberechne und in die CC-Register schreibe. 
Ich denke, keine Person könnte so am Rad drehen ;), damit da mal eine 
Position verschluckt wird. Das liese sich dann bequem mit 4 zusätzlichen 
Zeilen Code umsetzen und damit auch die 2 Hardewarebaugruppen sowie 
deren Implementierungsaufwand sparen. Noch dazu weil ich keine 
Entprelllogik am Encoderpin hab (dafür hab ich ja das 
Encoder-Interface).

Ich weiß nicht wann ich dazu komme das auszuprobieren, aber wenn ich was 
weiß, geb ich nochmal kurz Rückmeldung, wie gut/schlecht das 
funktioniert.

Gruß,
Deph

von Peter D. (peda)


Lesenswert?

Deph schrieb:
> Das Timeout machst du dann also direkt via Watchdog oder extra Timer.

Warum kein neuer Thread für ein neues Thema!

Der Watchdog ist tabu, der Watchdog ist Watchdog und damit 
ausschließlich für die Sicherheit zuständig.
Timeouts macht man immer mit dem Systemtimer, z.B. über einen Sheduler 
oder ein RTOS.

Deph schrieb:
> Mit nem geringeren Wake-Takt (sagen wir
> einfach mal 1s)

Rechne doch erstmal nach, ob 1s eine deutliche Stromeinsparung bewirkt 
gegenüber z.B. 50ms.
Ich bin mir sicher, das lohnt nicht. Dafür bereiten solche elend lange 
Zeiten schnell Ärger.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Deph schrieb:
> Das Timeout machst du dann also direkt via Watchdog oder extra Timer.
Nein, das "Timeout" ist einfach nur ein Zeitstempel, der mit dem immer 
durchlaufenden 1ms-Timertick verglichen wird.

> dass ich einfach 2 CaptureCompare(CC)-Interrupts nutze
Ich mache so wenig wie möglich mit Interrupts. Weil die nämlich immer 
dann kommen, wenn man sie nicht brauchen kann. Man kennt es aus dem 
täglichen Leben: natürlich klingelt der Paketbote seinen Interrupt 
gerade dann, wenn du beim Pinkeln bist. Und solange du dann grade noch 
so abgewürgt beim Pakteboten stehst (Händewaschen wird eh' 
überbewertet!), klingelt die Schwiegermutter ihren Interrupt mit dem 
Telefon.
Und wenn du einen der Interrupts "erstmal" ignorierst, kannst du 
hinterher dein Paket am anderen Ende der Stadt abholen oder die 
Schwiegermutter ist einen Monat lang sauer auf dich.

> Ich denke, keine Person könnte so am Rad drehen ;)
Ich lass dir mit einem kleinen ESD-Impuls dank meiner neuen Gummischuhe 
dann gleich beide Interrupts triggern. Und mit dem brutzelnden Schalter 
der daneben liegenden Steckerleiste gleich ein paar Mal 
hintereinander...


Deph schrieb:
> [\code]
Du musst nur eine spitze Klammer '>' an den Zeilenanfang setzen, dann 
klappt das auch mit dem Zitieren...

: Bearbeitet durch Moderator
von Deph (Gast)


Lesenswert?

Heyho,

@Peter D.
> Warum kein neuer Thread für ein neues Thema!
Versteh ich nicht. Das Thema ist doch "STM32 Timer: Interrupt bei jedem 
Inkrement/Dekrement". Also lass uns mal gemeinsam die Liste durchgehen:
- Ich hab nen STM32 -> ✓
- Ich nutz den Timer als Encoder-Interface -> ✓
- Ich möchte bei jedem CNT-Change nen Interrupt -> ✓

Sieht für mich soweit korrekt aus und auf Beiträge wie "Benutzt die 
SuFu" statt ner Lösung kann ich verzichten.


@Lothar
> ...spitze Klammer '>' an den Zeilenanfang...
Jupp, vielen dank für den Hinweis :)


> Ich mache so wenig wie möglich mit Interrupts...
Klingt für mich eher danach, als ob du deinen ganzen Code in deine ISRs 
packst. Sowas versuch ich zu vermeiden, wo es nur geht, weil Probleme, 
wie du bereits angemerkt hast.
Mein Handler sieht daher so aus:
1
void TIM1_CC_IRQHandler(void)
2
{
3
  TIM1->SR &= ~TIM1_SR_CC1_IF; // Clear IR
4
  // static callCnt = 0;
5
  // callCnt++;
6
  if (TIM1->SR & (CC1IRF | CC2IRF) == TIM1_CC_BOTHEDGES)
7
  {
8
    counterVar = (TIM1->CNT + 1) / DISKENCODER_COUNTS_PER_STEP; // Error due to upward-count -> +1; /-Operator truncates back to int
9
    // callCnt = 0;
10
    SCB->SCR &= ~SCB_SCR_SLEEP_ON_EXIT; // Wake µC for 1 main-cycle
11
  }
12
}
Ich setze also nur die aktuelle Zählzeit in ner Variable und sag dem µC 
er soll wach bleiben, wenn er den ISR verlässt (so sehen übrigens alle 
meine ISRs aus). Den Rest macht dann mein Main. Ich hatte auch bereits 
eine "callCounter" Variable mit drin (auskommentiert) und hab innerhalb 
der if per Breakpoint angehalten. Bisher konnte ich aber keinen Wert >1 
beobachten.

Zieh ich also mal deine Vergleichsweise heran, dann schreib ich mir nen 
Post-It und kümmere mich darum, wenn Zeit dafür ist.

Aber egal. Ich hab auf jeden Fall nun was ich wollte. Ich bekomme bei 
jedem Change einen Interrupt. Der zählt zwar in einer Richtung 
blöderweise meistens um 1 zu wenig, aber das hab ich mit der 
"Fehlerkorrektur" (int)((Zähler+1)/CountsPerStep) für alle Fälle 
abdecken können. Und der Code sollte auch kurz genug sein, um nur wenig 
ins Gewicht zu fallen.

Nichtsdestotrotz Danke für deine Beiträge :).

Grüße,
Deph

von Christopher J. (christopher_j23)


Lesenswert?

Man kann bei den STM32 auch Timer cascadieren, d.h. den Trigger-Out 
(TRGO) des einen Timers (Master) als Trigger-Input (TRGI) des anderen 
Timers (Slave) verwenden.
Wenn man dann das Update-Event des Masters als TRGO-Signal wählt und den 
TRGI des Slave als Clock-Source, dann hat man im Endeffekt den 
Master-Timer als Prescaler für den Slave und bei jedem Update-Event des 
Masters zählt der Slave-Counter um eins hoch.

Genaueres findest du in AN4013 "STM32 cross-series timer overview" in 
Kapitel 3 "timer synchronisation".

von M. H. (deph)


Lesenswert?

Hallo Christopher,

> ... Timer cascadieren ...
Ich hab es jetzt nicht überprüft, aber kann gut sein. Allerdings brauch 
ich dann für nen Timer-Sync wieder bereits zwei Timer.
Ich hab mir halt einfach für meine Projekte als Ziel gesetzt immer so 
wenig Ressourcen zu verbrauchen wie nur möglich - sowohl Hard- als auch 
Softwaretechnisch. Einfach nur, weil man dabei alles ausreizen muss was 
geht und man dadurch außerordentlich viel über Begrenzungen lernt. Bspw. 
ab wann es Sinn macht mehr Software (evtl. auch auf Kosten der Hardware) 
zu bauen. Ich überlege derzeit nämlich die Software mit nem Eventsystem 
aufzublähen, da ich viele verschiedene Modi habe, die ich dann mittels 
Eventsystem auf die Interrupts (de-)registrieren kann. Das würde die 
IR-Verwaltung für mich, als Programmierer, um einiges vereinfachen. Ich 
muss allerdings dazu erwähnen, dass ich weder Hard- noch Software ans 
Limit bringe (bei weitem nicht). Nichtsdestotrotz ist es aber 
interessant zu sehen, was dabei rauskommt, wenn man so tut, als wäre es 
so ;).

Ich hab das Stückchen Code von oben allerdings nur aus Gedanken schnell 
nachgetippt und vorhin beim Testen bemerkt, dass die Error-Correction 
Blödsinn ist. Grund ist:
1
+1 // hebt den Upcounting-Zählfehler des Encoders auf
2
/DISKENCODER_COUNTS_PER_STEP // hebt dgen +1 Fehler beim "richtigen" Zählen des Encoders auf
Jedoch fehlt noch ein
1
*DISKENCODER_COUNTS_PER_STEP // Hebt den /4 Fehler auf
um die Skalierung von der Divisions-Operation aufzuheben.


Gruß,
Deph

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Hier wird immer davon abgeraten, solche Eingabegerät via Pin-Change 
Interrupt abzufragen. Wenn er prellt hast du nicht unter Kontrolle, wie 
viele Interrupts er auslöst. Das kann dein ganzes System zeitweise lahm 
legen.

Normalerweise fragt man solche Dinger in regelmäßigen Intervallen ab, 
z.B. jede ms. Entprellen und Zählen sind dann Aufgabe der Software.

Nachtrag: Ach Käse, ich habe auf eine 2 Jahre alte Frage geantwortet.

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.