Understanding a Mouse Scroll Wheel

Having been rather addicted to understanding electronics lately, I’m always looking for something to play with. Last week I was about to throw out a broken wireless Microsoft mouse, but I caught myself and ended up taking it apart.

Rotary Encoder from a Mouse

Rotary Encoder from a Mouse

I dug down to the scroll wheel and found out it was a nice little rotary encoder - there’s an LED pointed at two phototransistors (light detectors) in it, as well as a circle of tiny black posts that pass between the LED and the detectors, as you can see to the left.  When the posts pass in front of the detectors, the Arduino reads them and is able to determine which direction they are going and increments or decrements a variable that I can access.

It took me about 4 hours to figure out how to hook this thing up, so I figured I would share the info in case someone else wants to use a rotary encoder from a Microsoft mouse in their project.

If you are holding the encoder so that the rotating half is facing you and the wires are facing down, the pinout is as follows:

Pin 1: Photo Sensor 1
Pin 2: Vcc
Pin 3: Photo Sensor 2
Pin 4: IR LED +
Pin 5: IR LED -

Unlike most of the examples I found for using a rotary encoder, this one requires +Vcc on Pin 2 - power will not flow the other way.  The LED power is very picky too;  I am powering it from the 5v regulated supply on my Arduino through a 1k resistor to keep the right amount of light in the encoder.  Too much light and both sensors will be high for too long and too little, neither will go high.  The hardest part to figure out was the voltage divider circuit for the sensors.  I could get them working ok on an analog input - swinging between about 80-600, but it wasn’t clean enough to input as digital until I found the right resistor: 10k.  This nicely divides the input between ground and +5v so the encoder can be read.

Here is the Arduino sketch for the project as shown in the video above:

// Arduino Rotary Encoder from Mouse Scroll Wheel
// and SparkFun 4 Digit Serial 7-Segment Display
// by Steve Kamerman 12/18/2010
// https://www.stevekamerman.com/2010/12/understanding-a-mouse-scroll-wheel/

// These MUST be on interrupt pins!
#define encoderPinA 2
#define encoderPinB 3

#define displayTXPin 10
#define displayRXPin 11

// used only for sprintf()
#include <stdio.h>

// If you don't have NewSoftSerial, grab it from:
// http://arduiniana.org/libraries/NewSoftSerial/
#include <NewSoftSerial.h>
NewSoftSerial displaySerial(displayRXPin, displayTXPin);

// the value - this must be volatile because it is modified
// during an interrupt function and used in a normal function
volatile unsigned int encoderPos = 0;

void setup() {
  // setup the encoder
  pinMode(encoderPinA, INPUT); 
  pinMode(encoderPinB, INPUT); 
  attachInterrupt(0, readEncoderA, CHANGE);
  attachInterrupt(1, readEncoderB, CHANGE);  
  
  // setup the display
  pinMode(displayTXPin, OUTPUT);
  displaySerial.begin(9600);
  // reset display
  displaySerial.print("v");
  // Set display brightness
  displaySerial.print(0x7A, BYTE);
  displaySerial.print(0x05, BYTE);
  // set the display to "   0"
  displaySerial.print("xxx0");
}
void loop(){
  displayNumber(encoderPos);
}

// prints the given number, right justified padded with spaces
// sprtinf() is not a great idea if you want to save space
// but I'm really not too worried about it :)
void displayNumber(int num){
  char buf[4];
  sprintf(buf, "%4d", num);
  displaySerial.print(buf);
}

void readEncoderA(){
  if (digitalRead(encoderPinA) == HIGH){
    encoderPos += (digitalRead(encoderPinB) == LOW)? 1: -1;
  }else{
    encoderPos += (digitalRead(encoderPinB) == HIGH)? 1: -1;
  }
}
void readEncoderB(){
  if (digitalRead(encoderPinB) == HIGH){
    encoderPos += (digitalRead(encoderPinA) == HIGH)? 1: -1;
  }else{
    encoderPos += (digitalRead(encoderPinA) == LOW)? 1: -1;
  }
}

stevekamerman

COO @scientiamobile