STM32-Drehgeber mit Hardware-Interrupts

Ich versuche, einen Drehgeber an meinem STM32 zum Laufen zu bringen.

  1. Ich habe Kanal A & B auf 3 V hochgezogen und mit 1uF-Kondensatoren entprellt.
  2. Die Karte hat Kanal A & B mit PA11 bzw. PA10 verbunden und hat für beide Hardware-Interrupts konfiguriert
  3. Ich habe eine Reihe verschiedener Algorithmen ausprobiert, um die Drehrichtung zu decodieren, aber egal, was ich tue, ich kann keine konsistenten abwechselnden Interrupts erhalten (z. B. ABABAB).
  4. Ich habe versucht, an beiden Flanken zu triggern, einfach fallend, einfach steigend und egal was ich bekomme, die Interrupts werden scheinbar zufällig ausgelöst.

Gibt es etwas, das ich falsch mache? Reichen die Interrupts nicht aus, um mit der Geschwindigkeit des Encoders Schritt zu halten? Ist die Entprellung nicht genug (Sieht auf einem Oszilloskop okay aus, aber vielleicht sind die Interrupts empfindlicher)? Gibt es einen besseren Weg, dies im Allgemeinen zu tun? Ich stecke hier schon eine Weile fest, also wäre jede Hilfe großartig. Lassen Sie mich wissen, wenn weitere Informationen erforderlich sind.

*Code bearbeitet, um Änderungen widerzuspiegeln (Arbeitsalgorithmus)

UNTERBRECHUNGS-HANDLER

static int8_t states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
uint8_t RotaryCurrentState = 0x00;
uint8_t RotaryTransition = 0;
int8_t RotaryPosition = 0;

/*Rotary Encoder Interrupt Handler
Channel A (IRQ11) & B (IRQ10)

              CW --->
 A (IRQ11)  ¯|___|¯¯¯¯|___|¯¯¯¯
 Interrupts  ^   ^    ^   ^
 B (IRQ 10) ¯¯¯|___|¯¯¯¯¯|___|¯
 Interrupts    ^   ^     ^   ^
                  CCW <---

void EXTI4_15_IRQHandler(void)
{ 
  RotaryCurrentState = (Read_IO(ROTARY_A) << 1) | Read_IO(ROTARY_B);
  RotaryTransition = (RotaryTransition <<2 ) | RotaryCurrentState;
  RotaryPosition = RotaryPosition + states[RotaryTransition & 0x0F];

  EXTI_ClearITPendingBit(EXTI_Line10);  //Clear Channel B
  EXTI_ClearITPendingBit(EXTI_Line11);  //Clear Channel A
}

HAUPT C

//Initialize 
RotaryTransition = Read_IO(ROTARY_A) << 3 | Read_IO(ROTARY_B) << 2 | \
                   Read_IO(ROTARY_A) << 1 | Read_IO(ROTARY_B);

while(1)
{
  //CW Transition
  if (RotaryPosition == 4)
  {
    STM_EVAL_LEDToggle(LED4);
    RotaryPosition = 0;
  }
  //CCW Transition
  else if (RotaryPosition == -4)
  {
    STM_EVAL_LEDToggle(LED3);
    RotaryPosition = 0;
  }
}
Encoder auf dem STM32F4 können direkt mit Timern behandelt werden.
Leider bin ich bei der Verwendung dieser IOs aufgrund des Board-Designs und der IO-Einschränkungen festgefahren. Ich glaube nicht, dass sie die Dekodierung mit dem Timer durchführen können.
Diese Encoder haben zwei Kanäle. Der einfachste Weg, sie zu verwenden, besteht darin, einen als "Uhr" zu verwenden und einen durch eine einzelne Flanke ausgelösten Interrupt daran anzuhängen, dann fungiert der andere Kanal als "Richtungsanzeiger". Bei jedem Interrupt überprüfen Sie den Wert des 'Richtungs'-Kanals und addieren oder subtrahieren einen Schritt vom aktuellen Wert.
Wichtige Details: Wie genau werden die Interrupt-Eingänge hochgezogen? Haben die Interrupt-Eingänge eine Hysterese?
Bewegst du dich nicht viel zu viel?? Sollte die 8 nicht eine 2 sein?
Möglicherweise müssen Sie einen Quadratur-Encoder nicht wirklich entprellen - er kann zwischen zwei Zuständen oszillieren, aber er sollte keine falschen Zählungen ansammeln, da sich jeweils nur ein Kanal in der "Prellzone" befinden sollte. Es ist denkbar, dass eine schlecht durchdachte Entprellschaltung tatsächlich zu einer Fehlinterpretation führt. Um beide Kanäle gleichzeitig zum Prellen zu bringen, müssten Sie eine Rotationsrate (Kommutierungsrate) nahe der Prelldauer haben. An diesem Punkt scheint es nicht möglich zu sein, eine Entprellcharakteristik zu definieren, die dies manchmal ausschließen würde Maskierung des eigentlichen Signals.
Ich habe die Taktmethode ausprobiert und obwohl sie etwas zuverlässiger war, war sie immer noch nicht verwendbar.
Die Leitungen werden von 10k-Widerständen auf der Platine hochgezogen. Ich verstehe nicht, warum sie Hysterese haben sollten, aber vielleicht übersehe ich etwas.
Ich kodiere jeden Zustand als 8-Bit-Zahl und die kombinierten Zustände als 16-Bit, also denke ich, dass ich richtig verschiebe, obwohl vielleicht nicht die effizienteste.
@spizzak Hysterese kann kritisch sein, wenn Sie versuchen, eine Flanke an einem Eingang mit einer langsam ansteigenden Flanke zu erkennen. Sie haben eine RC-Zeitkonstante von 10 ms an Ihren Eingängen, was sehr, sehr langsam ist und sie aufgrund von Rauschen anfällig für falsches Auslösen macht. Die von mir verwendeten NXP-Prozessoren haben eine Hysterese an den Eingängen, um dieses Problem zu beseitigen. ST-Prozessoren kenne ich nicht.
Gut gemacht!! Das einzige, was ich hinzufügen würde, ist, ein Flag im Interrupt zu setzen und die Überprüfung in der Hauptschleife nur auszuführen, wenn sich der Encoder-Status ändert: while(~encoderflag){} ; ..... Nur damit Sie wissen, dass Sie den nächsten Block an der richtigen Stelle betreten
Kleines Detail, aber Sie können mit nur einem Anruf davonkommen EXTI_ClearITPendingBit:EXTI_ClearITPendingBit(EXTI_Line10 | EXTI_Line11)

Antworten (1)

Das ist kein großartiger Algorithmus in Ihrem Handler. Sie sollten NULL ifs haben. Keine Entscheidungen.

Speichern Sie Ihren AB-Zustand, dh 00 oder 01, und hängen Sie dann Ihren nächsten Zustand an, dh 0001 bedeutet, dass AB von 00 auf 01 wechselte, also B von 0 auf 1 geändert wurde. Machen Sie dies zu +1. Wenn Sie bei 00 beginnen und zu 10 wechseln, nennen Sie dies eine -1. Erstellen Sie ein 16-Element-Array aller möglichen Übergänge, das die Zahl enthält, die zu Ihrer Zählung hinzugefügt werden muss, wenn es auftritt, und beachten Sie, dass einige illegal sind und behandelt werden müssen.

0000       0    0  no transition


0001       1   +1

0010       2   -1

0011       3    0   Illegal, two transitions,  and so on.

Indexieren Sie bei jedem Übergang in dieses Array, achten Sie auf illegale Ereignisse und behandeln Sie sie so, wie Sie es für richtig halten. Addieren Sie das Ergebnis zur Zählung. Verschieben Sie die neuen Werte an die Stelle des alten Werts in der Indexnummer und wiederholen Sie dies für immer

Im Pseudocode

 signed int8 add_subt[16] = { 0, 1 ,-1 ,  ....};
 unsigned int8 idx;
 signed int32 pos_count;

main() {
% initialize idx
idx = readA <<3 + readB<<2 + readA<<1 + readB;
while(1){}
}

interrupt_on_any_change(){
idx=idx<<2 & 0x0F + readA<<1 + readB;
pos_count=pos_count+add_subt[idx];
}

Sie könnten err_idx pflegen, um Ihnen zu helfen, schlechte Übergänge zu kennzeichnen

Sieht das nicht um Haaresbreite einfacher aus??

Diese scheinen sinnvoll zu sein, und ich habe online ein ähnliches Beispiel gesehen, als ich gesucht habe, das das Array als 4x6-Zustandsmaschine implementiert hat, was ich für einen interessanten Ansatz hielt. Im Grunde würde ich also auf die steigende und fallende Flanke beider Kanäle triggern und dann bei jedem Interrupt den Wert beider Leitungen lesen?
Jawohl. bei jedem Übergang triggern und A und B in die unteren beiden Bits des Index einlesen.
Auch eine Zustandsmaschine ist nicht erforderlich. Ein Array, nach Index, teilt Ihnen mit, ob Sie Ihre Zählung erhöhen oder verringern müssen.
Eingeführt und funktioniert super! Danke für den Vorschlag. Ich habe meinen Code im ursprünglichen Beitrag auf die Arbeitsversion aktualisiert.
Gut gemacht!! Ursprünglich dachte ich, Sie würden versuchen, dies für einen Quad-Encoder eines Motors zu verwenden, in diesem Fall wären Interrupts auf den DIOs wahrscheinlich grenzwertig, aber dann habe ich tatsächlich eine Verbindung zu dem Teil hergestellt, mit dem Sie gearbeitet haben !! Ich denke, Sie können dies tatsächlich etwas kleiner machen, indem Sie zwei Ganzzahlen verwenden, eine zum Inkrementieren und eine zum Dekrementieren, und das entsprechende Bit mit Bitmaskierung versehen, so etwas wie count + incr&(1<<idx) - decr&(1<<idx) , aber wenn Sie sich das Array leisten können, warum nicht!
Dies ist ein eingebetteter Codierungsstil! Wir sehen so oft Interrupts mit vielen Dingen darin. NEIN! Unterbrechungen sollten wie die vorgeschlagene sein: Verdammt schnell!
Ups, das letzte Bit wäre count = count + (incr&(1<<idx))>>idx - (decr&(1<<idx))>>idx. Ich denke, das ist etwas weniger Assembler als (incr&(1<<idx))>0. Ich habe das Gefühl, dass der Code für diesen Ansatz etwas langsamer sein könnte als für den Array-Ansatz. Der richtige Ansatz hängt natürlich davon ab, wie viel Speicher Sie aus dem System herausquetschen möchten
Sind beim Ausfüllen der Übergangstabelle nicht alle anderen Übergänge entweder ein ungültiger Zustand oder ein Nullübergang?
@ Michael nein. Für jeden Ausgangszustand gibt es legitime Einträge. 0 1 kann zum Beispiel zu 0 0 werden. Der Tabellenausschnitt, den ich gezeigt habe, ist nur für den Start bei 0 0
@Scott danke. Ich glaube, mir fehlt ein grundlegendes Verständnis dafür, wie dieser Encoder oder dieser Algorithmus funktioniert? Ich dachte, das Ziel ist es, positive Flankenübergänge effektiv zu zählen, wobei die Beziehung zwischen A und B die Richtung bestimmt. Die anderen Übergänge werden also nicht zum Zählen benötigt und liefern daher keine nützlichen Informationen zur Summe?
Der Interrupt wird bei jedem Übergang ausgeführt, sodass Übergänge in den Kuchen gebacken werden
Ich verstehe. Ich habe auf einer anderen Seite mit dieser Art von Algorithmus Hilfe gefunden. Der Punkt, den ich übersehen habe, ist, dass die Beziehung zwischen pos_count und dem DPR des Encoders: 360/(degrees_per_revolution*4) = Degrees_per_pos_count