2014년 3월 21일 금요일

아두이노에 마우스 센서 갖다 붙이기

로지텍 마우스에 붙어 있는 마우스 센서를 사용하기 위해서 우선 열어 보았다. 센서 같이 보이는 칩의 상단에 표기된 봐로는 H2000이라고 되어 있지만, 실제로 정확한 마우스 센서의 이름은 HDNS 2000이다. 저렴한 마우스를 직접 이용하는 것이 손 쉬은 것은 필요한 부품은 일일이 구하로 다닐 필요 없이 약간의 기판 쪼가리만 가지고 있으면 아두이노와 연결 할 수 있다는 점이다.

실제로 작업에 들어 가기에 앞서 HDNS 2000의 데이터 시트를 살펴보자. 아래의 회로는 데이터 시트에서 추천하는 인터페이스 회로이다. 두 개를 제안하고 있지만, 내가 사용하고자하는 용도에는 다음의 회로가 적합하다.


 위의 회로에서 Cypress사의 Cyxxxx라는 것은 무시하고 HDNS2000과3.3V용 레귤레이터 그리고 다섯개의 신호선만 사용할 것이다. 실제로 로지텍 마우스를 열어 보면 거의 동일하다. 다섯개의 신호선중 NRESET이라는 신호선은 실제로 사용하지 않아도 무관한 신호선이다. 그냥 내버려 두어도 무관한다. 나중에 아두이노에서 때때로 센서를 초기화 해야 할 경우가 발생하면 그 때 가서 연결해보 무방하다. 아무튼 지금은 간단하기 네 개의 신호선 XA, XB, YA, YB만 사용할 것이다. 이 네 신호선에 대한 설명도 데이터 시트를 살펴보면 복잡하다. 예전에 마이크로 마우스를 건드려 본 경험이 있거나, 모터를 만지작 해 보았다면 encoder라는 것을 들어 보았을 것이다. HDNS2000은 아주 간단히 설명하자면 X축과 Y축 방향으로 encoder가 하나씩 들어 있는 칩으로 보면 정확할 것이다. 실제로 코드도 encoder를 다루는 방식으로 접근한다.

 

XA, XB, YA, YB 핀은 각각 아두이노의 D8, D9, D10, D11핀에 연결한다. 보면 알겠지만, 아쉽게도 외부 인터럽트 핀은 INT0, INT1으로 두개 밖에 지원하지 않는다.












간단히 이야기 하자면 HDNS 2000 센서는 X축과 Y축을 위해 각각 두 개의 엔코더 신호선을 제공한다. 아쉽게도 직접 영상을 얻어 올 수 있는 통로는 제공되지 않는다. 아무튼 총 4 신호선을 외부 인터럽트로 다루기 위해서 코드가 복잡해 진다. 그래서 이미 만들어 져 있는 코드를 아두이노 IDE에 외부 라이브러리 형태로 설치해야 한다. 다음의 누리집에 가면 pinchangeint라는 라이브러리를 받아 볼 수 있다.

 http://code.google.com/p/arduino-pinchangeint/downloads/list  

가장 최신의 것을 다운로드하고 윈도우 시스템의 경우 다음의 경로에

 C:\Users\Anonymous\Documents\Arduino\libraries  

압축을 해제하고, 다음의 코드를 컴파일하자.


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include <PinChangeInt.h>
//#include <PinChangeIntConfig.h>
        
#define ledPin (13)
#define encoderXPinA (10)
#define encoderXPinB (11)
#define encoderYPinA (8)
#define encoderYPinB (9)

volatile unsigned int encoderXPos = 0;
volatile unsigned int encoderYPos = 0;
unsigned int lastReportedXPos = 1;
unsigned int lastReportedYPos = 1;
static boolean rotatingX = false;
static boolean rotatingY = false;

boolean XA_set = false;
boolean XB_set = false;
boolean YA_set = false;
boolean YB_set = false;

// the setup routine runs once when you press reset:
void setup() {                
  Serial.begin(9600);
  Serial.println("hello");

  pinMode(ledPin, OUTPUT);
  pinMode(encoderXPinA, INPUT);
  pinMode(encoderXPinB, INPUT);
  pinMode(encoderYPinA, INPUT);
  pinMode(encoderYPinB, INPUT);
  
  //digitalWrite(encoderXPinA, HIGH);
  //digitalWrite(encoderXPinB, HIGH);
  //digitalWrite(encoderYPinA, HIGH);
  //digitalWrite(encoderYPinB, HIGH);
  
  // Attach pin change interrupt service routines from the Wiegand RFID readers
  PCintPort::attachInterrupt(encoderXPinA, doEncoderXA, CHANGE); // attach a PinChange Interrupt to our pin on the rising edge
  PCintPort::attachInterrupt(encoderXPinB, doEncoderXB, CHANGE); // attach a PinChange Interrupt to our pin on the rising edge
  PCintPort::attachInterrupt(encoderYPinA, doEncoderYA, CHANGE); // attach a PinChange Interrupt to our pin on the rising edge
  PCintPort::attachInterrupt(encoderYPinB, doEncoderYB, CHANGE); // attach a PinChange Interrupt to our pin on the rising edge

}

// the loop routine runs over and over again forever:
void loop() {
  //rotatingX = true; // reset the debouncer
  //rotatingY = true; // reset the debouncer
  
  if (lastReportedXPos != encoderXPos ||
      lastReportedYPos != encoderYPos) {
    Serial.print("X:");
    Serial.print(encoderXPos, DEC);
    lastReportedXPos = encoderXPos;
    Serial.print(" Y:");
    Serial.println(encoderYPos, DEC);
    lastReportedYPos = encoderYPos;
  }
  
  digitalWrite(ledPin, HIGH);
  delay(200);
  digitalWrite(ledPin, LOW);
  delay(200);               // wait for a second
}

// Interrupt on A changing state
void doEncoderXA(void)
{
  // debounce
  if (rotatingX) delay (1);  // wait a little until the bouncing is done
  // Test transition, did things really change? 
  if (digitalRead(encoderXPinA) != XA_set) {  // debounce once more
    XA_set = !XA_set;
    // adjust counter + if A leads B
    if (XA_set && !XB_set) encoderXPos += 1;
    rotatingX = false;  // no more debouncing until loop() hits again
  }
}

// Interrupt on B changing state, same as A above
void doEncoderXB(){
  if (rotatingX) delay (1);
  if (digitalRead(encoderXPinB) != XB_set) {
    XB_set = !XB_set;
    //  adjust counter - 1 if B leads A
    if (XB_set && !XA_set) encoderXPos -= 1;
    rotatingX = false;
  }
}

// Interrupt on A changing state
void doEncoderYA(void)
{
  // debounce
  if (rotatingY) delay (1);  // wait a little until the bouncing is done
  // Test transition, did things really change? 
  if (digitalRead(encoderYPinA) != YA_set) {  // debounce once more
    YA_set = !YA_set;
    // adjust counter + if A leads B
    if (YA_set && !YB_set) encoderYPos += 1;
    rotatingY = false;  // no more debouncing until loop() hits again
  }
}

// Interrupt on B changing state, same as A above
void doEncoderYB(){
  if (rotatingY) delay (1);
  if (digitalRead(encoderYPinB) != YB_set) {
    YB_set = !YB_set;
    //  adjust counter - 1 if B leads A
    if (YB_set && !YA_set) encoderYPos -= 1;
    rotatingY = false;
  }
}

코드를 테스트 하기위해서 렌즈와 뚜껑을 덥고,  아두이노 IDE에서 실행 이미지를 다운로드 하여 실제로 마우스 처럼 굴러가는 지 확인해 보았다.













 아직은 정확도를 확인해 보지는 못하였다.


참고 자료
http://photonicsguy.ca/_media/projects/electronics/opticalmouse/h2000.pdf
http://www.pighixxx.com/pgdev/Temp/Arduino_uno_Pinout.pdf
http://playground.arduino.cc/Main/RotaryEncoders#.UysL-IUmk7k
http://forum.arduino.cc/index.php?topic=84040.0
http://code.google.com/p/arduino-pinchangeint/
http://arduino.cc/en/Guide/Libraries#.UysE2IUmk7k
http://codeformatter.blogspot.com/
http://hilite.me/