Forum: Mikrocontroller und Digitale Elektronik STM32 Quadrature Encoder Z Index Reset


von Andreas T. (skycurve)


Lesenswert?

Hallo zusammen,

wie der Titel schon sagt, geht es um einen Encoder (1024 P/R).
Dieser wird zur Bestimmung der Rotorposition eines BLDC Motors 
verwendet.

A und B des Quadrature Encoder ist an einen Timer eines STM32F4 
angeschlossen.
1
GPIO_PinAFConfig(GPIOE,GPIO_PinSource9,GPIO_AF_TIM1); // A
2
GPIO_PinAFConfig(GPIOE,GPIO_PinSource11,GPIO_AF_TIM1); // B
3
...
4
TIM_EncoderInterfaceConfig(TIM1, TIM_EncoderMode_TI2, TIM_ICPolarity_Falling, TIM_ICPolarity_Rising);
5
TIM_ITConfig(TIM1,TIM_IT_Update , ENABLE);

Es soll zusätzlich die Z Phase ausgewertet werden, um ein Driften zu 
vermeiden.
Ich habe den Z Ausgang an einen Pin angeschlossen und eine IRQ für 
diesen Pin implementiert. Dort setze ich den Timer einfach wieder auf 
Null.
So habe ich keinen Drift.

Problem: Die ganze IRQ ist zu langsam und wenn der Timer zurückgesetzt 
wird, hat der Motor schon zu weit gedreht.
So läuft der Motor bei höheren Geschwindigkeiten nicht konstant da vom 
Encoder mal mehr, mal weniger zeitverschobener Positionswinkel ankommt.

Gibt es eine Möglichkeit, den Timer so zu konfigurieren, dass er per 
Hardware, ähnlich wie die A und B Eingänge, zurückgesetzt wird?
Habe dazu nichts gefunden. Sogar im Datenblatt wird es einfach 
empfohlen, die Z Auswertung 'billig' in eine IRQ zu werfen.

EDIT: Z Ausgang bei meinem Encoder ist negativaktiv und liefert an 
Position NULL einen Nadelimpuls.

Gruß
Andreas

: Bearbeitet durch User
von Rainer (Gast)


Lesenswert?

Hallo Andreas,

theoretisch sollte man den TImer über einne exteren Puls zurücksetzen 
können. Schau mal im Ref Manual in der Timerbeschreibung unter "Timers 
and external trigger synchronization", hier gibt es den Reset mode.
Ob der allerdings im Encodermode funktioniert kann ich dir nicht sagen.

Gruß
Rainer

von Uwe Bonnes (Gast)


Lesenswert?

Willst Du wirklich den Zähler bedingungslos bei jeder Umdrehung 
zurücksetzen? Ich würde eher den "Z-Impuls" als Capture Signal verwenden 
und den Zählerstand beim Z-Impuls in ein Capture Register einlesen. Erst 
wenn der Capture Wert inkonsistent ist, also micht um 1024 vom letzten 
Impuls abweicht, erst dann hast Du ein Problem und soltest es behandeln.

von Andreas T. (skycurve)


Lesenswert?

Vielen Dank für die Antworten.

@Rainer:
hier auf der Seite 244 ist es beschrieben:
http://www.st.com/web/en/resource/technical/document/reference_manual/CD00246267.pdf

habe auf die Schnelle leider nur ein Beispiel für Reset Mode gefunden:
https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Flat.aspx?RootFolder=https%3a%2f%2fmy%2est%2ecom%2fpublic%2fSTe2ecommunities%2fmcu%2fLists%2fcortex_mx_stm32%2fTimer%20slave%20mode%20%20Reset%20mode&FolderCTID=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C3CDE46A77F0FFD06506F5B&currentviews=547

Ich habe es ausprobiert und es scheint sich mit dem Encoder Mode nicht 
zu vertragen, hat es jemand schon mal geschafft, diese Modi gleichzeitig 
laufen zu lassen?

@Uwe:
Es spricht ja nichts dagegen, den Timer bei jedem Z Durchgang zu 
resetten.
Oder hast du es so gemeint, dass ich für den Z Ausgang einen 
zusätzlichen Timer verwenden soll, welcher die Z Impulse zählt?
An welcher Stelle sollte ich dann prüfen, ob die Timer noch konsistent 
laufen?
Bzw. bekomme ich dann nicht genauso das (Performance-)Problem, dass 
während dieser Konsistenz-Überprüfung mein Motor schon weiterläuft und 
ich den Timer quasi zu späte resette?

Gruß
Andreas

: Bearbeitet durch User
von Chris (Gast)


Lesenswert?

Ich würde über den Index Puls ein Capture Event auslösen inkl. ISR.
In der ISR kannst du dann die Differenz zwischen aktuellem Zählerstand 
und Capture wert berechnen und setzt den Timer auf diesen Wert (und 
nicht auf 0)

von Uwe Bonnes (Gast)


Lesenswert?

Ich verstehe nicht, warum Du bei jeder Umdrehung Fehler erwartest und 
daher den Encoderstand zuruecksetzten willst! Waere es nicht sinnvoller, 
Fehler im Encoder, die doch hoffentlich selten passieren, zu erkennen 
und komplexer zu reagieren?

Anderenfalls kannst Du den mit dem mit Z-Impuls erfassten Capturewert 
eine Korrektur fuer Deinen Encoder berechen. Fuer ein Zuruecksetzten 
synchron zum Z-Impuls hat bisher keiner eine Idee. Wenn Du den Wert in 
der Capture SR korrigierst, wie von Chris vorgeschlagen, ist der Motor 
natuerlich moeglicherweise wieder weitergelaufen wie mit dem Ruecksetzen 
in der ISR.

von Andreas T. (skycurve)


Lesenswert?

Ich hätte vielleicht dazu schreiben müssen, dass ich den Timer nur bis 
2047 (1024+1024, A, B) zählen lasse. Sprich, der muss pro Umdrehung 
sowieso ein mal wieder auf Null kehren.

Chris schrieb:
> In der ISR kannst du dann die Differenz zwischen aktuellem Zählerstand
> und Capture wert berechnen und setzt den Timer auf diesen Wert

Uwe Bonnes schrieb:
> ist der Motor
> natuerlich moeglicherweise wieder weitergelaufen wie mit dem Ruecksetzen
> in der ISR

Ich denke auch, dass es leider genauso zu den oben genannten Performance 
Problemen führt. (Motor schon weitergelaufen)
Schade, dass ARM, bzw ST sich hier nichts besseres einfallen hat lassen, 
außer ISR zu verwenden.

Ich hätte noch eine allg. Frage zu den Encodern. Ich verwende diesen 
hier:
https://www.sparkfun.com/products/11102

Der Z Ausgang ist auf deutsch gesagt die ganze Zeit HIGH und macht an 
einer bestimmten Wellenposition jede Umdrehung einen Spike nach LOW. Auf 
diesen Spike kann ich aber nicht einmal mein Scope triggern lassen, auch 
nicht bei niedrigen Drehzahlen.
(Tektronix DSO)
Die ISR des stm32 Timers auf dem Z Pin dagegen reagiert auf diesen Spike 
bei niedrigen Drehzahlen wunderbar.
Wenn ich in der ISR beispielsweise einen anderen Pin toggle, kann ich 
mein Scope auf diesen anderen Pin triggern.
Aber wie gesagt, nur bei niedrigen Drehzahlen, wenns schneller wird, 
werden auch hier Umdrehungen verpasst.
Mit Schnell meine ich aber auch nur 1000rpm an der Welle, was ja für die 
ISR und den Z Pin ja eig. ein Witz ist.
Dieses Verhalten scheint mir nicht normal zu sein nicht wahr?
Vielleicht ist es weniger ein Performance Problem wie ein Fehler in 
meinem Encoder?
Auf dem "Datenblatt" Seite 5 sieht Z auch nicht wie ein Nadelimpuls aus.


Gruß
Andreas

: Bearbeitet durch User
von anon (Gast)


Lesenswert?

Andreas True schrieb:


> Ich denke auch, dass es leider genauso zu den oben genannten Performance
> Problemen führt. (Motor schon weitergelaufen)
> Schade, dass ARM, bzw ST sich hier nichts besseres einfallen hat lassen,
> außer ISR zu verwenden.

Also das wundert mich nun doch. Bei 1000 rpm und einen 1024 bit Encoder 
kommt das Encoder Signal mit etwa 35 kHz, das sollte doch mehr als genug 
Zeit für einen STM32 sein um da zwischen zwei Flanken über ein Interrupt 
einen Counter zurückzusetzen.

Wobei auch mir das Konzept den Counter des Encoders bei jeder Umdrehung 
zurückzusetzen etwas seltsam erscheint. Warum macht man das?

von Andreas T. (skycurve)


Lesenswert?

Es sieht für mich logisch aus, wenn der Counter pro Umdrehung um 2048 
erhöht wird, diesen einfach an der Nullposition (Z Durchgang) wieder auf 
Null zu setzen und nur bis 2048 zählen zu lassen. So kann ich zu jeder 
Zeit direkt die absolute Rotorposition ablesen.
Natürlich würde es über verUNDen genau so funktionieren, den Timer 
einfach weiter laufen zu lassen und trotzdem die Position zu bekommen. 
Aber ich sehe da keinen Grund dafür.
Ist diese Vorgehensweise unüblich?

: Bearbeitet durch User
von Uwe Bonnes (Gast)


Lesenswert?

Andreas True schrieb:
> Es sieht für mich logisch aus, wenn der Counter pro Umdrehung um 2048
> erhöht wird, diesen einfach an der Nullposition (Z Durchgang) wieder auf
> Null zu setzen und nur bis 2048 zählen zu lassen. So kann ich zu jeder
> Zeit direkt die absolute Rotorposition ablesen...

Fuer mich ist das nicht logisch. Nach 8 Umdrehungen hast Du doch das 
Ruecksetzen automatisch, jedenfalls bei den STM32 16-bit Zaehlern.

Und die absolute Position innerhalb einer Umdrehung hast Du auch ohne 
das Zuruecksetzen wenn Du mit 0x3fff verundest.

von Andreas T. (skycurve)


Lesenswert?

Jap, habe ich oben auch dazugeschrieben. Wenn ich aber per Z Durchgang 
auf 0 setze, habe ich immer an der selben physischen Position den selben 
Timer Counter Wert.

Aber findest du von mir oben beschrieben Verhalten nicht auch seltsam?

Andreas True schrieb:
> https://www.sparkfun.com/products/11102
>
> Der Z Ausgang ist auf deutsch gesagt die ganze Zeit HIGH und macht an
> einer bestimmten Wellenposition jede Umdrehung einen Spike nach LOW. Auf
> diesen Spike kann ich aber nicht einmal mein Scope triggern lassen, auch
> nicht bei niedrigen Drehzahlen.
> (Tektronix DSO)
> Die ISR des stm32 Timers auf dem Z Pin dagegen reagiert auf diesen Spike
> bei niedrigen Drehzahlen wunderbar.
> Wenn ich in der ISR beispielsweise einen anderen Pin toggle, kann ich
> mein Scope auf diesen anderen Pin triggern.
> Aber wie gesagt, nur bei niedrigen Drehzahlen, wenns schneller wird,
> werden auch hier Umdrehungen verpasst.

von anon (Gast)


Lesenswert?

Andreas True schrieb:
> Aber findest du von mir oben beschrieben Verhalten nicht auch seltsam?

Ja, das Teil ist wohl kaputt. Du hast ja selbst schon erkannt, dass Z 
sich nicht so verhält wie er es nach Datenblatt sollte.

von Alex E. (tecnologic) Benutzerseite


Lesenswert?

Moin,

Das Problem ist nicht der Ausgang sondern dessen Beschaltung!!
Datenblatt des Sensors Seite 5. Meine Zeichendeutung ist nicht gut, aber 
der Schaltplan für den Z-Pin deutet für mich darauf hin das der PIN 
deinen Pullup nicht treiben kann. Prüfe das mal bitte ob deine Externe 
Beschaltung des Encoders richtig ist.

von Andreas T. (skycurve)


Angehängte Dateien:

Lesenswert?

Hi,

Auf Seite 5 habe ich den A6B2-CWZ3E Encoder.

Es steht zwar irgendwo dass die Ausgänge open collector sind, aber die 
Schaltung auf Seite 5 deutet ja darauf hin, dass der PullUp schon 
integriert ist, weil ja noch ein Rahmen rundum ist.
Oder sehe ich das falsch?

Ich habe aktuell keinen PullUp, sondern sogar einen Spannungsteiler 
gegen GND, um 5Volt->3Volt zu wandeln.
Meine Beschaltung siehe Anhang.

Aber der Wurm scheint trotzdem hier vergraben zu sein.
Wenn ich mein Scope vor dem Spannungsteiler direkt an den Z Ausgang 
ansetze, kann das Oszi triggern und messen, hinter dem Spannungsteiler 
aber nicht mehr.
Es ist sogar so, dass auch bei niedrigen Drehzahlen die ISR nicht 
ausgeführt/getriggert wird, sobald ich den Tastkopf nach dem 
Spannungsteiler, also auf der uC Seite ansetze.
Mein Spannungsteiler hat insgesamt irgendwas um die 10k.

Wenn ich vor dem Spg-Teiler messen kann und dahinter nicht, bedeutet es 
doch dass ich einen kleineren Gesamtwiderstand für den Spg-Teiler wählen 
muss?

EDIT: ok, das ist Schwachsinn, ich kann vor und nach dem Spannungsteiler 
mit dem Scope problemlos messen, bei hohen und bei niedrigen Drehzahlen. 
Hatte die TriggerlevelSpannung am Scope ungünstig eingestellt :D
Heißt ja aber, dass der Encoder Ausgang die Beschaltung und den uC Pin 
treiben kann nicht wahr?
Jetzt bleibt noch die frage, ob man einen PullUp braucht.

Gruß
Andreas

: Bearbeitet durch User
von Andreas T. (skycurve)


Lesenswert?

Habe jetzt noch einiges auprobiert: Der Encoder ist nicht defekt.
Mein uC überspringt auch keine Index (Z) Signale, die ISR wird jedes mal 
betreten.
Das Problem scheint wirklich die Performance zu sein.
Wenn ich in der ISR den Timer bei höheren Drehzahlen nicht auf 0 sondern 
zB auf 10 setze, läuft der Motor wesentlich besser und konstanter, die 
Stromaufnahme geht auch zurück.

Bedeutet am Schluss, dass wenn der Motor mit 1500rpm läuft und am 
Nullpunkt vorbeifährt, die ISR aufgerufen wird um den Timer 
zurückzusetzen (sprich zu synchronisieren), ist der Motor schon 10 
Encoderschritte weitergelaufen.

Das wirkt sich natürlich stark auf den Motorlauf aus, da 7 elektrische 
Rototationen einer Wellenrotation entsprechent bei meinem Motor.
Es scheint wohl keine Hardwarelösung für den Z Input zu geben :(

von Alex E. (tecnologic) Benutzerseite


Lesenswert?

Naja die HW-Lösung ist Capture Compare wie bereits angedeutet. dann ist 
die Laufzeit der Isr irrelevant

von anon (Gast)


Lesenswert?

Wenn du den oben verlinkten Encoder hast (also in der verlinkten 
Ausführung), dann hat der Push-Pull Ausgänge. Da die (meisten) Eingänge 
des STM32F4 5V tolerant sind, kannst du direkt auf den Eingang gehen.

Meine Spannungsteiler sehen übrigens anders aus...

von Andreas T. (skycurve)


Lesenswert?

anon schrieb:
> Meine Spannungsteiler sehen übrigens anders aus...

Meine Beschriftung ist hier vielleicht etwas irreführend:
ENCODER_A, ENCODER_B, ENCODER_Z gehen an den uC
und die Rechtecke stellen die Anschlussleiste für die Encoderausgänge 
dar.
"uC" ist falsch plaziert.

: Bearbeitet durch User
von Andreas T. (skycurve)


Lesenswert?

Tec Nologic schrieb:
> Naja die HW-Lösung ist Capture Compare wie bereits angedeutet. dann ist
> die Laufzeit der Isr irrelevant

Ich denke jetzt habe ich verstanden, was Chris und Uwe mir weiter oben 
sagen wollten:
Wenn A und B an Channel 1 und 2 hängen, soll ich Z auf Channel 3 legen 
und diesen auf Capture konfigurieren. Immer wenn es auf CH3 einen Event 
gibt, wird der aktuelle Timer Zählerstand weggespeichert. Diesen kann 
ich dann mit
1
TIM_GetCapture3(TIMx)
abholen und habe alle Zeit der Welt den Zählerstand zu korrigieren.
Hoffentlich jetzt richtig verstanden D:

Könntet ihr bitte über meinen Code drüberschauen, ich habe den Timer wie 
beschrieben eingestellt, es wird auch brav durch den Encoder 
hochgezählt, nur der TIM8_CC_IRQHandler wird nicht betreten.
1
void configQuadratureEncoder(void)
2
{
3
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE);
4
5
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
6
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8; // A, B, Z; CH1, CH2, CH3
7
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
8
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
10
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
11
  GPIO_Init(GPIOC, &GPIO_InitStructure);
12
13
  GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_TIM8);
14
  GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_TIM8);
15
  GPIO_PinAFConfig(GPIOC,GPIO_PinSource8,GPIO_AF_TIM8);
16
17
  /* Time base configuration */
18
  TIM_TimeBaseStructure.TIM_Period = 0x07ff; // Encoder -> 2048 Ticks pro Umdrehung => 0x07FF = 2047(dez)
19
  TIM_TimeBaseStructure.TIM_Prescaler = 0;
20
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
21
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
22
23
  // Capture Config
24
  NVIC_InitStructure.NVIC_IRQChannel = TIM8_CC_IRQn;
25
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
26
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
27
  NVIC_InitStructure.NVIC_IRQChannel = ENABLE;
28
  NVIC_Init(&NVIC_InitStructure);
29
  
30
  TIM_ICInitTypeDef TIM_ICInitStructure;
31
  TIM_ICInitStructure.TIM_Channel = TIM_Channel_3;
32
  TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;
33
  TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
34
  TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
35
  TIM_ICInitStructure.TIM_ICFilter = 0x0;
36
  TIM_ICInit(TIM8, &TIM_ICInitStructure);
37
  
38
  TIM_ClearITPendingBit(TIM8, TIM_IT_CC3);
39
  TIM_ITConfig(TIM8, TIM_IT_CC3, ENABLE);
40
  // END Capture Config
41
  
42
  TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure);
43
  
44
  TIM_EncoderInterfaceConfig(TIM8, TIM_EncoderMode_TI2, TIM_ICPolarity_Falling, TIM_ICPolarity_Rising);
45
  
46
  
47
  TIM_Cmd(TIM8, ENABLE);
48
  TIM8->CNT=0;
49
}
1
void TIM8_CC_IRQHandler (void)
2
{
3
  GPIOD->ODR ^= GPIO_Pin_12; // LED am Board, toggelt nicht!
4
  GPIOD->ODR ^= GPIO_Pin_11; // Scope, , toggelt nicht!
5
  if (TIM_GetITStatus(TIM8, TIM_IT_CC3) != RESET)
6
  {
7
    // code
8
    
9
    TIM_ClearITPendingBit(TIM8, TIM_IT_CC3);
10
  }
11
}

Gruß
Andreas

von Joe23 (Gast)


Lesenswert?

Ich habe genau das gleiche Problem wie der Thread-Starter und habe, ohne 
diesen Thread zu kennen, den Inputcapture als Ausweichlösung in Betracht 
gezogen. Im Moment fehlt mir noch eine Softwarelösung. Schade, dass der 
Thread sang- und klanglos versackt ist.

von Joe23 (Gast)


Lesenswert?

Inzwischen habe ich auch eine funtionierende Initialisierung.

von kem (Gast)


Lesenswert?

Hallo Joe23, könntest du bitte deine funktionierende Initialisierung 
hier posten? Danke

von aSma>> (Gast)


Lesenswert?

Servus,

Andreas T. schrieb:
> TIM_TimeBaseStructure.TIM_Period = 0x07ff;

Andreas T. schrieb:
> NVIC_InitStructure.NVIC_IRQChannel = TIM8_CC_IRQn;
>   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
>   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
>   NVIC_InitStructure.NVIC_IRQChannel = ENABLE;
>   NVIC_Init(&NVIC_InitStructure);

IRQChannelPreemptionPriority lieber auf 0 setzen, dann kann diese ISR 
nicht unterbrochen werden oder mit:__disable_irq(), __enanble_irq() 
arbeiten.

Bei zu langen Leitung müsste man sich die Quadratursignale am Scope mal 
anschauen. Könnte deshalb schlechte Flanken ergeben. Pullup würde ich so 
je nach länge der Leitung zwischen 1,5k bei 5V und max 6000rmp. Man kann 
natürlich bei ganz langen Leitung auf 24V plus Spannungsteiler hingehen. 
Aber auch hier nicht zu hochomig, sodas zwischen 3-5mA Strom fließt.

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.