KY-40 rotary encoder


  • Hardware Contributor

    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?


  • Mod

    @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.


  • Hardware Contributor

    @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.


  • Hardware Contributor

    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;
      }
    }
    

  • Mod

    @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 😉


  • Admin


  • Hardware Contributor

    @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.


  • Mod

    @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?


  • Hero Member

    @vladimir there is an example for using rotary encoder on this page:

    https://www.mysensors.org/build/dimmer


Log in to reply
 

Suggested Topics

  • 87
  • 2
  • 5
  • 1
  • 3
  • 8

7
Online

11.4k
Users

11.1k
Topics

112.7k
Posts