If you needed a quadrature encoder and couldn’t make sense of the three-pin IR receiver component you found when you ripped up an old Logitech mouse, here’s some help.

The receiver chip is likely a custom Logitech chip which uses a single wire interface to communicate with a custom controller. In my case the controller IC is CP5928AM, for either of which a datasheet is nowhere to be found. They be proprietary.

Fortunately, I found a reference to a relevant Logitech patent (US6552716) on Hackaday forums, and while it only explains general operation and doesn’t offer exact timings, (and I don’t own an oscilloscope), a little trial and error has produced working code.

Based on the patent, I found that data can be read as follows:

  1. You’ll need two free I/O pins on your MCU. Connect one each to the IRLED (I’ll call it LED) and the data pin of the receiver (I’ll call it data pin). Use existing wiring to figure out the pins. Start with LED off, and data pin set to INPUT on the MCU.
  2. Pulse the LED on and then off. This produces a light pulse which changes the state of a couple of SR latches in the receiver to produce an output which you will now read off a parallel-to-serial shifter.
  3. Change the data pin to OUTPUT and drive the data pin high and then low. Then change it back to INPUT. Now read the data pin. You have your first directional bit.
  4. Again, change the data pin to OUTPUT and drive the data pin high and then low. Then change it back to INPUT. Now read the data pin. You have your second directional bit.
  5. Each bit indicates rotation in one direction. If both are 0, there is no movement. If both are 1, then you are not reading data fast enough.
  6. Repeat steps 2 to 5.

Here’s the code I wrote to test it with my Arduino:

/*************************************/
/*  Logitech Quadrature Encoder      */
/*  Single wire interface            */
/*  Code based on patent US6552716.  */
/*************************************/

// data, IRLED pins
#define QEDATAPIN 7
#define QELEDPIN 6

int qeOld1, qeOld2, turns=0, overflows=0;

void setup() {
  Serial.begin(115200);
  pinMode(QEDATAPIN, INPUT);
  //turn irled off -- high is OFF
  pinMode(QELEDPIN, OUTPUT);
  digitalWrite(QELEDPIN, HIGH);
  qeOld1=qeOld2=0;
}

//in microseconds, increase if they don't work
#define QE_SENSOR_PULSEWIDTH 2
#define QE_IRLED_PULSEWIDTH 2

inline void qeLEDlightPulse(){
  digitalWrite(QELEDPIN, LOW);
  delayMicroseconds(QE_IRLED_PULSEWIDTH);
  digitalWrite(QELEDPIN, HIGH);
}

inline byte getQEDataBit(){
  //switch to output mode and
  //pulse data line high and then low
  delayMicroseconds(QE_SENSOR_PULSEWIDTH/2);
  pinMode(QEDATAPIN, OUTPUT);
  digitalWrite(QEDATAPIN, HIGH);
  delayMicroseconds(QE_SENSOR_PULSEWIDTH);
  digitalWrite(QEDATAPIN, LOW);
  delayMicroseconds(QE_SENSOR_PULSEWIDTH);
  //switch back to input mode and
  //read data bit
  pinMode(QEDATAPIN, INPUT);
  delayMicroseconds(QE_SENSOR_PULSEWIDTH);
  return digitalRead(QEDATAPIN);
}

void loop() {
  qeLEDlightPulse();
  byte qe1 = getQEDataBit();
  byte qe2 = getQEDataBit();
  
  if(qeOld1!=qe1 || qeOld2!=qe2){
    if(qe1!=0)
      turns++;
    if(qe2!=0)
      turns--;
    if(qe1==qe2 && qe1==1)
      overflows++;
    Serial.print(turns);
    Serial.print("\t-- ");
    Serial.print(overflows);
    Serial.print("\t-- ");
    Serial.print(qe1);
    Serial.println(qe2);
  }
  qeOld1=qe1;
  qeOld2=qe2;
}