KY-40 rotary encoder
-
I bought the rotary encoder/switch that is linked in the store as I wanted to get such a combo to control my led strips (button = on/off, rotation = brightness or color). I did some googling but couldn't find any good and working code. Did anyone of you already use this device?
My goal is to use that with a battery driven sensor, so it needs to sleep a lot which means relying on interrupts.
What I think I got so far:
- SW seems to be active high (also mine is missing the pullup resistor it seems)
- DT and CLK are the rotation pins. Both are active low, one goes low first and indicates the direction of the rotation
- Some additional hardware debouncing might be needed
All the code I found is either not really working or requires and infinite loop and busy waiting (bad for the battery). My idea was to attach SW to one interrupt
attachInterrupt(1, push, FALLING); void push () { delay(5); // debouncing? if (!digitalRead(PinSW)) Serial.println("Pushed"); }
and one of the others to the other interrupt
attachInterrupt(0, rotate, FALLING); void rotate () { delay(5); if (!digitalRead(PinDT)) { virtualPosition = virtualPosition + 1; Serial.println("Position " + virtualPosition); } else { virtualPosition = virtualPosition - 1; Serial.println("Position " + virtualPosition); } // isr }
That doesn't seem to be working yet though. Any ideas from you guys?
-
@LastSamurai Try this library and connect the DT & CLK (horrible names btw) signals to interrupt pins, as described on this page.
Works like a charm for me in combination with an UNO.
-
@Yveaux Thanks. I tried it out with this example code
#include <Encoder.h> // Change these two numbers to the pins connected to your encoder. // Best Performance: both pins have interrupt capability // Good Performance: only the first pin has interrupt capability // Low Performance: neither pin has interrupt capability Encoder myEnc(3, 5); // avoid using pins with LEDs attached void setup() { Serial.begin(9600); Serial.println("Basic Encoder Test:"); } long oldPosition = -999; void loop() { long newPosition = myEnc.read(); if (newPosition != oldPosition) { oldPosition = newPosition; Serial.println(newPosition); } }
using one interrupt pin (need the other one for the button) and it doesn't really work. I get 0 -1 0 -1... when I rotate it and -2 when I push the switch. I also added 4.7uF caps between ground and the 2 pins which got rid off the "spam" of values but doesn't change the values themselves. Also I don't see a way with that library to sleep until some motion of the switch is detected.
-
Ok I found this code from the arduino page on rotary encoders. This seems to work perfectly (beside always needing 2 clicks to register a change). Still have no idea how to use this with sleep though... guess I need to keep experimenting.
/* interrupt routine for Rotary Encoders tested with Noble RE0124PVB 17.7FINB-24 http://www.nobleusa.com/pdf/xre.pdf - available at pollin.de and a few others, seems pretty universal The average rotary encoder has three pins, seen from front: A C B Clockwise rotation A(on)->B(on)->A(off)->B(off) CounterCW rotation B(on)->A(on)->B(off)->A(off) and may be a push switch with another two pins, pulled low at pin 8 in this case raf@synapps.de 20120107 */ // usually the rotary encoders three pins have the ground pin in the middle enum PinAssignments { encoderPinA = 2, // right encoderPinB = 3, // left clearButton = 5 // another two pins }; volatile unsigned int encoderPos = 0; // a counter for the dial unsigned int lastReportedPos = 1; // change management static boolean rotating=false; // debounce management // interrupt service routine vars boolean A_set = false; boolean B_set = false; void setup() { pinMode(encoderPinA, INPUT); pinMode(encoderPinB, INPUT); pinMode(clearButton, INPUT); // turn on pullup resistors digitalWrite(encoderPinA, HIGH); digitalWrite(encoderPinB, HIGH); digitalWrite(clearButton, HIGH); // encoder pin on interrupt 0 (pin 2) attachInterrupt(0, doEncoderA, CHANGE); // encoder pin on interrupt 1 (pin 3) attachInterrupt(1, doEncoderB, CHANGE); Serial.begin(9600); // output } // main loop, work is done by interrupt service routines, this one only prints stuff void loop() { rotating = true; // reset the debouncer if (lastReportedPos != encoderPos) { Serial.print("Index:"); Serial.println(encoderPos, DEC); lastReportedPos = encoderPos; } if (digitalRead(clearButton) == LOW ) { encoderPos = 0; } } // Interrupt on A changing state void doEncoderA(){ // debounce if ( rotating ) delay (1); // wait a little until the bouncing is done // Test transition, did things really change? if( digitalRead(encoderPinA) != A_set ) { // debounce once more A_set = !A_set; // adjust counter + if A leads B if ( A_set && !B_set ) encoderPos += 1; rotating = false; // no more debouncing until loop() hits again } } // 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; } }
-
@LastSamurai I'm sure that if you would use two interrupt-pins to connect the encoder, the suggested library will also work just fine.
You're changing your requirements along the way
-
Well, I created this example way back for the KY-40:
-
@Yveaux I am sure it works then but I am not changing requirements Perhaps I wasn't clear enough though.
My goal is to get the arduino to correctly read values from the button/encoder (works with the libraries/codes) but to also sleep most of the time when noone uses the encoder as I want this to be a seperate battery powered "sensor" if possible. Perhaps I am missing something here but the only way to wake up from sleep when something is used are interrupts. So if the library uses them but doesn't provide any hooks into the "wake up call" I can't use it for this usecase.
-
@LastSamurai said:
@Yveaux I am sure it works then but I am not changing requirements Perhaps I wasn't clear enough though.
I meant first you don't use two interrupts to connect the sensor and then with some other code you do. Anyway, never mind it
My goal is to get the arduino to correctly read values from the button/encoder (works with the libraries/codes) but to also sleep most of the time when noone uses the encoder as I want this to be a seperate battery powered "sensor" if possible. Perhaps I am missing something here but the only way to wake up from sleep when something is used are interrupts. So if the library uses them but doesn't provide any hooks into the "wake up call" I can't use it for this usecase.
Apart from reworking parts of the library I see some options:
- Use a wired-OR to combine both encoder signals and connect that to an interrupt input. This will trigger the ATmega to wakeup, after which the regular encoder code can determine the new encoder value.
- Use the pushbutton of the encoder to wake the ATmega (so connect it to an interrupt pin), then the regular encoder code can determine the new encoder value.
IMHO, sleep, wake, decode, send a message and go to sleep again is quite tricky to get it right without loosing pulses.
For a good user experience it's probably best to stay awake for a while after waking up, giving your software time to process any more incoming pulses. When no new pulses have been detected for some time, you can go back to sleep again.
-
@lastsamurai Hello!
Did you manage to implement this node?
-
@vladimir there is an example for using rotary encoder on this page: