Mouse Scroll Wheel Encoder to Arduino

Hello guys, its my first time to join into the world of Arduino forums. I just read the thread from the previous Arduino Fourms about quadrature encoders for mouse scroll wheel. Here it is by the way...

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1260385871/0

The scroll wheel in the mouse has typically 3 pins. One is the COM (ground), the other is the A and B that determines the quadrature encoding and its body itself should be connected to Vcc (+5V).

Here is a sample specification sheet of the mechanical encoder:

industrial.panasonic.com/www-data/pdf/ATC0000/ATC0000CE20.pdf

And barely to say, I've uploaded this code to try, but it seems a little buggy.

Here it is:

/*
Quadrature Example
*/
int quadAPin = 1;
int quadBPin = 2;

int lastQuadValue;

int CLOCKWISE = 1;
int CCLOCKWISE = -1;
int MISSEDPULSE = 999;

void setup()
{
  lastQuadValue = readQuadValue();
  Serial.begin(9600);
}

void loop()
{
  int newQuadValue = readQuadValue();
  int quadDir = getQuadDir(lastQuadValue, newQuadValue);

  if (quadDir == CLOCKWISE) Serial.print("Clockwise");
  if (quadDir == CCLOCKWISE) Serial.print("Counter Clockwise");
  if (quadDir == MISSEDPULSE) Serial.print("Missed Pulse Detected");

  lastQuadValue = newQuadValue;
}

int readQuadValue()
{
  int val = digitalRead(quadAPin);
  val = val * 2 + digitalRead(quadBPin);
  return val;
}

int getQuadDir(int prevVal, int newVal)
{

  //because the step order is 0, 1, 3, 2 lets do some switching around

  if (newVal == 3)
    {
    newVal = 2;
    }
  else
  {
    if (newVal == 2) newVal=3;
  }

  if (prevVal == 3)
    {
    prevVal = 2;
    }
  else
  {
    if (prevVal == 2) prevVal=3;
  }

  int quadDir = prevVal - newVal;

  //see if we missed a pulse (i.e. quadDir is 2 or -2)
  if (abs(quadDir) == 2) quadDir = MISSEDPULSE;

  return quadDir;
}

WHAT'S THE REAL DEAL WITH IT? I need to create a program that counts the number of scrolls and then be printed for example on an LCD. When rotated clockwise, it should count 0, 1, 2, 3, 4... and so on, while if it is rotated counter clockwise, it should be in decreasing order. Here's a sample video of it (but this one uses IR or optical encoder, so it has different pin configurations...)

http://www.stevekamerman.com/2010/12/understanding-a-mouse-scroll-wheel/

Please help me with it. I got to have four to five days. Thanks guys! :)

rency0722: And barely to say, I've uploaded this code to try, but it seems a little buggy. ... WHAT'S THE REAL DEAL WITH IT?

Can you please say, clearly and specifically, what the program does and in what way you consider that to be buggy? What do you expect it to do? What does it actually do?

The sketch you are using uses polled input pins to determine movement and direction. Not sure what you mean by 'buggy', but if you are getting "missed pulse" messages, then the mouse pulses are most likely happening too fast for this method of reading the mouse encoder pulses. If you search out the playground section for interrupt driven encoder sketches you should be able to find sketches that can handle encoder pulses at much higher rates. Also some use direct port access commands to read the input pins which is also much faster then using the arduino digitalRead() commands.

Here is one article that shows how to use both methods: http://www.arduino.cc/playground/Main/RotaryEncoders

Lefty

Anachrocomputer:

rency0722: And barely to say, I've uploaded this code to try, but it seems a little buggy. ... WHAT'S THE REAL DEAL WITH IT?

Can you please say, clearly and specifically, what the program does and in what way you consider that to be buggy? What do you expect it to do? What does it actually do?

Ok this is how it works.

I breadboard the rotary encoder pins A, B and COM and connected into Arduino Digital I/O pin 1 (for pin A) and pin 2 (for pin B). Then I uploaded the program on my Arduino and then opened the Serial Monitor...

It continuously, and endlessly, prints "Clockwise" and "Counter Clockwise" and also the "Missed Pulse Detected", even if I didn't turn the scroll wheel. And when I turn the scroll wheel, it doesn't affect the serial prints.

What I need is when I turn the scroll wheel for 1 detent (the somewhat "clicky" turn on the wheel), it should give a counter of 1, then for another, it should be added and added as it increase in clockwise and decrease in counter-clockwise manner.

Hope it helps. :)

Well pin 1 is used by the arduino serial channel to transmit characters to the PC, which your sketch uses to send messages back to the serial monitor. So you might be having a conflict with pin useage. You should change the encoder channels to use pins 2 and 3 instead of 1 and 2 and then try it out.

Lefty

ok dude, will do.

for now, here's the pic i'm working on...

I think I got the right program for the count... and the first code I've submitted was a hell of a crap (and it doesn't count... I mean, it does but it is not showed on the Serial Monitor, instead it shows if it turns CW or C/CW)...

BTW, this is the code... BUT I HAVE THREE PROBLEMS TO SOLVE, So again, help me with this guys! :)

#define encoder0PinA  2
#define encoder0PinB  3

volatile unsigned int encoder0Pos = 0;

void setup() { 


  pinMode(encoder0PinA, INPUT); 
  digitalWrite(encoder0PinA, HIGH);       // turn on pullup resistor
  pinMode(encoder0PinB, INPUT); 
  digitalWrite(encoder0PinB, HIGH);       // turn on pullup resistor

  attachInterrupt(0, doEncoder, CHANGE);  // encoder pin on interrupt 0 - pin 2
  Serial.begin (9600);
  Serial.println("start");                // a personal quirk

} 

void loop(){
// do some stuff here - the joy of interrupts is that they take care of themselves
}

void doEncoder() {
  /* If pinA and pinB are both high or both low, it is spinning
   * forward. If they're different, it's going backward.
   *
   * For more information on speeding up this process, see
   * [Reference/PortManipulation], specifically the PIND register.
   */
  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos++;
  } else {
    encoder0Pos--;
  }

  Serial.println (encoder0Pos, DEC);
}

/* See this expanded function to get a better understanding of the
 * meanings of the four possible (pinA, pinB) value pairs:
 */
void doEncoder_Expanded(){
  if (digitalRead(encoder0PinA) == HIGH) {   // found a low-to-high on channel A
    if (digitalRead(encoder0PinB) == LOW) {  // check channel B to see which way
                                             // encoder is turning
      encoder0Pos = encoder0Pos - 1;         // CCW
    } 
    else {
      encoder0Pos = encoder0Pos + 1;         // CW
    }
  }
  else                                        // found a high-to-low on channel A
  { 
    if (digitalRead(encoder0PinB) == LOW) {   // check channel B to see which way
                                              // encoder is turning  
      encoder0Pos = encoder0Pos + 1;          // CW
    } 
    else {
      encoder0Pos = encoder0Pos - 1;          // CCW
    }

  }
  Serial.println (encoder0Pos, DEC);          // debug - remember to comment out
                                              // before final program run
  // you don't want serial slowing down your program if not needed
}

Now here's the deal again...

  1. When turned counterclockwise from the initial point (at 0), it shouldn't give a value of 65,536, thus it should stay on 0 whenever it is endlessly rotated counter-clockwise.

  1. I think it should require debounce. Whenever the scroll wheel is turned (CW or C/CW), it gives sometimes two increments or decrements, and sometimes it just adds or subtracts 1 from the value.

I've checked the results and i've seen the maximum number of values it can print on a single turn is 2. For example. At initial (0), it sometimes gives 1 and then immediately the value 2 in just a split of a millisecond.

  1. More on debounce, it sometimes show a value repetitively. For example, the wheel is just turned, and the value gives 4 and 5. Whenever I turn again the wheel on the same direction, it prints again 4 and 5. Sometimes, it happens three or four times, before it gives value of 6, then 7, and so on...

I hope you could help me again on this... I dunno how to debounce this one. :|

First thing to do is to comment out the SerialPrint statement inside the ISR function, SerialPrint commands are blocking commands and are sure to cause loss of pulses coming from the encoder. Place the print statements in the main loop function and then test again.

Then if you still have missing counts you can cut the interrupts in half by changing this:

attachInterrupt(0, doEncoder, CHANGE);

To this:

attachInterrupt(0, doEncoder, FALLING);

Mice use optical encoders so there should never be any contact bounce to worry about.

Lefty

will surely do it, meanwhile... I'm using mechanical encoder instead of optical encoder. Do optical encoders perform better than mechanical encoders for mouse scroll wheel?

retrolefty: First thing to do is to comment out the SerialPrint statement inside the ISR function, SerialPrint commands are blocking commands and are sure to cause loss of pulses coming from the encoder. Place the print statements in the main loop function and then test again.

Then if you still have missing counts you can cut the interrupts in half by changing this:

attachInterrupt(0, doEncoder, CHANGE);

To this:

attachInterrupt(0, doEncoder, FALLING);

Mice use optical encoders so there should never be any contact bounce to worry about.

Lefty

Hey Lefty, I actually don't know how to alter the program. As I scan again the program, it seems I can omit the "Extended" part since it only tells the user how the short version works.

So the code will be like this...

#define encoder0PinA  2
#define encoder0PinB  3

volatile unsigned int encoder0Pos = 0;

void setup() { 


  pinMode(encoder0PinA, INPUT); 
  digitalWrite(encoder0PinA, HIGH);       // turn on pullup resistor
  pinMode(encoder0PinB, INPUT); 
  digitalWrite(encoder0PinB, HIGH);       // turn on pullup resistor

  attachInterrupt(0, doEncoder, CHANGE);  // encoder pin on interrupt 0 - pin 2
  Serial.begin (9600);
  Serial.println("start");                // a personal quirk

} 

void loop(){
// do some stuff here - the joy of interrupts is that they take care of themselves
}

void doEncoder() {
  /* If pinA and pinB are both high or both low, it is spinning
   * forward. If they're different, it's going backward.
   *
   * For more information on speeding up this process, see
   * [Reference/PortManipulation], specifically the PIND register.
   */
  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos++;
  } else {
    encoder0Pos--;
  }

  Serial.println (encoder0Pos, DEC);
}

And it gives also the same result as the previous one. If you could help me how will I insert the Serial print, because I really don't know how and its my first time to manage an Arduino. Sorry for that. But I'm trying to learn it with myself also. If so, can you alter for me the code. It will really be a big help from you sir! Thanks. :)

rency0722: will surely do it, meanwhile... I'm using mechanical encoder instead of optical encoder. Do optical encoders perform better than mechanical encoders for mouse scroll wheel?

Yes, mechanical encoders use mechanical switch contacts that 'bounce' when first changing states and if used as a interrupt signal often cause false steps. Optical (and magnetic) encoders use 'electronic' switches to change states so there are only valid pulse transitions on the channel signals.

However when talking about mouses, optical mouse usually means a mouse that does not use a rolling ball to track movement, but rather a laser light beam reflecting on a surface to detect movement. I'm not sure how to tell if a specific mouse is using mechanical or optical encoder for it's scroll wheel. I would think they all use a optical encoder for that function?

Lefty

rency0722:

retrolefty: First thing to do is to comment out the SerialPrint statement inside the ISR function, SerialPrint commands are blocking commands and are sure to cause loss of pulses coming from the encoder. Place the print statements in the main loop function and then test again.

Then if you still have missing counts you can cut the interrupts in half by changing this:

attachInterrupt(0, doEncoder, CHANGE);

To this:

attachInterrupt(0, doEncoder, FALLING);

Mice use optical encoders so there should never be any contact bounce to worry about.

Lefty

Hey Lefty, I actually don't know how to alter the program. As I scan again the program, it seems I can omit the "Extended" part since it only tells the user how the short version works.

So the code will be like this...

#define encoder0PinA  2
#define encoder0PinB  3

volatile unsigned int encoder0Pos = 0;

void setup() {

 pinMode(encoder0PinA, INPUT);  digitalWrite(encoder0PinA, HIGH);       // turn on pullup resistor  pinMode(encoder0PinB, INPUT);  digitalWrite(encoder0PinB, HIGH);       // turn on pullup resistor

 attachInterrupt(0, doEncoder, CHANGE);  // encoder pin on interrupt 0 - pin 2  Serial.begin (9600);  Serial.println("start");                // a personal quirk

}

void loop(){ // do some stuff here - the joy of interrupts is that they take care of themselves }

void doEncoder() {  /* If pinA and pinB are both high or both low, it is spinning   * forward. If they're different, it's going backward.   *   * For more information on speeding up this process, see   * [Reference/PortManipulation], specifically the PIND register.   */  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {    encoder0Pos++;  } else {    encoder0Pos--;  }

 Serial.println (encoder0Pos, DEC); }




And it gives also the same result as the previous one. If you could help me how will I insert the Serial print, because I really don't know how and its my first time to manage an Arduino. Sorry for that. But I'm trying to learn it with myself also. If so, can you alter for me the code. It will really be a big help from you sir! Thanks. :)

In the doEncoder function place a comment mark (//) in front of the serial command to diable it, like this:

void doEncoder() {
  /* If pinA and pinB are both high or both low, it is spinning
   * forward. If they're different, it's going backward.
   *
   * For more information on speeding up this process, see
   * [Reference/PortManipulation], specifically the PIND register.
   */
  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos++;
  } else {
    encoder0Pos--;
  }

 // Serial.println (encoder0Pos, DEC);

}

Then add the serial command into your main loop function like this.

void loop(){
// do some stuff here - the joy of interrupts is that they take care of themselves
Serial.println (encoder0Pos, DEC);
}

Finally I would change the interrupt type in the setup function from this:

void setup() { 


  pinMode(encoder0PinA, INPUT); 
  digitalWrite(encoder0PinA, HIGH);       // turn on pullup resistor
  pinMode(encoder0PinB, INPUT); 
  digitalWrite(encoder0PinB, HIGH);       // turn on pullup resistor

  attachInterrupt(0, doEncoder, CHANGE);  // encoder pin on interrupt 0 - pin 2
  Serial.begin (9600);
  Serial.println("start");                // a personal quirk

} 

To this ( replace CHANGE with FALLING):

[code]

void setup() { 


  pinMode(encoder0PinA, INPUT); 
  digitalWrite(encoder0PinA, HIGH);       // turn on pullup resistor
  pinMode(encoder0PinB, INPUT); 
  digitalWrite(encoder0PinB, HIGH);       // turn on pullup resistor

  attachInterrupt(0, doEncoder, FALLING);  // encoder pin on interrupt 0 - pin 2
  Serial.begin (9600);
  Serial.println("start");                // a personal quirk

}

Try that and see if it helps.

Lefty

[/code]

retrolefty:

rency0722: will surely do it, meanwhile... I'm using mechanical encoder instead of optical encoder. Do optical encoders perform better than mechanical encoders for mouse scroll wheel?

Yes, mechanical encoders use mechanical switch contacts that 'bounce' when first changing states and if used as a interrupt signal often cause false steps. Optical (and magnetic) encoders use 'electronic' switches to change states so there are only valid pulse transitions on the channel signals.

However when talking about mouses, optical mouse usually means a mouse that does not use a rolling ball to track movement, but rather a laser light beam reflecting on a surface to detect movement. I'm not sure how to tell if a specific mouse is using mechanical or optical encoder for it's scroll wheel. I would think they all use a optical encoder for that function?

Lefty

FYI, we've been hunting mice for last week and destroyed them, and all of them do have mechanical encoders. So we're hoping to have an optical encoder for us to work it easily but still, we see such mechanical encoders for the scroll. Our bad.

So we have no time to spare to search for an optical-encoder mouse scroll wheel, and we should still try to work on the mechanical encoder until it works perfectly for the program. And luckily, I'm so desperate to make it work right now since I still have three more days... :)

In my analysis, it should be debounced (the encoder), but I don't know how...

Thanks Lefty. Will try it ASAP. I'm really crossing my finger tightly with this! XD

Ok... I've tried the new code, and it gives me another continuous and unending Serial Print... And still, the encoder jumps from 0 to a greater number and vice versa..

I think it doesn't need to be placed on loop since it continuously loops the Serial Print...

Is it possible to debounce the encoder just like a pushbutton switch? if ever?

And seriously, can I debounce it by getting the encoder0Pos value and then if the last is not equal to current minus 1 (for clockwise-increasing) or current plus 1 (for C/CW-decreasing value)... something like that...

Is it possible to debounce the encoder just like a pushbutton switch? if ever?

Possible, contact debouncing is either done with hardware using external resistor/caps or in software using delays. but I can’t tell you specidic hardware values of caps and resistors or how to wire it up. And software delay() doesn’t work inside of ISR routines because all interrupts are disabled at that time.

Not sure what further help I can give you. I first played around with a cheap mechanical encoder switch a couple of years ago and never was happy with it’s performance. I finally found a cheap optical encoder at a surplus dealer and that worked great.

Dealing with contact bounce and using interrupt inputs is a difficult task at best.

Lefty

Oh I see... so its quite a hard task to debounce this dumb mech encoder... i'm now wondering where will I get a mouse that has an optical encoder with it.. geez...

To my desperation, I got to have it ordered online since I'm from the Philippines. Here, they don't have any rotary encoders for mouse scroll wheels.

Can you suggest where can I get one on your place, so I can place order online or through my close relatives abroad? It was a bad decision but for good since I got to have it working by the end of this week.

i’m now wondering where will I get a mouse that has an optical encoder with it… geez…

Not a clue. I would suspect most mouse descriptions wouldn’t state what kind of encoder their scroll wheel uses?

retrolefty:

i'm now wondering where will I get a mouse that has an optical encoder with it.. geez...

Not a clue. I would suspect most mouse descriptions wouldn't state what kind of encoder their scroll wheel uses?

yes.. and its hard to buy mice without knowing that it doesn't have an optical encoder in it.. argh. fail mechanical encoder..

by the way, I read again Steve Kamerman's blog (http://www.stevekamerman.com/2010/12/understanding-a-mouse-scroll-wheel/) and he used his dumped Microsoft Mouse...