Debounced Rotory Encoder

Da von unseren Kunden in letzter Zeit immer wieder Fragen zur Implementierung des Drehimpulsgebers (digitales Poti) gestellt wurden möchten wir hier kurz eine Möglichkeit zeigen, ein sauberes Signal des Drehimpulsgebers mithilfe von Interrupts auszugeben. Hier im Beispiel am Seriellen Monitor:

Schaltung:

Stückliste:

 

Arduino Sketch:

/*
* Rotary Encoder connected to a Microcontroller, compatible with Arduino Uno R3
*
* Encoder Pins: CLK, DT, SW, +, GND
* The encoder contains two pull-up-resistors of 10k at CLK and DT
* All contacts are low-active
*
* Program based on software by Stefan Nikolaus
* https://www.nikolaus-lueneburg.de/2016/02/rotary-encoder/
* and important code written by rafbuff at http://playground.arduino.cc/Main/RotaryEncoders
* "Another Interrupt Library THAT REALLY WORKS (the Encoder interrupts the processor
* and debounces like there is no tomorrow)."
*
* Pins 2 and 3 are used because they support interrups !
* Debouncing works really great !!!
* Math.positive counting: DT on 3, CLK on 2
* Clockwise counting: DT on 2, CLK on 3
*
* Modifications:
* - deleted 'unprintable' characters, modified {} and TABs
* - added the button detection for pin 4 "SW"
* - added actionButton() and actionPosition(); here we can put down the
* code for further processing in our system.
*
* UKA 31.05.2018
*/


#define encoderPinA 2
#define encoderPinB 3
#define SW 4


volatile unsigned int encoderPos = 0; // a counter for the dial
unsigned int lastReportedPos = 1; // change management
static boolean rotating = false; // debounce management

int button = LOW;
int old_button = LOW;

// interrupt service routine variables
boolean A_set = false;
boolean B_set = false;


void setup()
{
pinMode(encoderPinA, INPUT);
pinMode(encoderPinB, INPUT);

pinMode( SW, INPUT_PULLUP );


digitalWrite(encoderPinA, HIGH); // turn on pullup resistors
digitalWrite(encoderPinB, HIGH); // turn on pullup resistors

attachInterrupt(0, doEncoderA, CHANGE); // encoder pin on interrupt 0 (pin 2)
attachInterrupt(1, doEncoderB, CHANGE); // encoder pin on interrupt 1 (pin 3)

Serial.begin(9600); // output to PC Serial Monitor

}// --------------------- setup --------------------------------



void loop()
{
rotating = true; // reset the debouncer

if (lastReportedPos != encoderPos)
{
actionPosition();
lastReportedPos = encoderPos;
}


button = !digitalRead( SW );

if( button != old_button )
{
actionButton();
delay( 10 );
old_button = button;
}

}// --------------------------- main loop -----------------------


void actionButton()
{
Serial.print("Button: ");
Serial.print ( button );
Serial.print (" | ");
Serial.println(encoderPos, DEC);
} // ------------------ actionButton --------------------------------


void actionPosition()
{
Serial.print("Position: ");
Serial.print(encoderPos, DEC);
Serial.print (" | ");
Serial.println ( button );
} // ------------------ actionPosition --------------------------------




// ------------- Interrupt Service Routines ------------------------------

// ISR Interrupt on A changing state (Increment position counter)
void doEncoderA()
{
if ( rotating ) delay (1); // wait a little until the bouncing is done
if( digitalRead(encoderPinA) != A_set ) // debounce once more
{
A_set = !A_set;
// adjust counter +1 if A leads B
if ( A_set && !B_set )
encoderPos += 1;
rotating = false; // no more debouncing until loop() hits again
}
}// ------------------- doEncoderA ----------------------------------------


// ISR Interrupt on B changing state, same as A above
void doEncoderB()
{
if ( rotating ) delay (1);
if( digitalRead(encoderPinB) != B_set )
{
B_set = !B_set;
//adjust counter -1 if B leads A
if( B_set && !A_set )
encoderPos -= 1;
rotating = false;
}
}// -------------------- doEncoderB --------------------------------------

Wir hoffen, dass Ihnen unser heutiger Blogbeitrag zur Lösung Ihres Problems und als Gedankenanstoß für Ihre Projekte weitergeholfen hat und wir freuen uns auf Ihre Kommentare. Bis zum nächsten Beitrag von AZ-Delivery, Ihrem Experten für Mikroelektronik!

 

2 Kommentare

Stefan Andres

Stefan Andres

Mit dem ESP32 (Arduino IDE) hatte ich viele Probleme den KY-040 vernünftig zum laufen zu bringen.
Ich habe einige Lib’s und Lösungen versucht, vergebens.
Der Grund: Es ist ein analoges Signal und der Interrupt wird, beim Eintreffen von CLK, mehrfach ausgelöst. Manchmal sogar einige 100 mal. Ein “debouncing” mit delay(x) bringt auch nicht wirklich eine Lösung. Kondensatoren gegen GND genau so wenig.
Nach einigen Versuchen habe ich eine Lösung mittels Schmitt Trigger gefunden:
Das Signal (clk) vom KY-040, zum Unterdrücken von Schwingungen mit einem Kondenstator (0,1µF) gegen GND an den Eingang des Schmitt Trigger (A) legen. Den Ausgang (Y) dann auf den Interrupt PIN. Damit wird die Interrupt Routine ebenfalls sehr einfach:
Nun noch einen Interrupt auf CLK Flanke “FALLING”. In der Routine brauch man nur noch DT abfragen.:
Code Auszug:
…….
volatile int16_t count=0;
uint8
t clkPin;
uint8
t _dtPin;

portMUX_TYPE mux = portMUX_INITIALIZERUNLOCKED;

void IRAM_ATTR ISR_ROTATION() {
portENTER_CRITICAL_ISR(&(mux));
if (digitalRead(
dtPin)) _count++; //CW
else count—; // CCW
portEXIT_CRITICAL_ISR(&(mux));
}
KY040::KY040(uint8_t clkPin, uint8
t dtPin, uint8
t swPin) {
// constructor code
_clkPin = clkPin;
_dtPin = dtPin;
swPin = swPin;
pinMode(clkPin, INPUT);
pinMode(dtPin, INPUT);
pinMode(swPin, INPUT);
attachInterrupt(digitalPinToInterrupt(
clkPin), ISR
ROTATION , FALLING);
….
}
…..
int16
t KY040::readEncoder() {
uint16
t c = _count;
_count = 0;
return c;
}

Analog habe ich auch den Switch gegen Prellen geschützt.

Walter van Boer

Walter van Boer

/*

Walter van Boer 12.06.2018 Further modifications: - writing error: actionposition() actionbutton() in void loop() - should be actionPosition(), actionButton() - should be: if (A_set && !B_set) in ISR void doEncoderA() -This sketch works great!
*/

Einen Kommentar hinterlassen

Alle Kommentare werden vor der Veröffentlichung moderiert