[Home] [Downloads] [Search] [Help/forum]

Gammon Software Solutions forum

See www.mushclient.com/spam for dealing with forum spam. Please read the MUSHclient FAQ!

[Folder]  Entire forum
-> [Folder]  Electronics
. -> [Folder]  Microprocessors
. . -> [Subject]  Rotary encoders and interrupts
Home  |  Users  |  Search  |  FAQ
Username:
Register forum user name
Password:
Forgotten password?

Rotary encoders and interrupts

Postings by administrators only.

[Refresh] Refresh page


Posted by Nick Gammon   Australia  (19,607 posts)  [Biography] bio   Forum Administrator
Date Tue 24 May 2011 04:20 AM (UTC)  quote  ]

Amended on Mon 20 Oct 2014 09:43 PM (UTC) by Nick Gammon

Message
I have been reading about rotary encoders recently, and just assumed that they looked like this:



After all, that is a rotary-dial phone, and presumably as you dial the numbers are encoded.

However I now realize that people are probably talking about these things:



These are rotary switches, which (unlike potentiometers) are not analogue, but digital. As you turn the knob pulses are generated by switching the center (C) pin to either of the outer pins (A and B) in such a way that you can tell which way it is being turned.

To test this, I wired up the switch like this:



The 0.1 uF (ceramic) capacitors shown in the schematic are not in the photo. They were suggested as a means of doing a "hardware debounce". I did not use them initially, and you may find that the encoder works without them. But if you get occasional jumps or wrong readings, the capacitors may very well help.

It was very simple, here is a photo of it being connected to an Arduino Uno:



In the photo a couple of diodes are visible. This was part of a scheme to try to use only one interrupt, but it didn't work perfectly.

Now the center (common, or C) pin is grounded. The outer two pins are connected to pins 2 and 3 of the Arduino, which are then pulled high by setting pull-up resistors on them. In the code that is done like this:


  digitalWrite (2, HIGH); 
  digitalWrite (3, HIGH); 


To detect when the knob is being turned we set up an interrupt handler which fires whenever either pin changes:


  attachInterrupt (0, isr, CHANGE);   // pin 2
  attachInterrupt (1, isr, CHANGE);   // pin 3


A bit of investigation shows that whenever you turn the encoder by one click you get one of these state changes (where H is high and L is low):


Forward direction: LH then HH, or HL then LL
Reverse direction: HL then HH, or LH then LL


So we can see that both pins the same (HH or LL) is the "resting" position. So we need to remember what preceded them. Thus we remember the previous switch state, and then when we get HH or LL, we look at what we had last time to see which way the switch must have been turned.

An extra test was needed to cater for switch bounce (sometimes we got HH followed by HH). I also added code to alter the "increment" amount. The idea was that if a human is turning the knob, if he or she turns it faster, then we increment by more than one. That is so if you want a big change, you turn the knob quickly, and if you want a small change, you turn it slowly.

The finished test program looks like this:


// Rotary encoder example.
// Author: Nick Gammon
// Date:   24th May 2011

// Wiring: Connect common pin of encoder to ground.
// Connect pins A and B (the outer ones) to pins 2 and 3 (which can generate interrupts)

volatile boolean fired = false;
volatile long rotaryCount = 0;

// Interrupt Service Routine
void isr ()
{
  
static boolean ready;
static unsigned long lastFiredTime;
static byte pinA, pinB;  

// wait for main program to process it
  if (fired)
    return;
    
  byte newPinA = digitalRead (2);
  byte newPinB = digitalRead (3);
  
  // Forward is: LH/HH or HL/LL
  // Reverse is: HL/HH or LH/LL
  
  // so we only record a turn on both the same (HH or LL)
  
  if (newPinA == newPinB)
    {
    if (ready)
      {
      long increment = 1;
        
      // if they turn the encoder faster, make the count go up more
      // (use for humans, not for measuring ticks on a machine)
      unsigned long now = millis ();
      unsigned long interval = now - lastFiredTime;
      lastFiredTime = now;
      
      if (interval < 10)
        increment = 5;
      else if (interval < 20)
        increment = 3;
      else if (interval < 50)
        increment = 2;
         
      if (newPinA == HIGH)  // must be HH now
        {
        if (pinA == LOW)
          rotaryCount += increment;
        else
          rotaryCount -= increment;
        }
      else
        {                  // must be LL now
        if (pinA == LOW)  
          rotaryCount -= increment;
        else
          rotaryCount += increment;        
        }
      fired = true;
      ready = false;
      }  // end of being ready
    }  // end of completed click
  else
    ready = true;
    
  pinA = newPinA;
  pinB = newPinB;
}  // end of isr


void setup ()
{
  digitalWrite (2, HIGH);   // activate pull-up resistors
  digitalWrite (3, HIGH); 
  
  attachInterrupt (0, isr, CHANGE);   // pin 2
  attachInterrupt (1, isr, CHANGE);   // pin 3

  Serial.begin (115200);
}  // end of setup

void loop ()
{

  if (fired)
    {
    long currentCount;
    
    // protected access to the counter
    noInterrupts ();
    currentCount = rotaryCount;
    interrupts ();
    
    Serial.print ("Count = ");  
    Serial.println (currentCount);
    fired = false;
  }  // end if fired

}  // end of loop

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (19,607 posts)  [Biography] bio   Forum Administrator
Date Reply #1 on Wed 25 May 2011 05:56 AM (UTC)  quote  ]
Message
Following on from suggestions from jraskell on the Arduino forum, the simplified version below only requires a single interrupt pin, so you could use pin D2 (for the interrupt) and another pin (eg. D5) for the "B" side, thus freeing up an interrupt.

For simplicity I removed the code to detect the speed at which you were turning the encoder, but you could add that back in, in the main loop if you wanted it.


// Rotary encoder example.
// Author: Nick Gammon
// Date:   25th May 2011

// Thanks for jraskell for helpful suggestions.

// Wiring: Connect common pin of encoder to ground.
// Connect pin A (one of the outer ones) to a pin that can generate interrupts (eg. D2)
// Connect pin B (the other outer one) to another free pin (eg. D5)

volatile boolean fired;
volatile boolean up;

#define PINA 2
#define PINB 3
#define INTERRUPT 0  // that is, pin 2

// Interrupt Service Routine for a change to encoder pin A
void isr ()
{
  if (digitalRead (PINA))
    up = digitalRead (PINB);
  else
    up = !digitalRead (PINB);
  fired = true;
}  // end of isr


void setup ()
{
  digitalWrite (PINA, HIGH);     // enable pull-ups
  digitalWrite (PINB, HIGH); 
  attachInterrupt (INTERRUPT, isr, CHANGE);   // interrupt 0 is pin 2, interrupt 1 is pin 3

  Serial.begin (115200);
}  // end of setup

void loop ()
{
static long rotaryCount = 0;

  if (fired)
    {
    if (up)
      rotaryCount++;
    else
      rotaryCount--;
    fired = false;
        
    Serial.print ("Count = ");  
    Serial.println (rotaryCount);
    }  // end if fired

}  // end of loop

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


6,780 views.

Postings by administrators only.

[Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

[Home]

Written by Nick Gammon - 5K

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( http://www.gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Web site powered by FutureQuest.Net]