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.:
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.
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.
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...
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.
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.
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.
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
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.
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
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.
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...
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
voidTIM1_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
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".
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
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.