Inexpensive rotary encoder


- get a dead harddisk, preferrably an older one
- take out the flat spindle motor which spins the HD platters
- connect wires to two different motor output leads (any)
- build this circuit:


(Resistor values aren't critical - junk box parts. LM393 is a cheap dual comparator)

The circuit outputs are high as default. When the spindle is turned, there will be pulses on the outputs. The lower square wave output signal leads or lags 90 degrees in respect to the upper signal - it depends on the direction the spindle is turned. The circuit works from 2V up to 36V, so 3.3V logic works too. The LM393 gives out good pulses with as little as 5mV differential input, which means that this circuit works even when the motor is turned very slowly.

A HD stepper/spindle motor is ideally suited as an encoder because it isn't bulky nor noisy, and it can be spun easily (doesn't "brake" and carries good momentum), just like commercial units. And, it doesn't cost anything.

For a similar setup with example source code, see: Rotary encoders using stepper motors (Richard Hosking, VK6BRO)

To be of more use, the phase shift encoded signal is usually decoded into two logic signals e.g. 'UP' and 'DOWN'. This can be done with a bunch of discrete flip-flops. For example if you turn the wheel clockwise, the 'UP' signal is pulsed on each step. If counterclockwise, the 'DOWN' signal is pulsed at each step. An example of a discrete decoder can be found in a post in the nativeinstruments.de forum. I also took the liberty of mirroring Dr BeaT's schematic here, in case the forum goes down. Note that I haven't checked if the circuit works 100%, but I've heard it works ;-), and to me it looks quite convincingly like it is going to work.

There also exist dedicated 'quadrature decoder' chips that can be used. But, they often contain a multi-bit counter register in addition to UP/DOWN outputs, together with a load of other features, and are quite expensive. If you don't need UP/DOWN pulses and need just to maintain the number of times the wheel was jogged in some direction, then a simple 4-bit or similar counter may be sufficient. This means you can use one of the many bidirectional synchronous or asynchronous counters (aka Up/Down Counters) available, like e.g. a 74LS669: one signal is fed to both CLK and RCO to create the count, the other to U/!D to specify the counting direction.

On the other hand, if you are already using a microprocessor in your system, decoding and possible wheel debouncing is very easy to perform in software completely. Here is some example code for the MSP430 series:

// Port P2.0: wheel control
// Wheel encoder signal outputs go to port 2 pins 2.0 and 2.1,
// with interrupt enabled on pin 2.0

#include <msp430x14x.h>


// -- global counter for wheel steps

volatile signed int wheel_counter;


// -- port 2 interrupt handler

interrupt[PORT2_VECTOR] void intPort2(void) {

  // get pin 2.1 state
  int tmp = P2IN & BIT2; 

  // interrupt caused by port pin 2.0?
  if(P2IFG &= BIT0) { // (simultaneously, reset other interrupt flags)
      // pin 2.0 is low, so the state of pin 2.1 
      // determines which way the wheel was turned:
      //   1=up, 0=down  
      // currently the counter is changed by +-1, but of course this
      // can be modified (for example, for a 'Fast Scan' button
      // in the circuit)
 
     if( (0!=tmp) && (wheel_counter<32000) ) 
     {   wheel_counter += 1;  }
     else if( (0==tmp) && (wheel_counter>-32000) ) 
     {   wheel_counter -= 1;  }
  } 
  
  // reset interrupt flags (actually, bit0, if it was set)
  P2IFG = 0x00;
}


// -- init

void initInterrupts(void) {

  // enable tuning wheel interrupt on pin 2.0 (more info in
  // MSP430F14x series users guide, page 135)
  P2SEL &= ~(BIT0+BIT1);      // don't use modules, just i/o
  P2DIR &= ~(BIT0+BIT1);      // set both 2.0 and 2.1 to inputs
  P2IES |= BIT0;              // 2.0 high->low generates interrupt
  P2IFG |= 0x00;              // clear interrupt flags

  // reset the step counter
  wheel_counter = 0;
  
  // enable interrupts (users guide page 47, table 3-5)
  P2IE  |= BIT0;              // pin 2.0 int enable  
  IE1 |= NMIIE;
  _EINT();
}


The idea basically is: connect the two encoder data lines to two input pins of your microcontroller/processor, set one pin to cause an interrupt at rising (or falling) edges of the incoming signal, and in the interrupt handler check the state (high or low) of the other pin. If your uC doesn't offer interrupts on input data pins, you'll have to use polling which is less efficient. E.g. you'll have to set a function or task to regularly (in short enough intervals) check the state of the first pin. If its state changes, check the current state of the other pin, and increment/decrement your counter accordingly. Of course, detect only one state change direction e.g. low->high and skip high->low, otherwise it counts e.g. up and then immediately down again and the counter remains zero.



(C) 2003 Jan Florian Wagner, OH2GHR