High resolution harddisk rotary encoder,

Hi all,

I've been working on using a harddisk motor+disk as a scroll wheel. That work is still in progress.

However, I managed to use/program an HD motor as a rotary encoder that uses áll windings to in-/decrement.

So the code below is for my 4 lead, 12 coil HDmotor with 12 pulses per revolution resolution :slight_smile:

Maybe you can use it, maybe you can improve it.
Use it as you please and feedback would be appreciated.

Codeblock 1: Clean condensed code without comments
Codeblock 2: Code with explanation

Codeblock1

#include <Streaming.h>
#include <PinChangeInterrupt.h>

const byte L1 = 2; 
const byte L2 = 3; 
const byte L3 = 4;

int intOutput;

byte byteDirection = 0;

void setup() {
  Serial.begin(9600); 
  attachInterrupt(digitalPinToInterrupt(L1), isrRegisterPulseOnL1, RISING);           
  attachInterrupt(digitalPinToInterrupt(L2), isrRegisterPulseOnL2, RISING);           
  attachPCINT(digitalPinToPCINT(L3), isrRegisterPulseOnL3, CHANGE);
}

void trace(){
  Serial << intOutput << endl;
}

void handlePulse(int intLead, boolean bDir){
  if(bDir){ 
    bitSet(byteDirection, intLead);
    if(byteDirection == 7){                             
      intOutput++;
    }
  }else{
    bitClear(byteDirection, intLead);
    if(byteDirection == 0){
      intOutput--;
    }
  }
  trace();
}

void loop() {
}

void isrRegisterPulseOnL1(){    
  if(digitalRead(L3)){
    handlePulse(0, 1);
  }else{
    handlePulse(0, 0);
  }
}

void isrRegisterPulseOnL2(){
  if(digitalRead(L1)){
    handlePulse(1, 1);
  }else{
    handlePulse(1, 0);
  }
}

void isrRegisterPulseOnL3(){
  if(digitalRead(L3)){               
    if(digitalRead(L2)){
      handlePulse(2, 1);
    }else{
      handlePulse(2, 0);
    }
  }
}

Codeblock2

/*
Code to use all leads on a salvaged harddisk motor when using it as a rotary encoder
In this case a 4 lead motor, 1 common and 3 "signal" leads
Common connected to ground and leads L1, L2 and L3 connected to arduino inputs via an LM324 opamp
*/

#include <Streaming.h>              // for serial output
#include <PinChangeInterrupt.h>     // library to use NON-interrupt pins as interrupt pins


//HD Motor > LM324 > arduino pins
const byte L1 = 2;    // arduino (UNO) interrupt pin  
const byte L2 = 3;    // arduino (UNO) interrupt pin  
const byte L3 = 4;    // NOT AN arduino UNO interrupt pin, PinChangeInterrupt.h is used to use it as an interrupt pin

int intOutput;        // int to track output increase/decrease 

// To prevent changes due to "jitter" when starting or stopping the disk
// the output value is only changed when the three previous changes were in the same direction
// This is registered in a byte, the first three bits are used to store the previous changes as 0 or 1
// The output is only increased when the byte is 0111 (dec 7) and only decreased when the byte is 0000 (dec 0)
byte byteDirection = 0;

void setup() {
  Serial.begin(9600);                                                                 // initialize serial communication
    
  attachInterrupt(digitalPinToInterrupt(L1), isrRegisterPulseOnL1, RISING);           // set a rising edge interrupt on pin L1  
  attachInterrupt(digitalPinToInterrupt(L2), isrRegisterPulseOnL2, RISING);           // set a rising edge interrupt on pin L2  
  attachPCINT(digitalPinToPCINT(L3), isrRegisterPulseOnL3, CHANGE);                   // set a interrupt on pin L3, this is a CHANGE interrupt, it will be tested for RISING in isrRegisterPulseOnL3
}

void loop() {
      // Move along, nothing to see here ;)
      trace();
} //end loop

void trace(){
  Serial << intOutput << endl;                                                        // trace function for debugging
}

// Lead 1 =========================================================================
void isrRegisterPulseOnL1() {

    // To test all the leads when an interrupt is fired on one lead, uncomment the next line:
    // Serial << "L1: " << digitalRead(L1) << "    L2: " << digitalRead(L2) << "    L3: " << digitalRead(L3) << endl;
    
    // My results (yours might be different, depending on wiring of motor/circuit): 
    // clockwise:           L1: 1    L2: 0    L3: 1
    // counter clockwise:   L1: 1    L2: 1    L3: 0
    
    if(digitalRead(L3)){
      handlePulse(0, 1);
    }else{
      handlePulse(0, 0);
    }
}

// Lead 2 =========================================================================
void isrRegisterPulseOnL2() {

    // Serial << "L1: " << digitalRead(L1) << "    L2: " << digitalRead(L2) << "    L3: " << digitalRead(L3) << endl;
    // Result:
    // clockwise:           L1: 1    L2: 1    L3: 0
    // counter clockwise:   L1: 0    L2: 1    L3: 1

    if(digitalRead(L1)){
      handlePulse(1, 1);
    }else{
      handlePulse(1, 0);
    }
}

// Lead 3 =========================================================================
void isrRegisterPulseOnL3() {

    if(digitalRead(L3)){                // using the PinChangeInterrupt library only CHANGE can be detected, BUT you only want to detect a rising change by testing "if(digitalRead(L3)) is 1"
      
      // Serial << "L1: " << digitalRead(L1) << "    L2: " << digitalRead(L2) << "    L3: " << digitalRead(L3) << endl;
      // Result:
      // clockwise:           L1: 0    L2: 1    L3: 1
      // counter clockwise:   L1: 1    L2: 0    L3: 1

      if(digitalRead(L2)){
        handlePulse(2, 1);
      }else{
        handlePulse(2, 0);
      }
    }
}

void handlePulse(int intLead, boolean bDir){

  if(bDir){                                             // direction: clockwise

    bitSet(byteDirection, intLead);                     // set the bit, for intLead (where L1=0, L2=1, L3=2), in byteDirection 
    
    if(byteDirection == 7){                             // if the previous three changes were in the same direction, byteDirection will be 0111 = dec 7
      intOutput++;                                      // increase output by 1
      //trace();
    }
    
  }else{                                                // direction counter clockwise

    bitClear(byteDirection, intLead);
    
    if(byteDirection == 0){
      intOutput--;
      //trace();
    }
    
  }
  
}

Thanks for sharing your code, went through it when I was trying to achieve something very similar.
I tried using both Arduino Nano (CH330 chip) and Leonardo but the ADC speed was too slow, even when increasing it in code.

I bought the Arduino MKR zero with its SAMD21 processor that runs @ 48MHz. I got a lot better results with it versus nano/leonardo.
I also added the "AnalogReadFast.h" library and added "AnalogReadFast (10);" in the setup code, this made things a lot faster. Using AnalogReadFast(12); made it slower and less sensitive when changing direction. Link to the library: GitHub - avandalen/avdweb_AnalogReadFast

I'm going to test using the LM324 also to induce some hysteresis as I want to use this for changing the volume on my PC.

Anyhow here's the code I'm using currently, it's pretty simple but works for me. I'm using an HDD motor with four leads on it.

#include <avdweb_AnalogReadFast.h>


#define L1 A0
#define L2 A1
#define L3 A2
boolean L1Val = 0;

  
void setup() {
 Serial.begin(250000);
 analogReadFast(10);
 analogReference (AR_INTERNAL1V0);
 pinMode(L1, INPUT);
 pinMode(L2, INPUT);
 pinMode(L3, INPUT);

}

void loop() {
 if ((analogRead(L1) > 200) && (L1Val == 0)) {
   L1Val = 1;
 }

 if ((L1Val == 1) && (analogRead(L2) > 200)) {
   Serial.println("clockwise");
   L1Val = 0;
   delay(100);
 }

 if ((L1Val == 1) && (analogRead(L3) > 200)) {
   Serial.println("anticlockwise");
   L1Val = 0;
   delay(100);
 }
}

RotaryEncoder_250000_baud.ino (629 Bytes)