2016年2月10日 (水)

Arduino Micro を使って、USB 小型 キーボードを自作 (製作途中、暫定公開)

小型USBキーボード自作の途中経過、詳しい説明は後日。とりあえず動きました。

・過去のブログ記事「USBキーボードは自作できるのか下調べをしてみる」と合わせて読んでください。

2017/02/22:キーボードLED関連を追記、下の方に。

※注意!:私、はっきり言って、ほぼ素人です!!!、電子工作をちょろっとと、無印 C言語を少しかじったことがあるだけです。間違い等がある可能性が十分にありますので、ここはあくまで参考程度として、みなさん各自で試行錯誤してくださいませ。ここの間違い指摘は大歓迎です。

画像クリックで拡大します。

・使用したマイコンボードは、 Arduinoシリーズで、USB対応の ATmega32U4 を搭載した Arduino Micro

Pict0008

・使用している Aruduino の開発環境は、http://www.arduino.org/Arduino IDE 1.7.x

・今現在想定している、キー配置。QWERTY英語配列。
Image

・現在のキーマトリクス
_01

・回路図(結線図)
Image01
・Arduino は、デフォルトでアナログ入出力に設定されている I/Oピンを、デジタル入出力としても使用可能です。https://www.arduino.cc/en/Tutorial/RowColumnScanning
・但し、そのピンの対比は従来のArduino と Arduino Leonardo & Micro では異なっています。
・\Arduino\hardware\arduino\avr\variants\leonardo\pins_arduino.h
と \Arduino\hardware\arduino\avr\variants\micro\pins_arduino.h
に書かれていました。(A0 - A5 は、D18 - D23)

// ATMEL ATMEGA32U4 / ARDUINO LEONARDO
//
// D0                PD2                    RXD1/INT2
// D1                PD3                    TXD1/INT3
// D2                PD1        SDA         SDA/INT1
// D3#               PD0        PWM8/SCL    OC0B/SCL/INT0
// D4        A6      PD4                    ADC8
// D5#               PC6        ???         OC3A/#OC4A
// D6#       A7      PD7        FastPWM     #OC4D/ADC10
// D7                PE6                    INT6/AIN0
//
// D8        A8      PB4                    ADC11/PCINT4
// D9#       A9      PB5        PWM16       OC1A/#OC4B/ADC12/PCINT5
// D10#      A10     PB6        PWM16       OC1B/0c4B/ADC13/PCINT6
// D11#              PB7        PWM8/16     0C0A/OC1C/#RTS/PCINT7
// D12       A11     PD6                    T1/#OC4D/ADC9
// D13#              PC7        PWM10       CLK0/OC4A
//
// A0        D18     PF7                    ADC7
// A1        D19     PF6                    ADC6
// A2        D20     PF5                    ADC5
// A3        D21     PF4                    ADC4
// A4        D22     PF1                    ADC1
// A5        D23     PF0                    ADC0
//

・D12ピンにスイッチ付けてあります、プルアップ入力でグランド落とし。これは Arduino Microからのキーボード情報の出力のON/OFFに使用。一度プログラムミスで、でたらめなキー出力が止まらなくなって酷い目に遭ったので、その対策です。
・キーマトリクスの出力側ですが、最初は負論理の 74HC138 を使用して組んでみたのですが、動かしてみたら、マトリクスの同一ラインのキー同時押し(例:左シフトキーと CapsLock キー)が認識出来ないことが分かって、正論理の 74HC238 に交換しました。
・今回はアドレスデコーダを使用していますが、シフトレジスタの 74HC16474HC595 でも構いません、というかシフトレジスタのほうがいいかも。
・74HC238を使っているのは、ただの個人的な気分です。出力を決め打ち出来るのが気に入ってます。
・キースイッチからArduinoへの入力は抵抗を介してグランドに落としてプルダウンしてください。しないと不定になります(なりました。ので追加しました。負論理の74HC138を使っていたときは入力をプルアップで使用していたので気がつきませんでした)

・今回使用したタクトスイッチ。かなり昔にジャンクで購入したもの。77個、77key

Pict00119

・回路とプログラムの検証用に試験的に組んだ、自作の小型キーボード。キートップはまだ未製作。押しづらいですが動作確認は出来ます。

Image00011

Pict00014

・ブレッドボードに Arduino Micro と 74HC238 x2 を配置。

Pict000111

・キーボードは、キーピッチ 10mm x 10mm、横幅約14cm
・2mmピッチのユニバーサル基板とタクトスイッチで手作り、かなりいい加減で行き当たりばったりです。

Pict00115

・裏のマトリクス配線はぐちゃぐちゃ、ですが、誤配線は無し。

Pict00118

 

・Arduino Micro 用スケッチ(ソースプログラム)
ここから先のソースコードは、Arduino IDE 1.7.x や1.6.x 初期の、
#include "Keyboard.h"
#include "Mouse.h"

の記載が必要ないArduino IDE のバージョンでのみ動作します。
Arduino IDE 1.6.x の途中から及び1.8.xでは USB HID に関係する部分が大きく変更されているために、ここからのスケッチは使用不可能です。

// set pin numbers for keyboard active switch, and LED:
const int switchPin = 12;            // switch to turn on and off keyboard control
const int ledPin = 13;               // keyboard active LED

// keyboard active switch:
boolean keyboardIsActive = false;    // whether or not to control the keyboard
int lastSwitchState = HIGH;          // previous switch state

// key matrix out pin numbers:
// Please refer to \Arduino\hardware\arduino\avr\variants\leonardo\pins_arduino.h
// and \Arduino\hardware\arduino\avr\variants\micro\pins_arduino.h for the digital-out port of ArduinoMicro.
const int keyMatrixOutPin[4] = {
  20, 21, 22, 23
};

// key matrix in pin numbers:
const int keyMatrixInPin[8] = {
  2, 3, 4, 5, 6, 7, 8, 9
};

const int keyScanColMax = 14;
const int keyScanRowMax = 8;

const int keyFnCol = 0;
const int keyFnRow = 0;


boolean keyPlessMap[keyScanColMax][keyScanRowMax];

byte pressKey[6];
byte keyID = 0x00;
int pressKeyCount;

byte keyRept[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
byte previousKeyRept[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

int i, j;

const byte keyMatrixIdMap[keyScanColMax][keyScanRowMax] =
{
 { 0x00, 0x00, 0x00, 0x50, 0x52, 0x51, 0x4F, 0x00 },   // Fn, 00, 00, LeftArrow, UpArrow, DownArrow, RightArrow, 00

 { 0x00, 0x42, 0x00, 0x28, 0x00, 0x00, 0x2A, 0x00 },   // 00, F9, 00, ENTER, 00, 00, BACKSPACE, 00

 { 0x43, 0x00, 0xE6, 0x00, 0xE2, 0x00, 0x00, 0x00 },   // F10, 00, RightAlt, 00, LeftAlt, 00, 00, 00

 { 0x3E, 0xE0, 0x00, 0xE4, 0x00, 0x00, 0x00, 0x00 },   // F5, LeftControl, 00, RightControl, 00, 00, 00, 00

 { 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0xE1, 0x00 },   // 00, 00, 00, 00, 00, RightShift, LeftShift, 00

 { 0x00, 0x4C, 0x00, 0x00, 0x2C, 0x00, 0xE3, 0x00 },   // 00, DEL, 00, 00, SPACE, 00, LeftGUI, 00

 { 0x1E, 0x35, 0x00, 0x1D, 0x29, 0x04, 0x2B, 0x14 },   // 1, `, 00, Z, ESC, A, Tab, Q

 { 0x1F, 0x3A, 0x00, 0x1B, 0x00, 0x16, 0x39, 0x1A },   // 2, F1, 00, X, 00, S, CapsLock, W

 { 0x20, 0x3B, 0x00, 0x06, 0x3D, 0x07, 0x3C, 0x08 },   // 3, F2, 00, C, F4, D, F3, E

 { 0x21, 0x22, 0x05, 0x19, 0x0A, 0x09, 0x17, 0x15 },   // 4, 5, B, V, G, F, T, R

 { 0x24, 0x23, 0x11, 0x10, 0x0B, 0x0D, 0x1C, 0x18 },   // 7, 6, N, M, H, J, Y, U

 { 0x25, 0x2E, 0x00, 0x36, 0x3F, 0x0E, 0x30, 0x0C },   // 8, =, 00, ,, F6, K, ], I

 { 0x26, 0x41, 0xE7, 0x37, 0x00, 0x0F, 0x40, 0x12 },   // 9, F8, RightGUI, ., 00, L, F7, O

 { 0x27, 0x2D, 0x38, 0x31, 0x34, 0x33, 0x2F, 0x13 }    // 0, -, /, bslash, ', ;, [, P
};

/*
const byte keyMatrixNumLkMap[keyScanColMax][keyScanRowMax] =
{
 { 0x00, 0x00, 0x00, 0x50, 0x52, 0x51, 0x4F, 0x00 },   // Fn, 00, 00, LeftArrow, UpArrow, DownArrow, RightArrow, 00

 { 0x00, 0x42, 0x00, 0x28, 0x00, 0x00, 0x2A, 0x00 },   // 00, F9, 00, ENTER, 00, 00, BACKSPACE, 00

 { 0x43, 0x00, 0xE6, 0x00, 0xE2, 0x00, 0x00, 0x00 },   // F10, 00, RightAlt, 00, LeftAlt, 00, 00, 00

 { 0x3E, 0xE0, 0x00, 0xE4, 0x00, 0x00, 0x00, 0x00 },   // F5, LeftControl, 00, RightControl, 00, 00, 00, 00

 { 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0xE1, 0x00 },   // 00, 00, 00, 00, 00, RightShift, LeftShift, 00

 { 0x00, 0x4C, 0x00, 0x00, 0x2C, 0x00, 0xE3, 0x00 },   // 00, DEL, 00, 00, SPACE, 00, LeftGUI, 00

 { 0x1E, 0x35, 0x00, 0x1D, 0x29, 0x04, 0x2B, 0x14 },   // 1, `, 00, Z, ESC, A, Tab, Q

 { 0x1F, 0x3A, 0x00, 0x1B, 0x00, 0x16, 0x39, 0x1A },   // 2, F1, 00, X, 00, S, CapsLock, W

 { 0x20, 0x3B, 0x00, 0x06, 0x3D, 0x07, 0x3C, 0x08 },   // 3, F2, 00, C, F4, D, F3, E

 { 0x21, 0x22, 0x05, 0x19, 0x0A, 0x09, 0x17, 0x15 },   // 4, 5, B, V, G, F, T, R

 { 0x24, 0x23, 0x11, 0x10, 0x0B, 0x0D, 0x1C, 0x18 },   // 7, 6, N, M, H, J, Y, U

 { 0x25, 0x2E, 0x00, 0x36, 0x3F, 0x0E, 0x30, 0x0C },   // 8, =, 00, ,, F6, K, ], I

 { 0x26, 0x41, 0xE7, 0x37, 0x00, 0x0F, 0x40, 0x12 },   // 9, F8, RightGUI, ., 00, L, F7, O

 { 0x27, 0x2D, 0x38, 0x31, 0x34, 0x33, 0x2F, 0x13 }    // 0, -, /, bslash, ', ;, [, P
};
*/

const byte keyMatrixFnMap[keyScanColMax][keyScanRowMax] =
{
 { 0x00, 0x00, 0x00, 0x4A, 0x4B, 0x4E, 0x4D, 0x00 },   // Fn, 00, 00, Home, PgUp, PgDn, End, 00

 { 0x00, 0x48, 0x00, 0x28, 0x00, 0x00, 0x2A, 0x00 },   // 00, Pause, 00, ENTER, 00, 00, BACKSPACE, 00

 { 0x53, 0x00, 0xE6, 0x00, 0xE2, 0x00, 0x00, 0x00 },   // NumLk, 00, RightAlt, 00, LeftAlt, 00, 00, 00

 { 0x3E, 0xE0, 0x00, 0xE4, 0x00, 0x00, 0x00, 0x00 },   // F5, LeftControl, 00, RightControl, 00, 00, 00, 00

 { 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0xE1, 0x00 },   // 00, 00, 00, 00, 00, RightShift, LeftShift, 00

 { 0x00, 0x49, 0x00, 0x00, 0x2C, 0x00, 0xE3, 0x00 },   // 00, INS, 00, 00, SPACE, 00, LeftGUI, 00

 { 0x1E, 0x35, 0x00, 0x1D, 0x29, 0x04, 0x2B, 0x14 },   // 1, `, 00, Z, ESC, A, Tab, Q

 { 0x1F, 0x44, 0x00, 0x1B, 0x00, 0x16, 0x39, 0x1A },   // 2, F11, 00, X, 00, S, CapsLock, W

 { 0x20, 0x45, 0x00, 0x06, 0x3D, 0x07, 0x3C, 0x08 },   // 3, F12, 00, C, F4, D, F3, E

 { 0x21, 0x22, 0x05, 0x19, 0x0A, 0x09, 0x17, 0x15 },   // 4, 5, B, V, G, F, T, R

 { 0x24, 0x23, 0x11, 0x10, 0x0B, 0x0D, 0x1C, 0x18 },   // 7, 6, N, M, H, J, Y, U

 { 0x25, 0x2E, 0x00, 0x36, 0x3F, 0x0E, 0x30, 0x0C },   // 8, =, 00, ,, F6, K, ], I

 { 0x26, 0x47, 0xE7, 0x37, 0x00, 0x0F, 0x46, 0x12 },   // 9, ScrLk, RightGUI, ., 00, L, PrtSc, O

 { 0x27, 0x2D, 0x38, 0x31, 0x34, 0x33, 0x2F, 0x13 }    // 0, -, /, bslash, ', ;, [, P
};




void setup() {

  // initialize I/O pins:
  
  // initialize the keyboard active switch pins:
  pinMode(switchPin, INPUT_PULLUP);      // the keyboard active switch pin
  pinMode(ledPin, OUTPUT);               // the LED pin
  
  // initialize the key output pins:
  for (int thisPin = 0; thisPin < 4; thisPin++) {
    pinMode(keyMatrixOutPin[thisPin], OUTPUT);
  }
  // initialize the key input pins:
  for (int thisPin = 0; thisPin < 8; thisPin++) {
    pinMode(keyMatrixInPin[thisPin], INPUT);
  }
  
  Serial.begin(9600);
  
  Keyboard.begin();
  
}

void loop() {
  
  // read the activate switch:
  int switchState = digitalRead(switchPin);
  // if it's changed and it's high, toggle the keyboard state:
  if (switchState != lastSwitchState) {
    if (switchState == LOW) {
      keyboardIsActive = !keyboardIsActive;
      // turn on LED to indicate keyboard state:
      digitalWrite(ledPin, keyboardIsActive);
    }
  }
  // save switch state for next comparison:
  lastSwitchState = switchState;


  if (keyboardIsActive) {



    // key matrix scan:
    for (int keyScanCol = 0; keyScanCol < keyScanColMax; keyScanCol++) {
      
      // set 74HC238:
      for (int decoderBit = 0;  decoderBit < 4; decoderBit++) {
        digitalWrite(keyMatrixOutPin[decoderBit], bitRead(keyScanCol, decoderBit));
      }
      
      for (int keyScanRow = 0; keyScanRow < keyScanRowMax; keyScanRow++) {
        if (digitalRead(keyMatrixInPin[keyScanRow]) == HIGH ) {
          keyPlessMap[keyScanCol][keyScanRow] = 1;
        } else {
          keyPlessMap[keyScanCol][keyScanRow] = 0;
        }
      }
    }
    
    // ghost key check:
    
    /* Not been able to yet: */
    
    
    // set press key ID:
    
    keyRept[0] = 0x00;
    for (i = 0; i < 6; i++) {
      pressKey[i] = 0x00;
    }
    pressKeyCount = 0;
    
    
    for (int keyScanCol = 0; keyScanCol < keyScanColMax; keyScanCol++) {
      for (int keyScanRow = 0; keyScanRow < keyScanRowMax; keyScanRow++) {
        if (keyPlessMap[keyFnCol][keyFnRow] == 1) {    // pless Fn_Key:
          if (keyPlessMap[keyScanCol][keyScanRow] == 1) {
            keyID = keyMatrixFnMap[keyScanCol][keyScanRow];
          }
        } else {
          if (keyPlessMap[keyScanCol][keyScanRow] == 1) {
            keyID = keyMatrixIdMap[keyScanCol][keyScanRow];

            Serial.print(keyScanCol);
            Serial.print("\t");
            Serial.print(keyScanRow);
            Serial.print("\t");
            Serial.print(keyMatrixIdMap[keyScanCol][keyScanRow]);
            Serial.print("\n");
            Serial.print("keyID=");
            Serial.print(keyID, HEX);
            Serial.print("\n");

          }
        }
        
        // NumLk_Key mode:
        /* Not been able to yet: */

        // modifier key:
        if ((keyID >= 0xE0) && (keyID <= 0xE7)) {
          keyRept[0] |= ( 1 << (keyID - 0xE0));
          keyID = 0x00;
        }
        
        // Add keyID to the key report only if it's not already present
        // and if there is an empty slot.
        if ((keyID != 0x00) && (pressKeyCount <= 6 )) {
          pressKey[pressKeyCount] = keyID;
          pressKeyCount++;
          keyID = 0x00;
        }
      }
    }
    
    
    Serial.print("pressKey[]=");
    Serial.print("\t");
    for (int i = 0; i < 6; i++) {
      Serial.print(pressKey[i], HEX);
    }
    Serial.print("\n");
    
    
    for (i = 0; i < 6; i++) {
      if ((keyRept[i+2] != pressKey[0]) && (keyRept[i+2] != pressKey[1]) &&
          (keyRept[i+2] != pressKey[2]) && (keyRept[i+2] != pressKey[3]) &&
          (keyRept[i+2] != pressKey[4]) && (keyRept[i+2] != pressKey[5])) {
        keyRept[i+2] = 0x00;
      }
    }
    
/*
    Serial.print("reset keyRept"); 
    Serial.print("\n");
 
    Serial.print("pressKey[]=");
    Serial.print("\t");
    for (int i = 0; i < 6; i++) {
      Serial.print(pressKey[i], HEX);
    }
    Serial.print("\t");
 
    Serial.print("keyRept[]=");
    Serial.print("\t");
    for (int i = 0; i < 8; i++) {
      Serial.print(keyRept[i], HEX);
    }
    Serial.print("\n");
*/

    for (i = 0; i < 6; i++) {
      if ((pressKey[i] == keyRept[0+2]) || (pressKey[i] == keyRept[1+2]) || 
          (pressKey[i] == keyRept[2+2]) || (pressKey[i] == keyRept[3+2]) ||
          (pressKey[i] == keyRept[4+2]) || (pressKey[i] == keyRept[5+2])) {
        pressKey[i] = 0x00;
      }
    }
  
/*
    Serial.print("reset pressKey"); 
    Serial.print("\n");
 
    Serial.print("pressKey[]=");
    Serial.print("\t");
    for (int i = 0; i < 6; i++) {
      Serial.print(pressKey[i], HEX);
    }
    Serial.print("\t");
 
    Serial.print("keyRept[]=");
    Serial.print("\t");
    for (int i = 0; i < 8; i++) {
      Serial.print(keyRept[i], HEX);
    }
    Serial.print("\n");
*/

    for (i = 0; i < 6; i++){
      for (j = 0; j < 6; j++) {
        if ((keyRept[j+2] == 0x00) && (pressKey[i] != 0x00)) {
          keyRept[j+2] = pressKey[i];
          i++;
        }
      }
    }
    
    
    Serial.print("send keyRept[]=");
    Serial.print("\t");
      for (int i = 0; i < 8; i++) {
        Serial.print(keyRept[i], HEX);
      }
    Serial.print("\n");


    if ((keyRept[0] != previousKeyRept[0]) || (keyRept[1] != previousKeyRept[1]) ||
        (keyRept[2] != previousKeyRept[2]) || (keyRept[3] != previousKeyRept[3]) ||
        (keyRept[4] != previousKeyRept[4]) || (keyRept[5] != previousKeyRept[5]) ||
        (keyRept[6] != previousKeyRept[6]) || (keyRept[7] != previousKeyRept[7])) {
      
      HID_SendReport(2,keyRept,sizeof(keyRept));
      
      Serial.print("-------------------------------------");
      Serial.print("\n");
      
      Serial.print("sending keyRept[]=");
      Serial.print("\t");
      for (int i = 0; i < 8; i++) {
        Serial.print(keyRept[i], HEX);
      }
      Serial.print("\n");
      
      
      for (int i = 0; i < 8; i++) {
        previousKeyRept[i] = keyRept[i];
      }
      
    }
    
    
    Serial.print("---------------------------------------------");
    Serial.print("\n");
    
  }
  
}

一応動きますが、作りかけです。バグがある可能性は大いにあります。
2016/02/12:修正が必要な箇所がありました。若干書き直しました。

// set pin numbers for keyboard active switch, and LED:
const int switchPin = 12;            // switch to turn on and off keyboard control
const int ledPin = 13;               // keyboard active LED

// keyboard active switch:
boolean keyboardIsActive = false;    // whether or not to control the keyboard
int lastSwitchState = HIGH;          // previous switch state

// key matrix out pin numbers:
// Please refer to \Arduino\hardware\arduino\avr\variants\leonardo\pins_arduino.h
// and \Arduino\hardware\arduino\avr\variants\micro\pins_arduino.h for the digital-out port of ArduinoMicro.
const int keyMatrixOutPin[4] = {
  20, 21, 22, 23
};

// key matrix in pin numbers:
const int keyMatrixInPin[8] = {
  2, 3, 4, 5, 6, 7, 8, 9
};

const int keyScanColMax = 14;
const int keyScanRowMax = 8;

const int keyFnCol = 0;
const int keyFnRow = 0;


boolean keyPlessMap[keyScanColMax][keyScanRowMax];

byte pressKey[6];
byte keyID = 0x00;
int pressKeyCount;

byte keyRept[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
byte previousKeyRept[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

int i, j;

const byte keyMatrixIdMap[keyScanColMax][keyScanRowMax] =
{
 { 0x00, 0x00, 0x00, 0x50, 0x52, 0x51, 0x4F, 0x00 },   // Fn, 00, 00, LeftArrow, UpArrow, DownArrow, RightArrow, 00

 { 0x00, 0x42, 0x00, 0x28, 0x00, 0x00, 0x2A, 0x00 },   // 00, F9, 00, ENTER, 00, 00, BACKSPACE, 00

 { 0x43, 0x00, 0xE6, 0x00, 0xE2, 0x00, 0x00, 0x00 },   // F10, 00, RightAlt, 00, LeftAlt, 00, 00, 00

 { 0x3E, 0xE0, 0x00, 0xE4, 0x00, 0x00, 0x00, 0x00 },   // F5, LeftControl, 00, RightControl, 00, 00, 00, 00

 { 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0xE1, 0x00 },   // 00, 00, 00, 00, 00, RightShift, LeftShift, 00

 { 0x00, 0x4C, 0x00, 0x00, 0x2C, 0x00, 0xE3, 0x00 },   // 00, DEL, 00, 00, SPACE, 00, LeftGUI, 00

 { 0x1E, 0x35, 0x00, 0x1D, 0x29, 0x04, 0x2B, 0x14 },   // 1, `, 00, Z, ESC, A, Tab, Q

 { 0x1F, 0x3A, 0x00, 0x1B, 0x00, 0x16, 0x39, 0x1A },   // 2, F1, 00, X, 00, S, CapsLock, W

 { 0x20, 0x3B, 0x00, 0x06, 0x3D, 0x07, 0x3C, 0x08 },   // 3, F2, 00, C, F4, D, F3, E

 { 0x21, 0x22, 0x05, 0x19, 0x0A, 0x09, 0x17, 0x15 },   // 4, 5, B, V, G, F, T, R

 { 0x24, 0x23, 0x11, 0x10, 0x0B, 0x0D, 0x1C, 0x18 },   // 7, 6, N, M, H, J, Y, U

 { 0x25, 0x2E, 0x00, 0x36, 0x3F, 0x0E, 0x30, 0x0C },   // 8, =, 00, ,, F6, K, ], I

 { 0x26, 0x41, 0xE7, 0x37, 0x00, 0x0F, 0x40, 0x12 },   // 9, F8, RightGUI, ., 00, L, F7, O

 { 0x27, 0x2D, 0x38, 0x31, 0x34, 0x33, 0x2F, 0x13 }    // 0, -, /, bslash, ', ;, [, P
};

/*
const byte keyMatrixNumLkMap[keyScanColMax][keyScanRowMax] =
{
 { 0x00, 0x00, 0x00, 0x50, 0x52, 0x51, 0x4F, 0x00 },   // Fn, 00, 00, LeftArrow, UpArrow, DownArrow, RightArrow, 00

 { 0x00, 0x42, 0x00, 0x28, 0x00, 0x00, 0x2A, 0x00 },   // 00, F9, 00, ENTER, 00, 00, BACKSPACE, 00

 { 0x43, 0x00, 0xE6, 0x00, 0xE2, 0x00, 0x00, 0x00 },   // F10, 00, RightAlt, 00, LeftAlt, 00, 00, 00

 { 0x3E, 0xE0, 0x00, 0xE4, 0x00, 0x00, 0x00, 0x00 },   // F5, LeftControl, 00, RightControl, 00, 00, 00, 00

 { 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0xE1, 0x00 },   // 00, 00, 00, 00, 00, RightShift, LeftShift, 00

 { 0x00, 0x4C, 0x00, 0x00, 0x2C, 0x00, 0xE3, 0x00 },   // 00, DEL, 00, 00, SPACE, 00, LeftGUI, 00

 { 0x1E, 0x35, 0x00, 0x1D, 0x29, 0x04, 0x2B, 0x14 },   // 1, `, 00, Z, ESC, A, Tab, Q

 { 0x1F, 0x3A, 0x00, 0x1B, 0x00, 0x16, 0x39, 0x1A },   // 2, F1, 00, X, 00, S, CapsLock, W

 { 0x20, 0x3B, 0x00, 0x06, 0x3D, 0x07, 0x3C, 0x08 },   // 3, F2, 00, C, F4, D, F3, E

 { 0x21, 0x22, 0x05, 0x19, 0x0A, 0x09, 0x17, 0x15 },   // 4, 5, B, V, G, F, T, R

 { 0x24, 0x23, 0x11, 0x10, 0x0B, 0x0D, 0x1C, 0x18 },   // 7, 6, N, M, H, J, Y, U

 { 0x25, 0x2E, 0x00, 0x36, 0x3F, 0x0E, 0x30, 0x0C },   // 8, =, 00, ,, F6, K, ], I

 { 0x26, 0x41, 0xE7, 0x37, 0x00, 0x0F, 0x40, 0x12 },   // 9, F8, RightGUI, ., 00, L, F7, O

 { 0x27, 0x2D, 0x38, 0x31, 0x34, 0x33, 0x2F, 0x13 }    // 0, -, /, bslash, ', ;, [, P
};
*/

const byte keyMatrixFnMap[keyScanColMax][keyScanRowMax] =
{
 { 0x00, 0x00, 0x00, 0x4A, 0x4B, 0x4E, 0x4D, 0x00 },   // Fn, 00, 00, Home, PgUp, PgDn, End, 00

 { 0x00, 0x48, 0x00, 0x28, 0x00, 0x00, 0x2A, 0x00 },   // 00, Pause, 00, ENTER, 00, 00, BACKSPACE, 00

 { 0x53, 0x00, 0xE6, 0x00, 0xE2, 0x00, 0x00, 0x00 },   // NumLk, 00, RightAlt, 00, LeftAlt, 00, 00, 00

 { 0x3E, 0xE0, 0x00, 0xE4, 0x00, 0x00, 0x00, 0x00 },   // F5, LeftControl, 00, RightControl, 00, 00, 00, 00

 { 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0xE1, 0x00 },   // 00, 00, 00, 00, 00, RightShift, LeftShift, 00

 { 0x00, 0x49, 0x00, 0x00, 0x2C, 0x00, 0xE3, 0x00 },   // 00, INS, 00, 00, SPACE, 00, LeftGUI, 00

 { 0x1E, 0x35, 0x00, 0x1D, 0x29, 0x04, 0x2B, 0x14 },   // 1, `, 00, Z, ESC, A, Tab, Q

 { 0x1F, 0x44, 0x00, 0x1B, 0x00, 0x16, 0x39, 0x1A },   // 2, F11, 00, X, 00, S, CapsLock, W

 { 0x20, 0x45, 0x00, 0x06, 0x3D, 0x07, 0x3C, 0x08 },   // 3, F12, 00, C, F4, D, F3, E

 { 0x21, 0x22, 0x05, 0x19, 0x0A, 0x09, 0x17, 0x15 },   // 4, 5, B, V, G, F, T, R

 { 0x24, 0x23, 0x11, 0x10, 0x0B, 0x0D, 0x1C, 0x18 },   // 7, 6, N, M, H, J, Y, U

 { 0x25, 0x2E, 0x00, 0x36, 0x3F, 0x0E, 0x30, 0x0C },   // 8, =, 00, ,, F6, K, ], I

 { 0x26, 0x47, 0xE7, 0x37, 0x00, 0x0F, 0x46, 0x12 },   // 9, ScrLk, RightGUI, ., 00, L, PrtSc, O

 { 0x27, 0x2D, 0x38, 0x31, 0x34, 0x33, 0x2F, 0x13 }    // 0, -, /, bslash, ', ;, [, P
};




void setup() {

  // initialize I/O pins:
  
  // initialize the keyboard active switch pins:
  pinMode(switchPin, INPUT_PULLUP);      // the keyboard active switch pin
  pinMode(ledPin, OUTPUT);               // the LED pin
  
  // initialize the key output pins:
  for (int thisPin = 0; thisPin < 4; thisPin++) {
    pinMode(keyMatrixOutPin[thisPin], OUTPUT);
  }
  // initialize the key input pins:
  for (int thisPin = 0; thisPin < 8; thisPin++) {
    pinMode(keyMatrixInPin[thisPin], INPUT);
  }
  
  Serial.begin(9600);
  
  Keyboard.begin();
  
}

void loop() {
  
  // read the activate switch:
  int switchState = digitalRead(switchPin);
  // if it's changed and it's high, toggle the keyboard state:
  if (switchState != lastSwitchState) {
    if (switchState == LOW) {
      keyboardIsActive = !keyboardIsActive;
      // turn on LED to indicate keyboard state:
      digitalWrite(ledPin, keyboardIsActive);
    }
  }
  // save switch state for next comparison:
  lastSwitchState = switchState;


  if (keyboardIsActive) {



    // key matrix scan:
    for (int keyScanCol = 0; keyScanCol < keyScanColMax; keyScanCol++) {
      
      // set 74HC238:
      for (int decoderBit = 0;  decoderBit < 4; decoderBit++) {
        digitalWrite(keyMatrixOutPin[decoderBit], bitRead(keyScanCol, decoderBit));
      }
      
      for (int keyScanRow = 0; keyScanRow < keyScanRowMax; keyScanRow++) {
        if (digitalRead(keyMatrixInPin[keyScanRow]) == HIGH ) {
          keyPlessMap[keyScanCol][keyScanRow] = 1;
        } else {
          keyPlessMap[keyScanCol][keyScanRow] = 0;
        }
      }
    }
    
    // ghost key check:
    
    /* Not been able to yet: */
    
    
    // reset keyRept[] and pressKey[] and pressKeyCount:
    keyRept[0] = 0x00;
    for (i = 0; i < 6; i++) {
      pressKey[i] = 0x00;
      keyRept[i+2] = 0x00;
    }
    pressKeyCount = 0;
    
    
    // set press key ID:
    for (int keyScanCol = 0; keyScanCol < keyScanColMax; keyScanCol++) {
      for (int keyScanRow = 0; keyScanRow < keyScanRowMax; keyScanRow++) {
        
        keyID = 0x00;
        if (keyPlessMap[keyFnCol][keyFnRow] == 1) {    // pless Fn_Key:
          if (keyPlessMap[keyScanCol][keyScanRow] == 1) {
            keyID = keyMatrixFnMap[keyScanCol][keyScanRow];

            Serial.print(keyScanCol);
            Serial.print("\t");
            Serial.print(keyScanRow);
            Serial.print("\t");
            Serial.print(keyMatrixFnMap[keyScanCol][keyScanRow]);
            Serial.print("\n");
            Serial.print("keyID=");
            Serial.print(keyID, HEX);
            Serial.print("\n");

          }
        } else {
          if (keyPlessMap[keyScanCol][keyScanRow] == 1) {
            keyID = keyMatrixIdMap[keyScanCol][keyScanRow];

            Serial.print(keyScanCol);
            Serial.print("\t");
            Serial.print(keyScanRow);
            Serial.print("\t");
            Serial.print(keyMatrixIdMap[keyScanCol][keyScanRow]);
            Serial.print("\n");
            Serial.print("keyID=");
            Serial.print(keyID, HEX);
            Serial.print("\n");

          }
        }
        
        // NumLk_Key mode:
        /* Not been able to yet: */
        
        // if plessed key is a modifier key:
        if ((keyID >= 0xE0) && (keyID <= 0xE7)) {
          keyRept[0] |= ( 1 << (keyID - 0xE0));
        } else if ((keyID != 0x00) && (pressKeyCount <= 6 )) {
          // Add keyID :
          pressKey[pressKeyCount] = keyID;
          pressKeyCount++;
        }
      }
    }
    
    
    Serial.print("pressKey[]=");
    Serial.print("\t");
    for (int i = 0; i < 6; i++) {
      Serial.print(pressKey[i], HEX);
    }
    Serial.print("\n");
    Serial.print("pressKeyCount=");
    Serial.print("\t");
    Serial.print(pressKeyCount);
    Serial.print("\n");
    





    for (i = 0; i < pressKeyCount; i++) {
      keyRept[i+2] = pressKey[i];
    }


    if ((keyRept[0] != previousKeyRept[0]) || (keyRept[1] != previousKeyRept[1]) ||
        (keyRept[2] != previousKeyRept[2]) || (keyRept[3] != previousKeyRept[3]) ||
        (keyRept[4] != previousKeyRept[4]) || (keyRept[5] != previousKeyRept[5]) ||
        (keyRept[6] != previousKeyRept[6]) || (keyRept[7] != previousKeyRept[7])) {
      
      HID_SendReport(2,keyRept,sizeof(keyRept));
      
      Serial.print("-------------------------------------");
      Serial.print("\n");
      
      Serial.print("sending keyRept[]=");
      Serial.print("\t");
      for (int i = 0; i < 8; i++) {
        Serial.print(keyRept[i], HEX);
      }
      Serial.print("\n");
      
      
      for (int i = 0; i < 8; i++) {
        previousKeyRept[i] = keyRept[i];
      }
      
    }
    
    
    Serial.print("---------------------------------------------");
    Serial.print("\n");
    
  }
  
}


・NumLock はまだ実装していません。
・ゴーストキーの判定はまだ組み込んでいません。
・\Arduino\hardware\arduino\avr\cores\arduino の中にある「HID.cpp」と「USBAPI.h」を参考、流用、改変して組み込んであります。
・標準で用意されている関数だけを使っています(但し一部イレギュラーな使い方をしています)
・本来なら7個目のキーが押された時にエラーを返さないといけないのですが、切り捨てて無視する事で省いてしまっています。
・デバッグ用に所々にシリアル出力を入れてあります。シリアルモニタで確認出来ます。必要が無くなったら外してください。

追記:2016/02/18
ArduinoのI/Oポートを節約したくて、キーマトリクスの出力側に、シフトレジスタの 74HC164 を使って組んでみました。
こっちのほうがより簡素で良いですね。

Image02

スケッチも変更。

// set pin numbers for keyboard active switch, and LED:
const int switchPin = 12;            // switch to turn on and off keyboard control
const int ledPin = 13;               // keyboard active LED

// keyboard active switch:
boolean keyboardIsActive = false;    // whether or not to control the keyboard
int lastSwitchState = HIGH;          // previous switch state

// key matrix out pin numbers:
// Please refer to \Arduino\hardware\arduino\avr\variants\leonardo\pins_arduino.h
// and \Arduino\hardware\arduino\avr\variants\micro\pins_arduino.h for the digital-out port of ArduinoMicro.

/*
const int keyMatrixOutPin[4] = {
  20, 21, 22, 23
};
*/

// out pin numbers to key matrix shift register :
const int shiftRegClkPin = 23;
const int shiftRegDataPin = 22;

// shift register output data:
const word shiftRegOutData = (B00000000 * 256) + B00000001;


// key matrix in pin numbers:
const int keyMatrixInPin[8] = {
  2, 3, 4, 5, 6, 7, 8, 9
};

const int keyScanColMax = 14;
const int keyScanRowMax = 8;

const int keyFnCol = 0;
const int keyFnRow = 0;


boolean keyPlessMap[keyScanColMax][keyScanRowMax];

byte pressKey[6];
byte keyID = 0x00;
int pressKeyCount;

byte keyRept[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
byte previousKeyRept[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

int i, j;

const byte keyMatrixIdMap[keyScanColMax][keyScanRowMax] =
{
 { 0x00, 0x00, 0x00, 0x50, 0x52, 0x51, 0x4F, 0x00 },   // Fn, 00, 00, LeftArrow, UpArrow, DownArrow, RightArrow, 00

 { 0x00, 0x42, 0x00, 0x28, 0x00, 0x00, 0x2A, 0x00 },   // 00, F9, 00, ENTER, 00, 00, BACKSPACE, 00

 { 0x43, 0x00, 0xE6, 0x00, 0xE2, 0x00, 0x00, 0x00 },   // F10, 00, RightAlt, 00, LeftAlt, 00, 00, 00

 { 0x3E, 0xE0, 0x00, 0xE4, 0x00, 0x00, 0x00, 0x00 },   // F5, LeftControl, 00, RightControl, 00, 00, 00, 00

 { 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0xE1, 0x00 },   // 00, 00, 00, 00, 00, RightShift, LeftShift, 00

 { 0x00, 0x4C, 0x00, 0x00, 0x2C, 0x00, 0xE3, 0x00 },   // 00, DEL, 00, 00, SPACE, 00, LeftGUI, 00

 { 0x1E, 0x35, 0x00, 0x1D, 0x29, 0x04, 0x2B, 0x14 },   // 1, `, 00, Z, ESC, A, Tab, Q

 { 0x1F, 0x3A, 0x00, 0x1B, 0x00, 0x16, 0x39, 0x1A },   // 2, F1, 00, X, 00, S, CapsLock, W

 { 0x20, 0x3B, 0x00, 0x06, 0x3D, 0x07, 0x3C, 0x08 },   // 3, F2, 00, C, F4, D, F3, E

 { 0x21, 0x22, 0x05, 0x19, 0x0A, 0x09, 0x17, 0x15 },   // 4, 5, B, V, G, F, T, R

 { 0x24, 0x23, 0x11, 0x10, 0x0B, 0x0D, 0x1C, 0x18 },   // 7, 6, N, M, H, J, Y, U

 { 0x25, 0x2E, 0x00, 0x36, 0x3F, 0x0E, 0x30, 0x0C },   // 8, =, 00, ,, F6, K, ], I

 { 0x26, 0x41, 0xE7, 0x37, 0x00, 0x0F, 0x40, 0x12 },   // 9, F8, RightGUI, ., 00, L, F7, O

 { 0x27, 0x2D, 0x38, 0x31, 0x34, 0x33, 0x2F, 0x13 }    // 0, -, /, bslash, ', ;, [, P
};

/*
const byte keyMatrixNumLkMap[keyScanColMax][keyScanRowMax] =
{
 { 0x00, 0x00, 0x00, 0x50, 0x52, 0x51, 0x4F, 0x00 },   // Fn, 00, 00, LeftArrow, UpArrow, DownArrow, RightArrow, 00

 { 0x00, 0x42, 0x00, 0x28, 0x00, 0x00, 0x2A, 0x00 },   // 00, F9, 00, ENTER, 00, 00, BACKSPACE, 00

 { 0x43, 0x00, 0xE6, 0x00, 0xE2, 0x00, 0x00, 0x00 },   // F10, 00, RightAlt, 00, LeftAlt, 00, 00, 00

 { 0x3E, 0xE0, 0x00, 0xE4, 0x00, 0x00, 0x00, 0x00 },   // F5, LeftControl, 00, RightControl, 00, 00, 00, 00

 { 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0xE1, 0x00 },   // 00, 00, 00, 00, 00, RightShift, LeftShift, 00

 { 0x00, 0x4C, 0x00, 0x00, 0x2C, 0x00, 0xE3, 0x00 },   // 00, DEL, 00, 00, SPACE, 00, LeftGUI, 00

 { 0x1E, 0x35, 0x00, 0x1D, 0x29, 0x04, 0x2B, 0x14 },   // 1, `, 00, Z, ESC, A, Tab, Q

 { 0x1F, 0x3A, 0x00, 0x1B, 0x00, 0x16, 0x39, 0x1A },   // 2, F1, 00, X, 00, S, CapsLock, W

 { 0x20, 0x3B, 0x00, 0x06, 0x3D, 0x07, 0x3C, 0x08 },   // 3, F2, 00, C, F4, D, F3, E

 { 0x21, 0x22, 0x05, 0x19, 0x0A, 0x09, 0x17, 0x15 },   // 4, 5, B, V, G, F, T, R

 { 0x24, 0x23, 0x11, 0x10, 0x0B, 0x0D, 0x1C, 0x18 },   // 7, 6, N, M, H, J, Y, U

 { 0x25, 0x2E, 0x00, 0x36, 0x3F, 0x0E, 0x30, 0x0C },   // 8, =, 00, ,, F6, K, ], I

 { 0x26, 0x41, 0xE7, 0x37, 0x00, 0x0F, 0x40, 0x12 },   // 9, F8, RightGUI, ., 00, L, F7, O

 { 0x27, 0x2D, 0x38, 0x31, 0x34, 0x33, 0x2F, 0x13 }    // 0, -, /, bslash, ', ;, [, P
};
*/

const byte keyMatrixFnMap[keyScanColMax][keyScanRowMax] =
{
 { 0x00, 0x00, 0x00, 0x4A, 0x4B, 0x4E, 0x4D, 0x00 },   // Fn, 00, 00, Home, PgUp, PgDn, End, 00

 { 0x00, 0x48, 0x00, 0x28, 0x00, 0x00, 0x2A, 0x00 },   // 00, Pause, 00, ENTER, 00, 00, BACKSPACE, 00

 { 0x53, 0x00, 0xE6, 0x00, 0xE2, 0x00, 0x00, 0x00 },   // NumLk, 00, RightAlt, 00, LeftAlt, 00, 00, 00

 { 0x3E, 0xE0, 0x00, 0xE4, 0x00, 0x00, 0x00, 0x00 },   // F5, LeftControl, 00, RightControl, 00, 00, 00, 00

 { 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0xE1, 0x00 },   // 00, 00, 00, 00, 00, RightShift, LeftShift, 00

 { 0x00, 0x49, 0x00, 0x00, 0x2C, 0x00, 0xE3, 0x00 },   // 00, INS, 00, 00, SPACE, 00, LeftGUI, 00

 { 0x1E, 0x35, 0x00, 0x1D, 0x29, 0x04, 0x2B, 0x14 },   // 1, `, 00, Z, ESC, A, Tab, Q

 { 0x1F, 0x44, 0x00, 0x1B, 0x00, 0x16, 0x39, 0x1A },   // 2, F11, 00, X, 00, S, CapsLock, W

 { 0x20, 0x45, 0x00, 0x06, 0x3D, 0x07, 0x3C, 0x08 },   // 3, F12, 00, C, F4, D, F3, E

 { 0x21, 0x22, 0x05, 0x19, 0x0A, 0x09, 0x17, 0x15 },   // 4, 5, B, V, G, F, T, R

 { 0x24, 0x23, 0x11, 0x10, 0x0B, 0x0D, 0x1C, 0x18 },   // 7, 6, N, M, H, J, Y, U

 { 0x25, 0x2E, 0x00, 0x36, 0x3F, 0x0E, 0x30, 0x0C },   // 8, =, 00, ,, F6, K, ], I

 { 0x26, 0x47, 0xE7, 0x37, 0x00, 0x0F, 0x46, 0x12 },   // 9, ScrLk, RightGUI, ., 00, L, PrtSc, O

 { 0x27, 0x2D, 0x38, 0x31, 0x34, 0x33, 0x2F, 0x13 }    // 0, -, /, bslash, ', ;, [, P
};




void setup() {

  // initialize I/O pins:
  
  // initialize the keyboard active switch pins:
  pinMode(switchPin, INPUT_PULLUP);      // the keyboard active switch pin
  pinMode(ledPin, OUTPUT);               // the LED pin
  
  // initialize the key output pins:
/*
  for (int thisPin = 0; thisPin < 4; thisPin++) {
    pinMode(keyMatrixOutPin[thisPin], OUTPUT);
  }
*/
  pinMode(shiftRegClkPin, OUTPUT);
  pinMode(shiftRegDataPin, OUTPUT);
  digitalWrite(shiftRegDataPin, LOW);
  for (int i = 0; i < keyScanColMax; i++) {
    digitalWrite(shiftRegClkPin, HIGH);
    digitalWrite(shiftRegClkPin, LOW);
  }
  
  // initialize the key input pins:
  for (int thisPin = 0; thisPin < 8; thisPin++) {
    pinMode(keyMatrixInPin[thisPin], INPUT);
  }
  
  Serial.begin(9600);
  
  Keyboard.begin();
  
}

void loop() {
  
  // read the activate switch:
  int switchState = digitalRead(switchPin);
  // if it's changed and it's high, toggle the keyboard state:
  if (switchState != lastSwitchState) {
    if (switchState == LOW) {
      keyboardIsActive = !keyboardIsActive;
      // turn on LED to indicate keyboard state:
      digitalWrite(ledPin, keyboardIsActive);
    }
  }
  // save switch state for next comparison:
  lastSwitchState = switchState;


  if (keyboardIsActive) {



    // key matrix scan:
    for (int keyScanCol = 0; keyScanCol < keyScanColMax; keyScanCol++) {
      
/*
      // set 74HC238:
      for (int decoderBit = 0;  decoderBit < 4; decoderBit++) {
        digitalWrite(keyMatrixOutPin[decoderBit], bitRead(keyScanCol, decoderBit));
      }
*/
      // set 74HC164:
      digitalWrite(shiftRegDataPin, bitRead(shiftRegOutData, keyScanCol));
      digitalWrite(shiftRegClkPin, HIGH);
      digitalWrite(shiftRegClkPin, LOW);
      
      
      for (int keyScanRow = 0; keyScanRow < keyScanRowMax; keyScanRow++) {
        if (digitalRead(keyMatrixInPin[keyScanRow]) == HIGH ) {
          keyPlessMap[keyScanCol][keyScanRow] = 1;
        } else {
          keyPlessMap[keyScanCol][keyScanRow] = 0;
        }
      }
    }
    
    // ghost key check:
    
    /* Not been able to yet: */
    
    
    // reset keyRept[] and pressKey[] and pressKeyCount:
    keyRept[0] = 0x00;
    for (i = 0; i < 6; i++) {
      pressKey[i] = 0x00;
      keyRept[i+2] = 0x00;
    }
    pressKeyCount = 0;
    
    
    // set press key ID:
    for (int keyScanCol = 0; keyScanCol < keyScanColMax; keyScanCol++) {
      for (int keyScanRow = 0; keyScanRow < keyScanRowMax; keyScanRow++) {
        
        keyID = 0x00;
        if (keyPlessMap[keyFnCol][keyFnRow] == 1) {    // pless Fn_Key:
          if (keyPlessMap[keyScanCol][keyScanRow] == 1) {
            keyID = keyMatrixFnMap[keyScanCol][keyScanRow];

            Serial.print(keyScanCol);
            Serial.print("\t");
            Serial.print(keyScanRow);
            Serial.print("\t");
            Serial.print(keyMatrixFnMap[keyScanCol][keyScanRow]);
            Serial.print("\n");
            Serial.print("keyID=");
            Serial.print(keyID, HEX);
            Serial.print("\n");

          }
        } else {
          if (keyPlessMap[keyScanCol][keyScanRow] == 1) {
            keyID = keyMatrixIdMap[keyScanCol][keyScanRow];

            Serial.print(keyScanCol);
            Serial.print("\t");
            Serial.print(keyScanRow);
            Serial.print("\t");
            Serial.print(keyMatrixIdMap[keyScanCol][keyScanRow]);
            Serial.print("\n");
            Serial.print("keyID=");
            Serial.print(keyID, HEX);
            Serial.print("\n");

          }
        }
        
        // NumLk_Key mode:
        /* Not been able to yet: */
        
        // if plessed key is a modifier key:
        if ((keyID >= 0xE0) && (keyID <= 0xE7)) {
          keyRept[0] |= ( 1 << (keyID - 0xE0));
        } else if ((keyID != 0x00) && (pressKeyCount <= 6 )) {
          // Add keyID :
          pressKey[pressKeyCount] = keyID;
          pressKeyCount++;
        }
      }
    }
    
    
    Serial.print("pressKey[]=");
    Serial.print("\t");
    for (int i = 0; i < 6; i++) {
      Serial.print(pressKey[i], HEX);
    }
    Serial.print("\n");
    Serial.print("pressKeyCount=");
    Serial.print("\t");
    Serial.print(pressKeyCount);
    Serial.print("\n");
    





    for (i = 0; i < pressKeyCount; i++) {
      keyRept[i+2] = pressKey[i];
    }


    if ((keyRept[0] != previousKeyRept[0]) || (keyRept[1] != previousKeyRept[1]) ||
        (keyRept[2] != previousKeyRept[2]) || (keyRept[3] != previousKeyRept[3]) ||
        (keyRept[4] != previousKeyRept[4]) || (keyRept[5] != previousKeyRept[5]) ||
        (keyRept[6] != previousKeyRept[6]) || (keyRept[7] != previousKeyRept[7])) {
      
      HID_SendReport(2,keyRept,sizeof(keyRept));
      
      Serial.print("-------------------------------------");
      Serial.print("\n");
      
      Serial.print("sending keyRept[]=");
      Serial.print("\t");
      for (int i = 0; i < 8; i++) {
        Serial.print(keyRept[i], HEX);
      }
      Serial.print("\n");
      
      
      for (int i = 0; i < 8; i++) {
        previousKeyRept[i] = keyRept[i];
      }
      
    }
    
    
    Serial.print("---------------------------------------------");
    Serial.print("\n");
    
  }
  
}

動いてるっぽいです。間違えていたら教えてください。

追記:2016/02/23
・汎用ロジックICの電源の配線を図に入れてなかったので追加して図を差し替え。
・シフトレジスタの初期値を間違えてました(16ビット必要なのに8ビットだった)

・体調の不調のために期間は長くなってしまっていますが、ほぼ素人の私ですけれど実作業に掛かっている時間自体はかなり短くて、それでも一応実用に耐えるであろう回路とプログラムが作成出来たのは、すべて Arduino のおかげです。
Arduino は、とっても楽です。Arduino を開発、維持してくれている皆さんと、そのArduinoを使って色々遊んでネット上に情報を上げてくださっている皆さんに、本当に感謝です。ありがとうございます。

ぶっちゃけ単純な事しかやっていません。
ロジック回路の基本的なことやC言語の基礎的なことしか知らない私でも作れるのですから、USBキーボードの自作、ぜひ挑戦してみてください。情報集めと、配線作業や回路とプログラムのデバッグにかける根性(あきらめの悪さ)があれば、けっこう誰にでも、USBの入力デバイスが簡単に作れてしまいます。
上記スケッチ(プログラムソース)のキーマトリクスとキーコード(Usage ID)を対応させている配列の中身を、「Universal Serial Bus HID Usage Tables」の「Keyboard/Keypad Page (0x07)」にある「Usage ID」を元にして書き換えるだけで、好きなように自由にキー配列の設定が出来ます。
(但し無改造のArduino IDE の関数では上限が101(HID.cpp で LOGICAL_MAXIMUM (101) )になってしまっているため、日本語キーボード特有の「ろ(0x87)」「ひらがな・カタカナ(0x88)」「¥(0x89)」「変換(0x8A)」「無変換(0x8B)」は、このままでは使用出来ません)

自分好みのデバイスが作れるのって楽しいですよ。
特にキーボードはキーレイアウトを自分好みに自由自在に指定出来るのがとても良いです。

 

追記:前に作ったスライドパッドマウスと合わせてみた。

以前、試作した、スライドパッドマウス に、今回のキーボード部分を乗せてみました。
そして、ソフトウェアを統合してみました。

Pict00120

配線は次の図のようになっています。
Image03

キーマトリクスを変更

_02

スケッチ(プログラムソース)はキーボードとマウスを統合してみましたが、ただ混ぜただけなので、後々整理が必要だと思います。自分で読んでもなんか気持ち良くないです。

// set pin numbers for keyboard active switch, and LED:
const int switchPin = 12;            // switch to turn on and off keyboard control
const int ledPin = 13;               // keyboard active LED

// keyboard active switch:
boolean keyboardIsActive = false;    // whether or not to control the keyboard
int lastSwitchState = HIGH;          // previous switch state


// keyboard constant value:

// key matrix out pin numbers:
// Please refer to \Arduino\hardware\arduino\avr\variants\leonardo\pins_arduino.h
// and \Arduino\hardware\arduino\avr\variants\micro\pins_arduino.h for the digital-out port of ArduinoMicro.


// out pin numbers to key matrix shift register :
const int shiftRegClkPin = 23;
const int shiftRegDataPin = 22;

// shift register output data:
const word shiftRegOutData = (B00000000 * 256) + B00000001;


// key matrix in pin numbers:
const int keyMatrixInPin[8] = {
  2, 3, 4, 5, 6, 7, 8, 9
};

const int keyScanColMax = 14;
const int keyScanRowMax = 8;

const int keyFnCol = 0;
const int keyFnRow = 0;


boolean keyPlessMap[keyScanColMax][keyScanRowMax];

byte pressKey[6];
byte keyID = 0x00;
int pressKeyCount;

byte keyRept[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
//byte previousKeyRept[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

int i, j;

const byte keyMatrixIdMap[keyScanColMax][keyScanRowMax] =
{
 { 0x00, 0x00, 0x00, 0x50, 0x52, 0x51, 0x4F, 0x00 },   // Fn, 00, 00, LeftArrow, UpArrow, DownArrow, RightArrow, 00

 { 0x00, 0x42, 0x00, 0x28, 0x00, 0x00, 0x2A, 0x00 },   // 00, F9, 00, ENTER, 00, 00, BACKSPACE, 00

 { 0x43, 0x00, 0xE6, 0x00, 0xE2, 0x00, 0x00, 0x00 },   // F10, 00, RightAlt, 00, LeftAlt, 00, 00, 00

 { 0x3E, 0xE0, 0x00, 0xE4, 0x00, 0x00, 0x00, 0x00 },   // F5, LeftControl, 00, RightControl, 00, 00, 00, 00

 { 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0xE1, 0x00 },   // 00, 00, 00, 00, 00, RightShift, LeftShift, 00

 { 0x00, 0x4C, 0x00, 0x00, 0x2C, 0x00, 0xE3, 0x00 },   // 00, DEL, 00, 00, SPACE, 00, LeftGUI, 00

 { 0x1E, 0x35, 0x00, 0x1D, 0x29, 0x04, 0x2B, 0x14 },   // 1, `, 00, Z, ESC, A, Tab, Q

 { 0x1F, 0x3A, 0x00, 0x1B, 0x00, 0x16, 0x39, 0x1A },   // 2, F1, 00, X, 00, S, CapsLock, W

 { 0x20, 0x3B, 0x00, 0x06, 0x3D, 0x07, 0x3C, 0x08 },   // 3, F2, 00, C, F4, D, F3, E

 { 0x21, 0x22, 0x05, 0x19, 0x0A, 0x09, 0x17, 0x15 },   // 4, 5, B, V, G, F, T, R

 { 0x24, 0x23, 0x11, 0x10, 0x0B, 0x0D, 0x1C, 0x18 },   // 7, 6, N, M, H, J, Y, U

 { 0x25, 0x2E, 0x00, 0x36, 0x3F, 0x0E, 0x30, 0x0C },   // 8, =, 00, ,, F6, K, ], I

 { 0x26, 0x41, 0x65, 0x37, 0x00, 0x0F, 0x40, 0x12 },   // 9, F8, AppKey, ., 00, L, F7, O

 { 0x27, 0x2D, 0x38, 0x31, 0x34, 0x33, 0x2F, 0x13 }    // 0, -, /, bslash, ', ;, [, P
};

/*
const byte keyMatrixNumLkMap[keyScanColMax][keyScanRowMax] =
{
 { 0x00, 0x00, 0x00, 0x50, 0x52, 0x51, 0x4F, 0x00 },   // Fn, 00, 00, LeftArrow, UpArrow, DownArrow, RightArrow, 00

 { 0x00, 0x42, 0x00, 0x28, 0x00, 0x00, 0x2A, 0x00 },   // 00, F9, 00, ENTER, 00, 00, BACKSPACE, 00

 { 0x43, 0x00, 0xE6, 0x00, 0xE2, 0x00, 0x00, 0x00 },   // F10, 00, RightAlt, 00, LeftAlt, 00, 00, 00

 { 0x3E, 0xE0, 0x00, 0xE4, 0x00, 0x00, 0x00, 0x00 },   // F5, LeftControl, 00, RightControl, 00, 00, 00, 00

 { 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0xE1, 0x00 },   // 00, 00, 00, 00, 00, RightShift, LeftShift, 00

 { 0x00, 0x4C, 0x00, 0x00, 0x2C, 0x00, 0xE3, 0x00 },   // 00, DEL, 00, 00, SPACE, 00, LeftGUI, 00

 { 0x1E, 0x35, 0x00, 0x1D, 0x29, 0x04, 0x2B, 0x14 },   // 1, `, 00, Z, ESC, A, Tab, Q

 { 0x1F, 0x3A, 0x00, 0x1B, 0x00, 0x16, 0x39, 0x1A },   // 2, F1, 00, X, 00, S, CapsLock, W

 { 0x20, 0x3B, 0x00, 0x06, 0x3D, 0x07, 0x3C, 0x08 },   // 3, F2, 00, C, F4, D, F3, E

 { 0x21, 0x22, 0x05, 0x19, 0x0A, 0x09, 0x17, 0x15 },   // 4, 5, B, V, G, F, T, R

 { 0x24, 0x23, 0x11, 0x10, 0x0B, 0x0D, 0x1C, 0x18 },   // 7, 6, N, M, H, J, Y, U

 { 0x25, 0x2E, 0x00, 0x36, 0x3F, 0x0E, 0x30, 0x0C },   // 8, =, 00, ,, F6, K, ], I

 { 0x26, 0x41, 0x65, 0x37, 0x00, 0x0F, 0x40, 0x12 },   // 9, F8, AppKey, ., 00, L, F7, O

 { 0x27, 0x2D, 0x38, 0x31, 0x34, 0x33, 0x2F, 0x13 }    // 0, -, /, bslash, ', ;, [, P
};
*/

const byte keyMatrixFnMap[keyScanColMax][keyScanRowMax] =
{
 { 0x00, 0x00, 0x00, 0x4A, 0x4B, 0x4E, 0x4D, 0x00 },   // Fn, 00, 00, Home, PgUp, PgDn, End, 00

 { 0x00, 0x48, 0x00, 0x28, 0x00, 0x00, 0x2A, 0x00 },   // 00, Pause, 00, ENTER, 00, 00, BACKSPACE, 00

 { 0x53, 0x00, 0xE6, 0x00, 0xE2, 0x00, 0x00, 0x00 },   // NumLk, 00, RightAlt, 00, LeftAlt, 00, 00, 00

 { 0x3E, 0xE0, 0x00, 0xE4, 0x00, 0x00, 0x00, 0x00 },   // F5, LeftControl, 00, RightControl, 00, 00, 00, 00

 { 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0xE1, 0x00 },   // 00, 00, 00, 00, 00, RightShift, LeftShift, 00

 { 0x00, 0x49, 0x00, 0x00, 0x2C, 0x00, 0xE3, 0x00 },   // 00, INS, 00, 00, SPACE, 00, LeftGUI, 00

 { 0x1E, 0x35, 0x00, 0x1D, 0x29, 0x04, 0x2B, 0x14 },   // 1, `, 00, Z, ESC, A, Tab, Q

 { 0x1F, 0x44, 0x00, 0x1B, 0x00, 0x16, 0x39, 0x1A },   // 2, F11, 00, X, 00, S, CapsLock, W

 { 0x20, 0x45, 0x00, 0x06, 0x3D, 0x07, 0x3C, 0x08 },   // 3, F12, 00, C, F4, D, F3, E

 { 0x21, 0x22, 0x05, 0x19, 0x0A, 0x09, 0x17, 0x15 },   // 4, 5, B, V, G, F, T, R

 { 0x24, 0x23, 0x11, 0x10, 0x0B, 0x0D, 0x1C, 0x18 },   // 7, 6, N, M, H, J, Y, U

 { 0x25, 0x2E, 0x00, 0x36, 0x3F, 0x0E, 0x30, 0x0C },   // 8, =, 00, ,, F6, K, ], I

 { 0x26, 0x47, 0xE7, 0x37, 0x00, 0x0F, 0x46, 0x12 },   // 9, ScrLk, RightGUI, ., 00, L, PrtSc, O

 { 0x27, 0x2D, 0x38, 0x31, 0x34, 0x33, 0x2F, 0x13 }    // 0, -, /, bslash, ', ;, [, P
};


// mouse constant value:

const int mouseLeftCol = 0;
const int mouseLeftRow = 2;
const int mouseRightCol = 0;
const int mouseRightRow = 1;
//const int mouseMiddleCol = ;
//const int mouseMiddleRow = ;
const int mouseScrollCol = 0;
const int mouseScrollRow = 7;


const int xAxis = A0;         // joystick X axis  
const int yAxis = A1;         // joystick Y axis

// send data when mouse wheel is move:
const int scrollWheelStop = 0x00;
const int scrollWheelUp   = 0x01;
const int scrollWheelDown = 0xFF;

// interval time for slow move:
const int pointerMoveInterval = 50;
const int scrollInterval = 500;

// parameters for reading the joystick:

const int xAnalogInMin = 160;
const int xAnalogInMax = 890;
const int yAnalogInMin = 140;
const int yAnalogInMax = 860;
const int threshold = 30;          // resting threshold


int xPointerMoveDirection = 0;
int yPointerMoveDirection = 0;

const int pointerMoveMaxValue = 3;
const int pointerSlowMoveRange = 5;
const int range = (pointerMoveMaxValue + pointerSlowMoveRange) * 2;  // output range of X or Y movement

int xSendAxisData = 0;
int ySendAxisData = 0;
int sendWheelData = 0;

// timer:
// unsigned long currentMillis;
unsigned long CurrentMillis;
unsigned long xPreviousMillis = 0;        // will store last time mouse move data was updated
unsigned long yPreviousMillis = 0;        // will store last time mouse move data was updated
unsigned long wheelPreviousMillis = 0;

// mouse mode change switch:
boolean scrollMode = false;

// response delay:
const int responseDelay = 3;


void setup() {

  // initialize I/O pins:
  
  // initialize the keyboard active switch pins:
  pinMode(switchPin, INPUT_PULLUP);      // the keyboard active switch pin
  pinMode(ledPin, OUTPUT);               // the LED pin
  
  // initialize the key output pins:
/*
  for (int thisPin = 0; thisPin < 4; thisPin++) {
    pinMode(keyMatrixOutPin[thisPin], OUTPUT);
  }
*/
  pinMode(shiftRegClkPin, OUTPUT);
  pinMode(shiftRegDataPin, OUTPUT);
  digitalWrite(shiftRegDataPin, LOW);
  for (int i = 0; i < keyScanColMax; i++) {
    digitalWrite(shiftRegClkPin, HIGH);
    digitalWrite(shiftRegClkPin, LOW);
  }
  
  // initialize the key input pins:
  for (int thisPin = 0; thisPin < 8; thisPin++) {
    pinMode(keyMatrixInPin[thisPin], INPUT);
  }
  
//Serial.begin(9600);
  
  Keyboard.begin();
  Mouse.begin();
  
}

void loop() {
  
  // read the activate switch:
  int switchState = digitalRead(switchPin);
  // if it's changed and it's high, toggle the keyboard state:
  if (switchState != lastSwitchState) {
    if (switchState == LOW) {
      keyboardIsActive = !keyboardIsActive;
      // turn on LED to indicate keyboard state:
      digitalWrite(ledPin, keyboardIsActive);
    }
  }
  // save switch state for next comparison:
  lastSwitchState = switchState;


  if (keyboardIsActive) {


    // key matrix scan:
    for (int keyScanCol = 0; keyScanCol < keyScanColMax; keyScanCol++) {
      
/*
      // set 74HC238:
      for (int decoderBit = 0;  decoderBit < 4; decoderBit++) {
        digitalWrite(keyMatrixOutPin[decoderBit], bitRead(keyScanCol, decoderBit));
      }
*/
      // set 74HC164:
      digitalWrite(shiftRegDataPin, bitRead(shiftRegOutData, keyScanCol));
      digitalWrite(shiftRegClkPin, HIGH);
      digitalWrite(shiftRegClkPin, LOW);
      
      
      for (int keyScanRow = 0; keyScanRow < keyScanRowMax; keyScanRow++) {
        if (digitalRead(keyMatrixInPin[keyScanRow]) == HIGH ) {
          keyPlessMap[keyScanCol][keyScanRow] = 1;
        } else {
          keyPlessMap[keyScanCol][keyScanRow] = 0;
        }
      }
    }
    
    // ghost key check:
    
    /* Not been able to yet: */
    
    
    // reset keyRept[] and pressKey[] and pressKeyCount:
    keyRept[0] = 0x00;
    for (i = 0; i < 6; i++) {
      pressKey[i] = 0x00;
      keyRept[i+2] = 0x00;
    }
    pressKeyCount = 0;
    
    
    // set press key ID:
    for (int keyScanCol = 0; keyScanCol < keyScanColMax; keyScanCol++) {
      for (int keyScanRow = 0; keyScanRow < keyScanRowMax; keyScanRow++) {
        
        keyID = 0x00;
        if (keyPlessMap[keyFnCol][keyFnRow] == 1) {    // pless Fn_Key:
          if (keyPlessMap[keyScanCol][keyScanRow] == 1) {
            keyID = keyMatrixFnMap[keyScanCol][keyScanRow];

/*
            Serial.print(keyScanCol);
            Serial.print("\t");
            Serial.print(keyScanRow);
            Serial.print("\t");
            Serial.print(keyMatrixFnMap[keyScanCol][keyScanRow]);
            Serial.print("\n");
            Serial.print("keyID=");
            Serial.print(keyID, HEX);
            Serial.print("\n");
*/

          }
        } else {
          if (keyPlessMap[keyScanCol][keyScanRow] == 1) {
            keyID = keyMatrixIdMap[keyScanCol][keyScanRow];

/*
            Serial.print(keyScanCol);
            Serial.print("\t");
            Serial.print(keyScanRow);
            Serial.print("\t");
            Serial.print(keyMatrixIdMap[keyScanCol][keyScanRow]);
            Serial.print("\n");
            Serial.print("keyID=");
            Serial.print(keyID, HEX);
            Serial.print("\n");
*/

          }
        }
        
        // NumLk_Key mode:
        /* Not been able to yet: */
        
        // if plessed key is a modifier key:
        if ((keyID >= 0xE0) && (keyID <= 0xE7)) {
          keyRept[0] |= ( 1 << (keyID - 0xE0));
        } else if ((keyID != 0x00) && (pressKeyCount <= 6 )) {
          // Add keyID :
          pressKey[pressKeyCount] = keyID;
          pressKeyCount++;
        }
      }
    }
    
    
/*
    Serial.print("pressKey[]=");
    Serial.print("\t");
    for (int i = 0; i < 6; i++) {
      Serial.print(pressKey[i], HEX);
    }
    Serial.print("\n");
    Serial.print("pressKeyCount=");
    Serial.print("\t");
    Serial.print(pressKeyCount);
    Serial.print("\n");
 
*/



    for (i = 0; i < pressKeyCount; i++) {
      keyRept[i+2] = pressKey[i];
    }
    HID_SendReport(2,keyRept,sizeof(keyRept));

/*
    if ((keyRept[0] != previousKeyRept[0]) || (keyRept[1] != previousKeyRept[1]) ||
        (keyRept[2] != previousKeyRept[2]) || (keyRept[3] != previousKeyRept[3]) ||
        (keyRept[4] != previousKeyRept[4]) || (keyRept[5] != previousKeyRept[5]) ||
        (keyRept[6] != previousKeyRept[6]) || (keyRept[7] != previousKeyRept[7])) {
 
      HID_SendReport(2,keyRept,sizeof(keyRept));
 
      Serial.print("-------------------------------------");
      Serial.print("\n");
 
      Serial.print("sending keyRept[]=");
      Serial.print("\t");
      for (int i = 0; i < 8; i++) {
        Serial.print(keyRept[i], HEX);
      }
      Serial.print("\n");
 
 
      for (int i = 0; i < 8; i++) {
        previousKeyRept[i] = keyRept[i];
      }
    }
*/
    
    
/*
    Serial.print("---------------------------------------------");
    Serial.print("\n");
*/
    

    // mouse:

    // read for I/O:
    int xReading = readAxis(xAxis, xAnalogInMin, xAnalogInMax);
    int yReading = readAxis(yAxis, yAnalogInMin, yAnalogInMax);
  
    // read the mouseModeSwitch:
//  int modeSwitchState = digitalRead(mouseScrollModeButton);

    int mouseModeSwitchState = keyPlessMap[mouseScrollCol][mouseScrollRow];
    
    if (mouseModeSwitchState == 1) {
      scrollMode = true;
      } else {
      scrollMode = false;
    }
    
    
    if (scrollMode == false) {
      // pointer mode:
      sendWheelData = scrollWheelStop;
      
      // set xAxis data for send:
      if (xReading == 0) {
        // center:
        xSendAxisData = 0;
      } else if (abs(xReading) <= pointerSlowMoveRange) {
        // slow:
        CurrentMillis = millis();
        xSendAxisData = pointerSlowMove(xReading, xPreviousMillis);
        if(xSendAxisData != 0) {
          xPreviousMillis = CurrentMillis;
        }
      } else {
        xSendAxisData = (xReading / abs(xReading)) * (abs(xReading) - pointerSlowMoveRange);
      }
      // set yAxis data for send:
      if (yReading == 0) {
        // center:
        ySendAxisData = 0;
      } else if (abs(yReading) <= pointerSlowMoveRange) {
        // slow:
        CurrentMillis = millis();
        ySendAxisData = pointerSlowMove(yReading, yPreviousMillis);
        if(ySendAxisData != 0) {
          yPreviousMillis = CurrentMillis;
        }
      } else {
        ySendAxisData = (yReading / abs(yReading)) * (abs(yReading) - pointerSlowMoveRange);
      }
    
    } else {
      // scroll mode:
      xSendAxisData = 0;
      ySendAxisData = 0;
      
      if (yReading == 0) {
        // center:
        sendWheelData = 0;
      } else {
        // scroll up and down:
        CurrentMillis = millis();
        int wheelMoveDirection = yReading / abs(yReading);
        if (CurrentMillis - wheelPreviousMillis < (scrollInterval / abs(yReading)) ) {
          sendWheelData = 0;
        } else {
          if (wheelMoveDirection < 0) {
            // scroll up:
            sendWheelData = scrollWheelUp;
          } else {
            //  scroll down:
            sendWheelData = scrollWheelDown;
          }
        }
        if(sendWheelData != 0) {
          wheelPreviousMillis = CurrentMillis;
        }
      }
    }
  
  

    // set mouse data for sending to USB:


    Mouse.move(xSendAxisData, ySendAxisData, sendWheelData);
    // read the mouse button and click or not click:
    setMouseButton(mouseLeftCol, mouseLeftRow, MOUSE_LEFT);
    setMouseButton(mouseRightCol, mouseRightRow, MOUSE_RIGHT);
//  setMouseButton(mouseMiddleCol, mouseMiddleRow, MOUSE_MIDDLE);
    
    
  }
  
//delay(responseDelay);
}


/*
 read axis data:
 */

int readAxis(int thisAxis, int analogInMin, int analogInMax) {
  // read the analog input:
  int analogInRange = analogInMax - analogInMin;
  int center = (analogInRange / 2) + analogInMin;
  
  int reading = analogRead(thisAxis);
  reading = constrain(reading, analogInMin, analogInMax);
  
  int distance = reading - center;
//int threshold = analogInRange / range;        // resting threshold
  // if the output reading is outside from the rest position threshold,  use it:
  if (abs(distance) < threshold) {
    distance = 0;
  } else {
    // map the reading from the analog input range to the output range:
    distance = map(reading, analogInMin, analogInMax, 0, range) - (range / 2);
  }
  // the Y axis needs to be inverted in order to
  // map the movemment correctly:
  if (thisAxis == yAxis) {
    distance = -distance;
  }
  return distance;  // return the distance for this axis:
}


/*
 cursor move low speed:
 */

int pointerSlowMove(int reading, unsigned long previousMillis) {
  unsigned long currentMillis = millis();
  if (reading == 0) {
    return 0;
  } else {
    int interval = pointerMoveInterval / abs(reading);
    int pointerMoveDirection = reading / abs(reading);
    if (currentMillis - previousMillis < interval) {
      return 0;
    } else {
      return pointerMoveDirection;
    }
  }
}


/*
 read the mouse button and click or not click:
 */

int setMouseButton(int keymapCol, int keymapRow, int mouseButton) {
  // if the mouse button is pressed:
  if (keyPlessMap[keymapCol][keymapRow] == 1) {
    // if the mouse is not pressed, press it:
    if (!Mouse.isPressed(mouseButton)) {
      Mouse.press(mouseButton);
    }
  }
  // else the mouse button is not pressed:
  else {
    // if the mouse is pressed, release it:
    if (Mouse.isPressed(mouseButton)) {
      Mouse.release(mouseButton);
    }
  }
}

・スライドパッドのガワを削って、キーボード基板をただ乗せているだけです。固定していません。なんとなく一体化してみたかっただけなので使い勝手は考慮していません。
・キーボードのデータのUSBへの出力で不必要っぽい処理を外しました。
・右GUIキーをアプリケーションキーに変更しています(代わりに Fn+AppKey に GUI_R を割り当て)

本当は仮のキートップを製作してから公開しようと思っていたのですが、なんか精神的にヤバくて、私はこの世から居なくなってしまいそうなので、途中を公開します。

現在、停滞している作業
・検証用プラ製キートップの手作り
・キーボード、マウスとして、実際に動作している様子を動画で撮影
・やってることの詳細をブログ記事内に追記

今後予定される、予定している、行いたい、出来ればいいな、作業
・ゴーストキー対策の実装
・NumLockの実装
・Arduinoの標準ライブラリの改変
 ・日本語キーボード特有のキーの有効化
 ・キーボードLEDの機能追加
 ・マルチメディアキーの実装
・キーバックライト用のLED点灯まわりの電子回路の検討、実験
・ATmega32U4(TQFP)と74HC164(SOP)などを用いて、キーボード自作専用のArduino下位互換基板の製作(まずは手半田で)
 ・回路図CADの操作習得と、回路図の描画
  ・動作試作回路の製作
 ・回路基板CADの操作習得と、基板のデザイン製作
 ・※実際に試作基板を発注するところまで行けるかは不透明
・Arduinoの標準ライブラリの、さらなる改変
(Arduinoはかなりの電気食い。USBのConfigurationDescriptorでMaxPowerが500mAに指定されていて、なおかつ実際に消費電力が多いためにモバイルに不向き)
(Arduinoには省電力のためのライブラリ等もあるらしいので、ソフト的にもハード的にも電力を出来るだけ抑えたい)
・3Dモデリングソフトの操作習得と、樹脂製キートップのモデリングおよび試作の3Dプリント

とりあえず思いついているのはこのくらい。
以下、完全に未定

 

つづきます。続いたらいいな…

 

追記:2017/02/22
ちょっとだけ続く続いた。

・USBキーボードのキーボードLED(Num Lock, Caps Lock, Scroll Lock,)の状態を収得。
読んだページ
"Leonardo keyboard leds emulation?"
ここにある"usb_key_leds.diff" を元にして、USBAPI.h と HID.cpp を書き換えて、キーボードのLEDの状態を、ホスト(繋いであるPCなど)から読み出す関数を追加。
USBAPI.h と HID.cpp を書き換えて、Arduino の USB HID の機能を変更する方法は、USB HID のライブラリが、分割されていない(自作スケッチに、Keyboard.h Mouse.h の記述をする必要が無い)Arduino IDE の 1.6.x の初期の頃のと 1.7.x でのみ、有効な方法です。(Arduino IDE 1.6.7以降(1.7.x は除く)や、1.8.x には、USB HID を大幅に拡張するライブラリが、あります。 Arduino HID Project 2.4.4

USBAPI.h(機能追加)


/*
  USBAPI.h
  Copyright (c) 2005-2014 Arduino.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#ifndef __USBAPI__
#define __USBAPI__

#include <inttypes.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include <avr/interrupt.h>
#include <util/delay.h>

typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32;

#include "Arduino.h"

#if defined(USBCON)

#include "USBDesc.h"
#include "USBCore.h"

//================================================================================
//================================================================================
// USB

class USBDevice_
{
public:
USBDevice_();
bool configured();

void attach();
void detach(); // Serial port goes down too...
void poll();
};
extern USBDevice_ USBDevice;

//================================================================================
//================================================================================
// Serial over CDC (Serial1 is the physical port)

struct ring_buffer;

#if (RAMEND < 1000)
#define SERIAL_BUFFER_SIZE 16
#else
#define SERIAL_BUFFER_SIZE 64
#endif

class Serial_ : public Stream
{
private:
int peek_buffer;
public:
Serial_() { peek_buffer = -1; };
void begin(unsigned long);
void begin(unsigned long, uint8_t);
void end(void);

virtual int available(void);
virtual int peek(void);
virtual int read(void);
virtual void flush(void);
virtual size_t write(uint8_t);
virtual size_t write(const uint8_t*, size_t);
using Print::write; // pull in write(str) and write(buf, size) from Print
operator bool();

volatile uint8_t _rx_buffer_head;
volatile uint8_t _rx_buffer_tail;
unsigned char _rx_buffer[SERIAL_BUFFER_SIZE];
};
extern Serial_ Serial;

#define HAVE_CDCSERIAL

//================================================================================
//================================================================================
// Mouse

#define MOUSE_LEFT 1
#define MOUSE_RIGHT 2
#define MOUSE_MIDDLE 4
#define MOUSE_ALL (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)

class Mouse_
{
private:
uint8_t _buttons;
void buttons(uint8_t b);
public:
Mouse_(void);
void begin(void);
void end(void);
void click(uint8_t b = MOUSE_LEFT);
void move(signed char x, signed char y, signed char wheel = 0);
void press(uint8_t b = MOUSE_LEFT); // press LEFT by default
void release(uint8_t b = MOUSE_LEFT); // release LEFT by default
bool isPressed(uint8_t b = MOUSE_LEFT); // check LEFT by default
};
extern Mouse_ Mouse;

//================================================================================
//================================================================================
// Keyboard

#define KEY_LEFT_CTRL 0x80 //0xE0 - 0x60(96)
#define KEY_LEFT_SHIFT 0x81 //0xE1 - 0x60(96)
#define KEY_LEFT_ALT 0x82
#define KEY_LEFT_GUI 0x83
#define KEY_RIGHT_CTRL 0x84
#define KEY_RIGHT_SHIFT 0x85
#define KEY_RIGHT_ALT 0x86
#define KEY_RIGHT_GUI 0x87

//UsageID + 0x88(136)
#define KEY_RETURN 0xB0 //0x28 + 0x88(136)
#define KEY_ESC 0xB1 //0x29 + 0x88(136)
#define KEY_BACKSPACE 0xB2 //0x2A + 0x88(136)
#define KEY_TAB 0xB3

#define KEY_CAPS_LOCK 0xC1 //0x39 + 0x88(136)
#define KEY_F1 0xC2 //0x3A + 0x88(136)
#define KEY_F2 0xC3
#define KEY_F3 0xC4
#define KEY_F4 0xC5
#define KEY_F5 0xC6
#define KEY_F6 0xC7
#define KEY_F7 0xC8
#define KEY_F8 0xC9
#define KEY_F9 0xCA
#define KEY_F10 0xCB
#define KEY_F11 0xCC
#define KEY_F12 0xCD

#define KEY_PRINT_SCREEN 0xCE
#define KEY_SCROLL_LOCK 0xCF
#define KEY_PAUSE 0xD0

#define KEY_INSERT 0xD1 //0x49 + 0x88(136)
#define KEY_HOME 0xD2
#define KEY_PAGE_UP 0xD3
#define KEY_DELETE 0xD4
#define KEY_END 0xD5
#define KEY_PAGE_DOWN 0xD6
#define KEY_RIGHT_ARROW 0xD7
#define KEY_LEFT_ARROW 0xD8
#define KEY_DOWN_ARROW 0xD9
#define KEY_UP_ARROW 0xDA
#define KEY_NUM_LOCK 0xDB //0x53 + 0x88(136)


#define LED_NUM_LOCK 0x01
#define LED_CAPS_LOCK 0x02
#define LED_SCROLL_LOCK 0x04

// Low level key report: up to 6 keys and shift, ctrl etc at once
typedef struct
{
uint8_t modifiers;
uint8_t reserved;
uint8_t keys[6];
} KeyReport;

class Keyboard_ : public Print
{
private:
KeyReport _keyReport;
void sendReport(KeyReport* keys);
uint8_t _ledStatus;
public:
Keyboard_(void);
void begin(void);
void end(void);
virtual size_t write(uint8_t k);
virtual size_t press(uint8_t k);
virtual size_t release(uint8_t k);
virtual void releaseAll(void);
virtual void setLedStatus(uint8_t s);
virtual uint8_t getLedStatus(void);
};
extern Keyboard_ Keyboard;

//================================================================================
//================================================================================
// Low level API

typedef struct
{
uint8_t bmRequestType;
uint8_t bRequest;
uint8_t wValueL;
uint8_t wValueH;
uint16_t wIndex;
uint16_t wLength;
} Setup;

//================================================================================
//================================================================================
// HID 'Driver'

int HID_GetInterface(uint8_t* interfaceNum);
int HID_GetDescriptor(int i);
bool HID_Setup(Setup& setup);
void HID_SendReport(uint8_t id, const void* data, int len);

//================================================================================
//================================================================================
// MSC 'Driver'

int MSC_GetInterface(uint8_t* interfaceNum);
int MSC_GetDescriptor(int i);
bool MSC_Setup(Setup& setup);
bool MSC_Data(uint8_t rx,uint8_t tx);

//================================================================================
//================================================================================
// CSC 'Driver'

int CDC_GetInterface(uint8_t* interfaceNum);
int CDC_GetDescriptor(int i);
bool CDC_Setup(Setup& setup);

//================================================================================
//================================================================================

#define TRANSFER_PGM 0x80
#define TRANSFER_RELEASE 0x40
#define TRANSFER_ZERO 0x20

int USB_SendControl(uint8_t flags, const void* d, int len);
int USB_RecvControl(void* d, int len);

uint8_t USB_Available(uint8_t ep);
int USB_Send(uint8_t ep, const void* data, int len); // blocking
int USB_Recv(uint8_t ep, void* data, int len); // non-blocking
int USB_Recv(uint8_t ep); // non-blocking
void USB_Flush(uint8_t ep);

#endif

#endif /* if defined(USBCON) */

HID.cpp(機能追加)

/* Copyright (c) 2011, Peter Barrett  
** 
** Permission to use, copy, modify, and/or distribute this software for 
** any purpose with or without fee is hereby granted, provided that the 
** above copyright notice and this permission notice appear in all copies. 
**
** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 
** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 
** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 
** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES 
** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 
** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 
** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 
** SOFTWARE. 
*/

#include "USBAPI.h"

#if defined(USBCON)
#ifdef HID_ENABLED

//#define RAWHID_ENABLED

// Singletons for mouse and keyboard

Mouse_ Mouse;
Keyboard_ Keyboard;

//================================================================================
//================================================================================

// HID report descriptor

#define LSB(_x) ((_x) & 0xFF)
#define MSB(_x) ((_x) >> 8)

#define RAWHID_USAGE_PAGE 0xFFC0
#define RAWHID_USAGE 0x0C00
#define RAWHID_TX_SIZE 64
#define RAWHID_RX_SIZE 64

extern const u8 _hidReportDescriptor[] PROGMEM;
const u8 _hidReportDescriptor[] = {

// Mouse
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop) // 54
    0x09, 0x02,                    // USAGE (Mouse)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x09, 0x01,                    //   USAGE (Pointer)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x85, 0x01,                    //     REPORT_ID (1)
    0x05, 0x09,                    //     USAGE_PAGE (Button)
    0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
    0x29, 0x03,                    //     USAGE_MAXIMUM (Button 3)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x95, 0x03,                    //     REPORT_COUNT (3)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0x95, 0x01,                    //     REPORT_COUNT (1)
    0x75, 0x05,                    //     REPORT_SIZE (5)
    0x81, 0x03,                    //     INPUT (Cnst,Var,Abs)
    0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
    0x09, 0x30,                    //     USAGE (X)
    0x09, 0x31,                    //     USAGE (Y)
    0x09, 0x38,                    //     USAGE (Wheel)
    0x15, 0x81,                    //     LOGICAL_MINIMUM (-127)
    0x25, 0x7f,                    //     LOGICAL_MAXIMUM (127)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x95, 0x03,                    //     REPORT_COUNT (3)
    0x81, 0x06,                    //     INPUT (Data,Var,Rel)
    0xc0,                          //   END_COLLECTION
    0xc0,                          // END_COLLECTION

  //  Keyboard
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)  // 47
    0x09, 0x06,                    // USAGE (Keyboard)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x85, 0x02,                    //   REPORT_ID (2)
    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
    0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
    0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x08,                    //   REPORT_COUNT (8)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
   
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)
   
    0x05, 0x08,                    //   USAGE_PAGE (LEDs)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x19, 0x01,                    //   USAGE_MINIMUM (1)
    0x29, 0x05,                    //   USAGE_MAXIMUM (5)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x05,                    //   REPORT_COUNT (5)
    0x91, 0x02,                    //   OUTPUT (Data,Var,Abs) // LED report
    0x75, 0x03,                    //   REPORT_SIZE (3)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x91, 0x01,                    //   OUTPUT (Constant) // padding
   
    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
    0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))
    0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0xe7,                    //   LOGICAL_MAXIMUM (231)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x95, 0x06,                    //   REPORT_COUNT (6)
    0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
   
    0xc0,                          // END_COLLECTION

#ifdef RAWHID_ENABLED
// RAW HID
0x06, LSB(RAWHID_USAGE_PAGE), MSB(RAWHID_USAGE_PAGE), // 30
0x0A, LSB(RAWHID_USAGE), MSB(RAWHID_USAGE),

0xA1, 0x01, // Collection 0x01
0x85, 0x03, // REPORT_ID (3)
0x75, 0x08, // report size = 8 bits
0x15, 0x00, // logical minimum = 0
0x26, 0xFF, 0x00, // logical maximum = 255

0x95, 64, // report count TX
0x09, 0x01, // usage
0x81, 0x02, // Input (array)

0x95, 64, // report count RX
0x09, 0x02, // usage
0x91, 0x02, // Output (array)
0xC0 // end collection
#endif
};

extern const HIDDescriptor _hidInterface PROGMEM;
const HIDDescriptor _hidInterface =
{
D_INTERFACE(HID_INTERFACE,1,3,0,0),
D_HIDREPORT(sizeof(_hidReportDescriptor)),
D_ENDPOINT(USB_ENDPOINT_IN (HID_ENDPOINT_INT),USB_ENDPOINT_TYPE_INTERRUPT,0x40,0x01)
};

//================================================================================
//================================================================================
// Driver

u8 _hid_protocol = 1;
u8 _hid_idle = 1;

#define WEAK __attribute__ ((weak))

int WEAK HID_GetInterface(u8* interfaceNum)
{
interfaceNum[0] += 1; // uses 1
return USB_SendControl(TRANSFER_PGM,&_hidInterface,sizeof(_hidInterface));
}

int WEAK HID_GetDescriptor(int /* i */)
{
return USB_SendControl(TRANSFER_PGM,_hidReportDescriptor,sizeof(_hidReportDescriptor));
}

void WEAK HID_SendReport(u8 id, const void* data, int len)
{
USB_Send(HID_TX, &id, 1);
USB_Send(HID_TX | TRANSFER_RELEASE,data,len);
}

bool WEAK HID_Setup(Setup& setup)
{
u8 r = setup.bRequest;
u8 requestType = setup.bmRequestType;
if (REQUEST_DEVICETOHOST_CLASS_INTERFACE == requestType)
{
if (HID_GET_REPORT == r)
{
//HID_GetReport();
return true;
}
if (HID_GET_PROTOCOL == r)
{
//Send8(_hid_protocol); // TODO
return true;
}
}

if (REQUEST_HOSTTODEVICE_CLASS_INTERFACE == requestType)
{
if (HID_SET_PROTOCOL == r)
{
_hid_protocol = setup.wValueL;
return true;
}

if (HID_SET_IDLE == r)
{
_hid_idle = setup.wValueL;
return true;
}

if (HID_SET_REPORT == r)
{
if (setup.wLength == 2)
{
uint8_t data[2];
if (2 == USB_RecvControl(data, 2))
{
Keyboard.setLedStatus(data[1]);
}
}
}
}
return false;
}

//================================================================================
//================================================================================
// Mouse

Mouse_::Mouse_(void) : _buttons(0)
{
}

void Mouse_::begin(void)
{
}

void Mouse_::end(void)
{
}

void Mouse_::click(uint8_t b)
{
_buttons = b;
move(0,0,0);
_buttons = 0;
move(0,0,0);
}

void Mouse_::move(signed char x, signed char y, signed char wheel)
{
u8 m[4];
m[0] = _buttons;
m[1] = x;
m[2] = y;
m[3] = wheel;
HID_SendReport(1,m,4);
}

void Mouse_::buttons(uint8_t b)
{
if (b != _buttons)
{
_buttons = b;
move(0,0,0);
}
}

void Mouse_::press(uint8_t b)
{
buttons(_buttons | b);
}

void Mouse_::release(uint8_t b)
{
buttons(_buttons & ~b);
}

bool Mouse_::isPressed(uint8_t b)
{
if ((b & _buttons) > 0)
return true;
return false;
}

//================================================================================
//================================================================================
// Keyboard

Keyboard_::Keyboard_(void)
{
}

void Keyboard_::begin(void)
{
}

void Keyboard_::end(void)
{
}

void Keyboard_::sendReport(KeyReport* keys)
{
HID_SendReport(2,keys,sizeof(KeyReport));
}

extern
const uint8_t _asciimap[128] PROGMEM;

#define SHIFT 0x80
const uint8_t _asciimap[128] =
{
0x00,          // NUL
0x00,          // SOH
0x00,          // STX
0x00,          // ETX
0x00,          // EOT
0x00,          // ENQ
0x00,          // ACK
0x00,          // BEL
0x2a,          // BS Backspace
0x2b,          // TAB Tab
0x28,          // LF Enter
0x00,          // VT
0x00,          // FF
0x00,          // CR
0x00,          // SO
0x00,          // SI
0x00,          // DEL
0x00,          // DC1
0x00,          // DC2
0x00,          // DC3
0x00,          // DC4
0x00,          // NAK
0x00,          // SYN
0x00,          // ETB
0x00,          // CAN
0x00,          // EM
0x00,          // SUB
0x00,          // ESC
0x00,          // FS
0x00,          // GS
0x00,          // RS
0x00,          // US

0x2c,          //' ' //ASCII 32
0x1e|SHIFT,    // !
0x34|SHIFT,    // "
0x20|SHIFT,    // #
0x21|SHIFT,    // $
0x22|SHIFT,    // %
0x24|SHIFT,    // &
0x34,          // '
0x26|SHIFT,    // (
0x27|SHIFT,    // )
0x25|SHIFT,    // *
0x2e|SHIFT,    // +
0x36,          // ,
0x2d,          // -
0x37,          // .
0x38,          // /
0x27,          // 0
0x1e,          // 1
0x1f,          // 2
0x20,          // 3
0x21,          // 4
0x22,          // 5
0x23,          // 6
0x24,          // 7
0x25,          // 8
0x26,          // 9
0x33|SHIFT,    // :
0x33,          // ;
0x36|SHIFT,    // <
0x2e,          // =
0x37|SHIFT,    // >
0x38|SHIFT,    // ?
0x1f|SHIFT,    // @
0x04|SHIFT,    // A
0x05|SHIFT,    // B
0x06|SHIFT,    // C
0x07|SHIFT,    // D
0x08|SHIFT,    // E
0x09|SHIFT,    // F
0x0a|SHIFT,    // G
0x0b|SHIFT,    // H
0x0c|SHIFT,    // I
0x0d|SHIFT,    // J
0x0e|SHIFT,    // K
0x0f|SHIFT,    // L
0x10|SHIFT,    // M
0x11|SHIFT,    // N
0x12|SHIFT,    // O
0x13|SHIFT,    // P
0x14|SHIFT,    // Q
0x15|SHIFT,    // R
0x16|SHIFT,    // S
0x17|SHIFT,    // T
0x18|SHIFT,    // U
0x19|SHIFT,    // V
0x1a|SHIFT,    // W
0x1b|SHIFT,    // X
0x1c|SHIFT,    // Y
0x1d|SHIFT,    // Z
0x2f,          // [
0x31,          // bslash
0x30,          // ]
0x23|SHIFT,    // ^
0x2d|SHIFT,    // _
0x35,          // `
0x04,          // a
0x05,          // b
0x06,          // c
0x07,          // d
0x08,          // e
0x09,          // f
0x0a,          // g
0x0b,          // h
0x0c,          // i
0x0d,          // j
0x0e,          // k
0x0f,          // l
0x10,          // m
0x11,          // n
0x12,          // o
0x13,          // p
0x14,          // q
0x15,          // r
0x16,          // s
0x17,          // t
0x18,          // u
0x19,          // v
0x1a,          // w
0x1b,          // x
0x1c,          // y
0x1d,          // z
0x2f|SHIFT,    // {
0x31|SHIFT,    // |
0x30|SHIFT,    // }
0x35|SHIFT,    // ~ //ASCII 126
0              // DEL
};

uint8_t USBPutChar(uint8_t c);

// press() adds the specified key (printing, non-printing, or modifier)
// to the persistent key report and sends the report.  Because of the way
// USB HID works, the host acts like the key remains pressed until we
// call release(), releaseAll(), or otherwise clear the report and resend.
size_t Keyboard_::press(uint8_t k)
{
uint8_t i;
if (k >= 136) { // it's a non-printing key (not a modifier)
k = k - 136;
} else if (k >= 128) { // it's a modifier key
_keyReport.modifiers |= (1<<(k-128));
k = 0;
} else { // it's a printing key
k = pgm_read_byte(_asciimap + k);
if (!k) {
setWriteError();
return 0;
}
if (k & 0x80) { // it's a capital letter or other character reached with shift
_keyReport.modifiers |= 0x02; // the left shift modifier
k &= 0x7F;
}
}

// Add k to the key report only if it's not already present
// and if there is an empty slot.
if (_keyReport.keys[0] != k && _keyReport.keys[1] != k &&
_keyReport.keys[2] != k && _keyReport.keys[3] != k &&
_keyReport.keys[4] != k && _keyReport.keys[5] != k) {

for (i=0; i<6; i++) {
if (_keyReport.keys[i] == 0x00) {
_keyReport.keys[i] = k;
break;
}
}
if (i == 6) {
setWriteError();
return 0;
}
}
sendReport(&_keyReport);
return 1;
}

// release() takes the specified key out of the persistent key report and
// sends the report.  This tells the OS the key is no longer pressed and that
// it shouldn't be repeated any more.
size_t Keyboard_::release(uint8_t k)
{
uint8_t i;
if (k >= 136) { // it's a non-printing key (not a modifier)
k = k - 136;
} else if (k >= 128) { // it's a modifier key
_keyReport.modifiers &= ~(1<<(k-128));
k = 0;
} else { // it's a printing key
k = pgm_read_byte(_asciimap + k);
if (!k) {
return 0;
}
if (k & 0x80) { // it's a capital letter or other character reached with shift
_keyReport.modifiers &= ~(0x02); // the left shift modifier
k &= 0x7F;
}
}

// Test the key report to see if k is present.  Clear it if it exists.
// Check all positions in case the key is present more than once (which it shouldn't be)
for (i=0; i<6; i++) {
if (0 != k && _keyReport.keys[i] == k) {
_keyReport.keys[i] = 0x00;
}
}

sendReport(&_keyReport);
return 1;
}

void Keyboard_::releaseAll(void)
{
_keyReport.keys[0] = 0;
_keyReport.keys[1] = 0;
_keyReport.keys[2] = 0;
_keyReport.keys[3] = 0;
_keyReport.keys[4] = 0;
_keyReport.keys[5] = 0;
_keyReport.modifiers = 0;
sendReport(&_keyReport);
}

size_t Keyboard_::write(uint8_t c)
{
uint8_t p = press(c);  // Keydown
release(c);            // Keyup
return (p); // just return the result of press() since release() almost always returns 1
}

void Keyboard_::setLedStatus(uint8_t s)
{
_ledStatus = s;
}

uint8_t Keyboard_::getLedStatus(void)
{
return _ledStatus;
}

#endif

#endif /* if defined(USBCON) */

テスト用に組んだブレッドボード(タクトスイッチとLEDだけ)

Pict0022

動作確認に使ったスケッチ(Arduino IDE 1.7.11 を使用)

// set pin numbers for keyboard active switch, and LED:
const int switchPin = 2;             // switch to turn on and off keyboard control
const int ledPin = 13;               // keyboard active LED

// keyboard active switch:
boolean keyboardIsActive = false;    // whether or not to control the keyboard
int lastSwitchState = HIGH;          // previous switch state

const int keyboardInputPin[4] = {
  3, 4, 5, 6
};

const int keyboardLedOutPin[3] = {
  7, 8, 9
};


void setup() {
  // initialize I/O pins:
  
  pinMode(switchPin, INPUT_PULLUP);      // the keyboard active switch pin
  pinMode(ledPin, OUTPUT);               // the LED pin
  
  // switch:
  // initialize the key input pins:
  for (int thisPin = 0; thisPin < 4; thisPin++) {
    pinMode(keyboardInputPin[thisPin], INPUT_PULLUP);
  }
  
  // LED:
  for (int thisPin = 0; thisPin < 3; thisPin++) {
    pinMode(keyboardLedOutPin[thisPin], OUTPUT);
  }

  Keyboard.begin();
//  Serial.begin(9600);
  
  int keyboardLedStatus = 0;
//  Keyboard.setLedStatus(keyboardLedStatus);
  
}

void loop() {
  
  
  // read the activate switch:
  int switchState = digitalRead(switchPin);
  // if it's changed and it's high, toggle the keyboard state:
  if (switchState != lastSwitchState) {
    if (switchState == LOW) {
      keyboardIsActive = !keyboardIsActive;
      // turn on LED to indicate keyboard state:
      digitalWrite(ledPin, keyboardIsActive);
    }
  }
  // save switch state for next comparison:
  lastSwitchState = switchState;
  
  
  // main
  if (keyboardIsActive) {
    
    if (digitalRead(keyboardInputPin[0]) == LOW) {
      Keyboard.write(KEY_NUM_LOCK);
    }
    if (digitalRead(keyboardInputPin[1]) == LOW) {
      capslock();
    }
    if (digitalRead(keyboardInputPin[2]) == LOW) {
      Keyboard.write(KEY_SCROLL_LOCK);
    }
    // print screen.
    if (digitalRead(keyboardInputPin[3]) == LOW ){
      printScreen();
    }
   
    int keyboardLedStatus = Keyboard.getLedStatus();
 /* if ((keyboardLedStatus && LED_NUM_LOCK) == 1){
    digitalWrite(keyboardLedOutPin[0], HIGH);
  } else {
    digitalWrite(keyboardLedOutPin[0], LOW);
  }
 */    
    digitalWrite(keyboardLedOutPin[0], (keyboardLedStatus & LED_NUM_LOCK));
    digitalWrite(keyboardLedOutPin[1], (keyboardLedStatus & LED_CAPS_LOCK));
    digitalWrite(keyboardLedOutPin[2], (keyboardLedStatus & LED_SCROLL_LOCK));
    
    // LED_NUM_LOCK      0x01
    // LED_CAPS_LOCK     0x02
    // LED_SCROLL_LOCK   0x04

//  Serial.print(keyboardLedStatus);
//  Serial.print("\n");
     
    
  }
  delay(300);
}

// PCが日本語キーボードの配列前提で動いてるので、
// Caps Lock は[Shift]+[Caps Lock]
void capslock(void) {
    Keyboard.press(KEY_LEFT_SHIFT);
    Keyboard.press(KEY_CAPS_LOCK);
    delay(100);
    Keyboard.releaseAll();
}

// Windowsで[Windowsキー]+[PrintScreen]をタイプすると、
// ピクチャのスクリーンショットのフォルダ内に
// スクリーンショットが保存されます。
void printScreen(void) {
    Keyboard.press(KEY_LEFT_GUI);
    Keyboard.press(KEY_PRINT_SCREEN);
    delay(100);
    Keyboard.releaseAll();
}

実際に動いている様子(動画)
 

 

次は何ができるだろうか、、、

 

| | コメント (6) | トラックバック (0)

2015年10月10日 (土)

Arduino Micro を使って、試しに USB スライドパッド マウスを作ってみた(小型USBキーボード自作のための準備色々)

※2017/02/06:更新、追加しました。

※注意!:私、はっきり言って、ほぼ素人です!!!、電子工作をちょろっとと、無印 C言語を少しかじったことがあるだけです。間違い等がある可能性が十分にありますので、ここはあくまで参考程度として、みなさん各自で試行錯誤してくださいませ。ここの間違い指摘は大歓迎です。

小型USBキーボードを自作してみたいと考え始めたわけですが、そのための情報収集や自分の経験値を積み重ねる必要があると思ったので、段階的に色々と作っていこうと思っています。

まずはマイコンを実際に使ってみないと。という目的と、キーボードにポインティングデバイスを付けたいという理由から、USBマウスっぽいものを作ってみる事にしました。

使用するマイコンは、前のブログ記事「USBキーボードは自作できるのか下調べをしてみる」で検討した結果から、
Arduino Micro」に決めました。

ポインティングデバイスは、小型のトラックボールが魅力的だったのですが、部品の入手先が少なくなってるのと、お値段がちょいお高いので今回はやめて、
3DSやPSPなどで使用されている、スライドさせるタイプのアナログジョイスティックを使ってみることにしました。

部品の購入は、今回、初めて秋月電子通商さんの通販を利用させて頂きました。
東京で働いていた頃、秋葉原で、まだLaOX ザ・コンピューター館が健在でその裏の通りでT-ZONEもあった頃に、店舗にはちょくちょく寄らさせて頂いてました。

通販で注文した部品は、

Arduino Micro 本体
アナログジョイスティックのパーツ
・仮組み実験用のブレッドボード
・ブレッドボード用のワイヤー
などなど

ネットで注文して数日で到着。
こんな感じで部品ごとにジップ袋に入れられてました。

Pict002

 

これが Arduino Micro

Pict0008

想像していたより小っさかったです。

こちらはアナログジョイスティックのパーツ

Pict0004

レバーを倒す(傾ける)タイプではなくて、平面的にスライドさせるタイプです。
外形は1.75cm角、厚さ5.5mm(レバー含まず)中に可変抵抗がXY軸でふたつ入っています。
100円

Arduino Micro を早速動かしてみます。
Arduino 入門」などで検索をかけて、まずは基本の「Lチカ」LEDの点滅あたりから初めて、色々と試してみます。

PCに Arduino IDE(統合開発環境、Arduinoに書き込むプログラムを開発するためのソフトウェア)をインストール。(1.6.x はこちら1.7.x はこちらから)
PCと Arduino Micro は、microUSBケーブルで繋ぎます。
microUSBのB端子ケーブルは、Androidスマートフォンなどで使っているもので良いですが、充電専用のケーブルはデータ線が繋がっていなくて使えませんので、データ通信対応のケーブルを用意してください。

プログラムの記述、コンパイル、Arduino Micro 本体へのプログラム書き込みの方法などは、入門サイトを見て回ってくださいな。
Arduino Leonard 、Arduino Micro などのUSB内蔵タイプでは従来のArduinoシリーズとは書き込みの仕様が異なるとのことですので、スイッチサイエンスさんが日本語に翻訳をしてくださった「Arduino Leonardoへのガイド」をお読みください。
あとArduino のプログラム言語のリファレンスの日本語訳が「Arduino 日本語リファレンス」にあります。

「Lチカ」等で使用するLEDですが、Arduinoでは13番ピンにLEDが繋がっていて基板上に実装されていますので、それを使用しました。

スイッチの入力は、内部プルアップ抵抗を有効にして、スイッチだけ繋ぎました。外部抵抗省略。
プルアップの場合はスイッチの片側はグランドに繋ぎます。
LOWアクティブの動作になります(スイッチを押すとHIGHからLOWに変わります)

この辺りを変更している場合は、スケッチ(プログラム)を記述する時に注意です。

ソースコードは Arduino 本家のページにある Leaning > Examples を参考にします(ほぼ丸写し)
例題が色々そろってて有り難いです。

Arduino Micro のピン配置

Arduinomicro_pinout

Arduino Micro と一緒に購入したアナログジョイスティックの入力を試してみます。
基本は可変抵抗の入力なので Arduino 公式の Examples のアナログ入力と JoystickMouseControl を読んでいきます。

Examples の JoystickMouseControl を読みながら、Arduino Micro とアナログジョイスティックのパーツを結線していきます。

ここで注意、この JoystickMouseControl の解説ページですが、上の結線図や回路図と、下のソースコードで食い違いがあったりします。

下のソースコードでは、マウスのクリック用のプッシュスイッチに加えて、Arduino がマウスとして動作するのを ON/OFF するためのプッシュスイッチが追加になっています。
これは Arduino がUSBマウスとして動作している間はPCにマウスが追加で接続されている状態になるために、もし Arduino が意図しないマウスの移動信号を出し続けてしまうと、PCに繋いである普段使いのマウスの動作を邪魔してしまって、マウスでの操作が出来なくなる恐れがあるためです。
まぁ、そうなったら Arduino 側のUSBケーブルを引っこ抜いちゃえば嫌でも止まるんですけれどね。
それではスマートでは無いので Examples のソースコードでは ON/OFF のスイッチが追加になってます。

ジョイスティックのパーツはテスターなどでパーツの中の配線を確認しておきます。

購入したパーツは下図のようになってました。

Analogjoystick

片側3本の端子のうち左右が抵抗の両端で真ん中が可変部分、端子の間隔は2.54mmではなくて2mm間隔です。

抵抗の両端を GND と 5V に繋げて、可変する端子を Arduino のアナログ入力に繋げます。
今回は X軸方向は A0、Y軸方向は A1。

ここでの注意は、アナログ入力の X-Y の向き。
USB HIDの規格では、左上が原点になっていて、右方向にX軸、下方向(手前側方向)にY軸です。
Hid1_11_30p

アナログ出力の上下がこれに合うようにジョイスティックパーツの5VとGNDを配線してください。

ジョイスティックパーツの端子の両端を5VとGNDに。真ん中の出力をアナログ入力の A0 と A1 に繋ぎます。(なぜこのように繋ぐとアナログの可変値が読み取れるのかは「可変抵抗 分圧回路」で検索してみてくださいな)
デジタル入出力の2番にマウスON/OFF用のタクトスイッチ、3番にマウスクリック。
内部プルアップを使うことにするので、片側は GND に落とします。

Pict0012

Arduino Micro (及び Arduino Leonardo 等も含めたUSB内蔵タイプ)は、これ単体で、それまでのUSBのシリアル入出力と、USB HID での入出力を混在させることが出来るようになっています。

なので、USBシリアル出力で生データをPCに送りつつ USB HID の出力も行う、といったことが可能です。
これがけっこう助かります。

手始めに、ジョイスティックから入力される生のデータをシリアルで出力させてみます。
ついでに map 関数を試しに使ってみたり。
Examples のアナログ入力のソースを切り貼り。
ここから先のソースコードには
#include "Keyboard.h"
#include "Mouse.h"

の記載がありません。
Arduino IDE 1.7.x を使用しているためです。
理由は後述します。

 

const int analogInPinX = A0;  // Analog input pin that the potentiometer is attached to
const int analogInPinY = A1;  // Analog input pin that the potentiometer is attached to

int sensorValueX = 0;        // value read from the port
int sensorValueY = 0;        // value read from the port
int outputValueX = 0;        // value output
int outputValueY = 0;        // value output

void setup() {
  // initialize serial communications at 9600 bps:
  Serial.begin(9600);
}

void loop() {
  // read the analog in value:
  sensorValueX = analogRead(analogInPinX);
  sensorValueY = analogRead(analogInPinY);
  // map it to the range of the analog out:
  outputValueX = map(sensorValueX, 0, 1023, 0, 255);
  outputValueY = map(sensorValueY, 0, 1023, 0, 255);
  // change the analog out value:
  //  analogWrite(analogOutPin, outputValue);
  
  // print the results to the serial monitor:
  Serial.print("sensor X = " );
  Serial.print(sensorValueX);
  Serial.print("\t outputX = ");
  Serial.print(outputValueX);
  
  Serial.print("\t sensor Y = " );
  Serial.print(sensorValueY);
  Serial.print("\t outputY = ");
  Serial.print(outputValueY);
  Serial.print("\n");
  
  // wait 2 milliseconds before the next loop
  // for the analog-to-digital converter to settle
  // after the last reading:
  delay(2);
}

Arduino IDE のエディタにソースをコピペして「検証」して「マイコンボードに書き込む」を行えば、書き込み終了後に Arduino にリセットがかかって書き込んだプログラムが動き出します。

シリアルモニタを開くと Arduino から送られてくるデータが表示されます(COMポートを合わせてくださいね)
ジョイスティックをスライドさせると数値の変化が分かります。

動かした時の数値を見てお気づきかと思いますが、スティックに触っていない中立の時の値が、1024 / 2 の 512ぴったりにはなりません(Arduino Micro のアナログ入力は 0 - 1023 の10bit)
ズレます。そして一定の値を取りません。
バネの力で中立位置に戻るので、上に上げてからスティックを離した時とか下に下げたあとスティックを離した時などでも中立時の数値が変わります。
これは部品や構造の個体差なので、ぴったりにはどうしてもなりません。
そこでソフト側で補正をしてやらないといけないのですが、それは後述します。

続いて、Examples の JoystickMouseControl のソースを使って、USBマウスとして動かしてみます。

結線は配線図ではなくソースコードに書かれている通りに、このブログの画像のように繋げます。

ソースコードは Examples の JoystickMouseControl のソースを元にして、今回スイッチをプルアップで使っているのでそこだけ書き換えれば(スイッチ関係の INPUT → INPUT_PULLUP、 HIGH → LOW、LOW → HIGH )、あとはそのままで動きます。

/*
  JoystickMouseControl
 
 Controls the mouse from a joystick on an Arduino Leonardo or Micro.
 Uses a pushbutton to turn on and off mouse control, and
 a second pushbutton to click the left mouse button
 
 Hardware:
 * 2-axis joystick connected to pins A0 and A1
 * pushbuttons connected to pin D2 and D3
 
 The mouse movement is always relative. This sketch reads
 two analog inputs that range from 0 to 1023 (or less on either end)
 and translates them into ranges of -6 to 6.
 The sketch assumes that the joystick resting values are around the
 middle of the range, but that they vary within a threshold.
 
 WARNING:  When you use the Mouse.move() command, the Arduino takes
 over your mouse!  Make sure you have control before you use the command.
 This sketch includes a pushbutton to toggle the mouse control state, so
 you can turn on and off mouse control.
 
 created 15 Sept 2011
 updated 28 Mar 2012
 by Tom Igoe
 
 this code is in the public domain
 
 */

// set pin numbers for switch, joystick axes, and LED:
const int switchPin = 2;      // switch to turn on and off mouse control
const int mouseButton = 3;    // input pin for the mouse pushButton
const int xAxis = A0;         // joystick X axis  
const int yAxis = A1;         // joystick Y axis
const int ledPin = 13;        // Mouse control LED

// parameters for reading the joystick:
int range = 12;               // output range of X or Y movement
int responseDelay = 5;        // response delay of the mouse, in ms
int threshold = range/4;      // resting threshold
int center = range/2;         // resting position value

boolean mouseIsActive = false;    // whether or not to control the mouse
int lastSwitchState = HIGH;       // previous switch state (PULL_UP)

void setup() {
  pinMode(switchPin, INPUT_PULLUP);   // the switch pin
  pinMode(ledPin, OUTPUT);            // the LED pin  
  pinMode(mouseButton, INPUT_PULLUP); // the mouseButton pin
 // take control of the mouse:
  Mouse.begin();
}

void loop() {
  // read the switch:
  int switchState = digitalRead(switchPin);
  // if it's changed and it's low, toggle the mouse state:
  if (switchState != lastSwitchState) {
    if (switchState == LOW) {
      mouseIsActive = !mouseIsActive;
      // turn on LED to indicate mouse state:
      digitalWrite(ledPin, mouseIsActive);
    }
  }
  // save switch state for next comparison:
  lastSwitchState = switchState;

  // read and scale the two axes:
  int xReading = readAxis(A0);
  int yReading = readAxis(A1);

  // if the mouse control state is active, move the mouse:
  if (mouseIsActive) {
    Mouse.move(xReading, yReading, 0);
  }  

  // read the mouse button and click or not click:
  // if the mouse button is pressed:
  if (digitalRead(mouseButton) == LOW) {
    // if the mouse is not pressed, press it:
    if (!Mouse.isPressed(MOUSE_LEFT)) {
      Mouse.press(MOUSE_LEFT);
    }
  }
  // else the mouse button is not pressed:
  else {
    // if the mouse is pressed, release it:
    if (Mouse.isPressed(MOUSE_LEFT)) {
      Mouse.release(MOUSE_LEFT);
    }
  }

  delay(responseDelay);
}

/*
  reads an axis (0 or 1 for x or y) and scales the
 analog input range to a range from 0 to <range>
 */

int readAxis(int thisAxis) {
  // read the analog input:
  int reading = analogRead(thisAxis);

  // map the reading from the analog input range to the output range:
  reading = map(reading, 0, 1023, 0, range);

  // if the output reading is outside from the
  // rest position threshold,  use it:
  int distance = reading - center;

  if (abs(distance) < threshold) {
    distance = 0;
  }

  // return the distance for this axis:
  return distance;
}

動かしてみると、確かに動くのですが、操作感がよろしくないです。

reading = map(reading, 0, 1023, 0, range);

で reading の値を[ 0 < 1023 ]→ [ 0 < 12 ] に変換しています(詳しくは map関数の説明を読んで下さい)

distance(移動量)は、

int distance = reading - center;

で、[ -6 < 0(中立) < 6 ] 0 ~ ±6 となります。

その後で、中立位置のブレを吸収する処理をしています。
threshold(しきい値)を range/4 = 3 として、


if (abs(distance) < threshold) {
    distance = 0;
  }

で、移動量の絶対値が 3 より小さい場合は、0(中立)と見なします。
このために、移動量は[ -6,-5,-4,-3,0,3,4,5,6 ]となり、3以下の移動量(1,2)が出力されることが無くなるため、移動量が3以下の細かな操作が出来なくなっています。
ポインタの動きがかなりおおざっぱになっていて、動かしづらいです。

そこで、変更してみました。
先に、しきい値を適用して中立位置のブレを吸収しておき、そのあとで map関数で移動量に最適化します。
これなら、移動量は[,,,-3,-2,-1,0,1,2,3,,,]となります。

書いてみたのが以下のソースです。
しきい値(threshold)を設定する計算式は、てきとーで雰囲気で、なんとなく。多分大丈夫。

チェック用にシリアル出力を入れてあります(コメントアウトしてあります)
シリアル出力で数値を確認する場合は、マウスON/OFFスイッチでONにする前に、シリアルモニタを開いておいてください。


/*
  JoystickMouseControl
 
 Controls the mouse from a joystick on an Arduino Leonardo or Micro.
 Uses a pushbutton to turn on and off mouse control, and
 a second pushbutton to click the left mouse button
 
 Hardware:
 * 2-axis joystick connected to pins A0 and A1
 * pushbuttons connected to pin D2 and D3
 
 The mouse movement is always relative. This sketch reads
 two analog inputs that range from 0 to 1023 (or less on either end)
 and translates them into ranges of -6 to 6.
 The sketch assumes that the joystick resting values are around the
 middle of the range, but that they vary within a threshold.
 
 WARNING:  When you use the Mouse.move() command, the Arduino takes
 over your mouse!  Make sure you have control before you use the command.
 This sketch includes a pushbutton to toggle the mouse control state, so
 you can turn on and off mouse control.
 
 created 15 Sept 2011
 updated 28 Mar 2012
 by Tom Igoe
 
 modified August 2015
 by HisashiKato
 
 this code is in the public domain
 
 */

// set pin numbers for switch, joystick axes, and LED:
const int switchPin = 2;      // switch to turn on and off mouse control
const int mouseButton = 3;    // input pin for the mouse pushButton
const int xAxis = A0;         // joystick X axis  
const int yAxis = A1;         // joystick Y axis
const int ledPin = 13;        // Mouse control LED

// parameters for reading the joystick:
int analogInMin = 0;
int analogInMax = 1023;
int analogInRange = analogInMax - analogInMin;
int center = (analogInRange / 2) + analogInMin; // resting position value
int mouseMoveMaxValue = 6;                      // 
int range = mouseMoveMaxValue * 2;              // output range of X or Y movement
int threshold = analogInRange / range;          // resting threshold
int responseDelay = 5;                          // response delay of the mouse, in ms

boolean mouseIsActive = false;    // whether or not to control the mouse
int lastSwitchState = HIGH;       // previous switch state

void setup() {
  pinMode(switchPin, INPUT_PULLUP);       // the switch pin
  pinMode(ledPin, OUTPUT);                // the LED pin  
  pinMode(mouseButton, INPUT_PULLUP);     // the mousButton pin

/*
  // initialize serial communications at 9600 bps:
  Serial.begin(9600);
 */

 // take control of the mouse:
  Mouse.begin();

}

void loop() {
  // read the switch:
  int switchState = digitalRead(switchPin);
  // if it's changed and it's low, toggle the mouse state:
  if (switchState != lastSwitchState) {
    if (switchState == LOW) {
      mouseIsActive = !mouseIsActive;
      // turn on LED to indicate mouse state:
      digitalWrite(ledPin, mouseIsActive);
    }
  }
  // save switch state for next comparison:
  lastSwitchState = switchState;


  // read and scale the two axes:
  int xReading = readAxis(A0);
  int yReading = readAxis(A1);

  // if the mouse control state is active, move the mouse:
  if (mouseIsActive) {
    Mouse.move(xReading, yReading, 0);
/*
  // print the results to the serial monitor:
  Serial.print("outputX = ");      
  Serial.print(xReading);  
  Serial.print("\t outputY = ");      
  Serial.print(yReading);  

  Serial.print("\t MouseButton = ");      
  Serial.print(digitalRead(mouseButton));
  Serial.print("\n");
 */
  
  }  
  // read the mouse button and click or not click:
  // if the mouse button is pressed:
  if (digitalRead(mouseButton) == LOW) {
    // if the mouse is not pressed, press it:
    if (!Mouse.isPressed(MOUSE_LEFT)) {
      Mouse.press(MOUSE_LEFT);
    }
  }
  // else the mouse button is not pressed:
  else {
    // if the mouse is pressed, release it:
    if (Mouse.isPressed(MOUSE_LEFT)) {
      Mouse.release(MOUSE_LEFT);
    }
  }
  delay(responseDelay);
}

/*
  reads an axis (0 or 1 for x or y) and scales the
 analog input range to a range from 0 to <range>
 */

int readAxis(int thisAxis) {
  int reading = analogRead(thisAxis);    // read the analog input:
  int distance = reading - center;
  // if the output reading is outside from the rest position threshold,  use it:
  if (abs(distance) < threshold) {
    distance = 0;
  } else {
  // map the reading from the analog input range to the output range:
  distance = map(reading, analogInMin, analogInMax, 0, range) - (range/2);
  }
  return distance;  // return the distance for this axis:
}

まだいまいち。

今回ポインティングデバイスとして、スライドするタイプのアナログジョイスティックのパーツを購入したのですが、同じタイプのジョイスティックを、3DSやPSP等で使用しているのを知りました(私、携帯型ゲーム機を持っていないので知らなかったのです)

ネットを色々見ていたら、どうやら同じような部品っぽいので、使ってみようと思いつきまして、中古で300円だった「ニンテンドー3DS専用拡張スライドパッド」というオプションを購入しました。

Img_3ds_slidepad

「3DS専用拡張スライドパッド」は、3DSの右側にもスライドパッドを、そして、Lボタンx1、Rボタンx2、を3DSに増設するオプションで、3DSとは赤外線経由で接続されます。

これを分解して、中の基板を取り払って、配線を引き出して、Arduino に繋いでみることにしました。

「3DS用拡張スライドパッド」の分解方法を調べると、専用のY字頭のドライバーが必要ということで、送料を合わせても格安のをネット通販で購入。数回しか使いませんしね、使えれば安物で十分。けれど安いせいか握り部分に成形のバリがあって回すと手が痛かったのでバリを削りました。

早速分解、を始めたのですが、ネットの分解の解説をチラ見していただけだったので、スライドパッドの部品からのフレキケーブルを基板に繋ぐラッチ付きコネクタを壊してしまいました、やっちまった・・・
仕方が無いので、スライドパッドの部品を加工してフレキケーブルを取っ払って端子を露出させて、ケーブルで直結しました。
何とか復旧。

各スイッチ等は、こんな感じの配線になってました。
Sp
これをそのまま外に引き出して、ブレッドボードに刺さるようにしました。
あとは結線するだけ。

Rボタン:左クリック
ZRボタン:右クリック
ZLボタン:センタークリック

に、とりあえず割り当てて配線。

Pict0007

ソフトウェアでのスクロールホイール機能の実装は後日にすることにしました。

試しに最初の生データをシリアルで出力させるプログラムを書き込んで、スライドパッドから出力されるデータを見てみます。

このスライドパッドでは、今度は出力の最小値と最大値が 0 と 1023 になりません。
このようにアナログの部品はそれぞれ違いが出るので、補正のプログラムを組まなければなりません。キャリブレーションというやつです。
が、後回し。今回は手を付けてません。

スライドパッドの部品の内部の配線の都合上、Y軸の値の増加は上方向になります。
送られてくる Y軸のデータをそのまま使うとパッドを上に動かした時にカーソルが下に動いてしまうので、反転させます。

そして色々いじくってみたのが以下のソースです。

/*
  JoystickMouseControl
 
 Controls the mouse from a joystick on an Arduino Leonardo or Micro.
 Uses a pushbutton to turn on and off mouse control, and
 a second pushbutton to click the left mouse button
 
 Hardware:
 * 2-axis joystick connected to pins A0 and A1
 * pushbuttons connected to pin D2 and D3
 
 The mouse movement is always relative. This sketch reads
 two analog inputs that range from 0 to 1023 (or less on either end)
 and translates them into ranges of -6 to 6.
 The sketch assumes that the joystick resting values are around the
 middle of the range, but that they vary within a threshold.
 
 WARNING:  When you use the Mouse.move() command, the Arduino takes
 over your mouse!  Make sure you have control before you use the command.
 This sketch includes a pushbutton to toggle the mouse control state, so
 you can turn on and off mouse control.
 
 created 15 Sept 2011
 updated 28 Mar 2012
 by Tom Igoe
 https://www.arduino.cc/en/Tutorial/JoystickMouseControl
 
 
 modified August 2015
 by HisashiKato
 http://kato-h.cocolog-nifty.com/khweblog/
 
 this code is in the public domain
 
 */


// set pin numbers for switch, joystick axes, and LED:
const int switchPin = 2;      // switch to turn on and off mouse control

const int mouseLeftButton   = 3;    // input pin for the mouse LEFT pushButton
const int mouseRightButton  = 4;    // input pin for the mouse RIGHT pushButton
const int mouseMiddleButton = 5;    // input pin for the mouse MIDDLE pushButton

const int xAxis = A0;         // joystick X axis  
const int yAxis = A1;         // joystick Y axis

const int ledPin = 13;        // Mouse control LED


// parameters for reading the joystick:
int analogInMin = 0;
int analogInMax = 1023;
int analogInRange = analogInMax - analogInMin;
int center = (analogInRange / 2) + analogInMin; // resting position value
int mouseMoveMaxValue = 6;                      // 
int range = mouseMoveMaxValue * 2;              // output range of X or Y movement
int threshold = analogInRange / range;          // resting threshold
int responseDelay = 6;                          // response delay of the mouse, in ms

boolean mouseIsActive = false;    // whether or not to control the mouse
int lastSwitchState = HIGH;       // previous switch state

void setup() {
  pinMode(switchPin, INPUT_PULLUP);            // the switch pin
  pinMode(ledPin, OUTPUT);                     // the LED pin
  pinMode(mouseLeftButton, INPUT_PULLUP);      // the mouse Left Button pin
  pinMode(mouseRightButton, INPUT_PULLUP);     // the mouse Right Button pin
  pinMode(mouseMiddleButton, INPUT_PULLUP);    // the mouse Middle Button pin
  
/*
  // initialize serial communications at 9600 bps:
  Serial.begin(9600);
 */
  
  // take control of the mouse:
  Mouse.begin();
  
  
}

void loop() {
  // read the switch:
  int switchState = digitalRead(switchPin);
  // if it's changed and it's low, toggle the mouse state:
  if (switchState != lastSwitchState) {
    if (switchState == LOW) {
      mouseIsActive = !mouseIsActive;
      // turn on LED to indicate mouse state:
      digitalWrite(ledPin, mouseIsActive);
    }
  }
  // save switch state for next comparison:
  lastSwitchState = switchState;
  
  
  // read and scale the two axes:
  int xReading = readAxis(xAxis);
  int yReading = readAxis(yAxis);
  
  // if the mouse control state is active, move the mouse:
  if (mouseIsActive) {
    Mouse.move(xReading, yReading, 0);
  
/*
  // print the results to the serial monitor:
  Serial.print("outputX = ");      
  Serial.print(xReading);  
  Serial.print("\t outputY = ");      
  Serial.print(yReading);  
  Serial.print("\n");
 */
  
  }
  
  // read the mouse button and click or not click:
  readMouseButton(mouseLeftButton, MOUSE_LEFT);
  readMouseButton(mouseRightButton, MOUSE_RIGHT);
  readMouseButton(mouseMiddleButton, MOUSE_MIDDLE);
  
  delay(responseDelay);
}


/*
  reads an axis (0 or 1 for x or y) and scales the
 analog input range to a range from 0 to <range>
 */

int readAxis(int thisAxis) {
  // read the analog input:
  int reading = analogRead(thisAxis);
  int distance = reading - center;
  // if the output reading is outside from the rest position threshold,  use it:
  if (abs(distance) < threshold) {
    distance = 0;
  } else {
    // map the reading from the analog input range to the output range:
    distance = map(reading, analogInMin, analogInMax, 0, range) - (range / 2);
  }
  // the Y axis needs to be inverted in order to
  // map the movemment correctly:
  if (thisAxis == yAxis) {
    distance = -distance;
  }
  return distance;  // return the distance for this axis:
}


/*
 read the mouse button and click or not click:
 */

int readMouseButton(int mouseInputPin, int mouseButton) {
  // if the mouse button is pressed:
  if (digitalRead(mouseInputPin) == LOW) {
    // if the mouse is not pressed, press it:
    if (!Mouse.isPressed(mouseButton)) {
      Mouse.press(mouseButton);
    }
  }
  // else the mouse button is not pressed:
  else {
    // if the mouse is pressed, release it:
    if (Mouse.isPressed(mouseButton)) {
      Mouse.release(mouseButton);
    }
  }
}


これで一応、USBマウスとして使えます、が、快適とは言えません。

マウスホイール機能の実装も含めて、次回。
(つづく)

追記:2015/09/18
先日、Arduino Micro の製品ページを見たら「Arduino/Genuino MICRO」と表記が変わってて「?」と思っていたのですよ。
あと、上記のソースの元になったサンプルコードが、最近、若干書き換えられてさらに「???」に。
サンプルコードにヘッダファイルのインクルードが追加されたけれど、そのヘッダファイルうちのIDEの中になくなくない???って。

実は、、Arduino の組織が内部分裂をしているのは知っていたのですが、WEBページまで分かれていることを、今さらですが知りました。

ずっと見ていた Arduino のWEBページは、https://www.arduino.cc/ で、ここのダウンロードのページで配布されている Arduino IDE は、
Arduino 1.6.x なのですが、

今、私が使っている Arduino IDE は、http://www.arduino.org/ からダウンロードした、
Arduino 1.7.6 だったのでした。

あれ?どーゆー経緯で自分は 1.7.x の方を落として使ってたんだろ?覚えてない・・・
とりあえず、私のソースは、1.7.x を前提としているということで、よろしくお願いします。

 

追記:2015/10/10
ソフトウェア追加です。

キャリブレーション(Calibration 較正)

今回使用した3DS用拡張スライドパッドのアナログスライドパッドの部品からの出力のことなのですが、
シリアルモニタで生データを見るとお分かりでしょうが、出力の最小値と最大値が、0 - 1023 にならずに、0より大きな値から1023より小さな値の範囲になります。
これは多分、部品のばらつきの問題や、スライドパッドの部品に余計な負荷が掛からないようにするために、あえてスライドパッド部品の最小と最大に行かないように、パッド部分が動く範囲を成形品の方で制限しているでしょう。

プログラム側では、今使用しているスライドパッドの出力値の最小と最大が必要になるので(ソース内のアナログ入力の analogInMin と analogInMax)、これを調べておかなければなりません。
そこで、Example の Calibration を元にして、
スライドパッドの出力データの最小と最大を表示させるプログラムを書いてみました。

// set pin numbers for switch, joystick axes, and LED:
const int switchPin = 2;      // switch to turn on and off mouse control
const int ledPin = 13;        // Mouse control LED

const int xAxis = A0;         // joystick X axis  
const int yAxis = A1;         // joystick Y axis

// variables:
int xSensorValue = 0;         // the sensor value
int xSensorMin = 1023;        // minimum sensor value
int xSensorMax = 0;           // maximum sensor value
int ySensorValue = 0;         // the sensor value
int ySensorMin = 1023;        // minimum sensor value
int ySensorMax = 0;           // maximum sensor value

int xCenter;
int yCenter;

boolean valueReset = true;
int stopCount= 0;

int xMinTotal = 0;
int xMaxTotal = 0;
int yMinTotal = 0;
int yMaxTotal = 0;

int xMinAverage;
int xMaxAverage;
int yMinAverage;
int yMaxAverage;

int responseDelay = 5;        // response delay of the mouse, in ms

// mouse active switch:
boolean mouseIsActive = false;    // whether or not to control the mouse
int lastSwitchState = HIGH;       // previous switch state


void setup() {
  // turn on LED to signal the start of the calibration period:
  pinMode(switchPin, INPUT_PULLUP);            // the switch pin
  pinMode(ledPin, OUTPUT);                     // the LED pin
  
  // initialize serial communications at 9600 bps:
  Serial.begin(9600);
}

void loop() {
  // read the switch:
  int switchState = digitalRead(switchPin);
  // if it's changed and it's high, toggle the mouse state:
  if (switchState != lastSwitchState) {
    if (switchState == LOW) {
      mouseIsActive = !mouseIsActive;
      // turn on LED to indicate mouse state:
      digitalWrite(ledPin, mouseIsActive);
    }
  }
  // save switch state for next comparison:
  lastSwitchState = switchState;

  // if the mouse control state is active, move the mouse:
  if (mouseIsActive) {
    valueReset = false;
    
    // read the sensor:
    xSensorValue = analogRead(xAxis);
    ySensorValue = analogRead(yAxis);

    // record the maximum sensor value
    if (xSensorValue > xSensorMax) {
      xSensorMax = xSensorValue;
    }
    if (ySensorValue > ySensorMax) {
      ySensorMax = ySensorValue;
    }
    // record the minimum sensor value
    if (xSensorValue < xSensorMin) {
      xSensorMin = xSensorValue;
    }
    if (ySensorValue < ySensorMin) {
      ySensorMin = ySensorValue;
    }

    xCenter = ((xSensorMax - xSensorMin) / 2) + xSensorMin;
    yCenter = ((ySensorMax - ySensorMin) / 2) + ySensorMin;

    // print the results to the serial monitor:
    Serial.print("input_X = ");
    Serial.print(xSensorValue);
    Serial.print("\t input_Y = ");
    Serial.print(ySensorValue);

    Serial.print("\t X_Min = ");
    Serial.print(xSensorMin);
    Serial.print("\t X_Max = ");
    Serial.print(xSensorMax);
    Serial.print("\t Y_Min = ");
    Serial.print(ySensorMin);
    Serial.print("\t Y_Max = ");
    Serial.print(ySensorMax);

    Serial.print("\t X_Center = ");
    Serial.print(xCenter);
    Serial.print("\t Y_Center = ");
    Serial.print(yCenter);

    Serial.print("\n");
  
  } else {
    if (!valueReset) {
      stopCount += 1;

      xMinTotal += xSensorMin;
      xMaxTotal += xSensorMax;
      yMinTotal += ySensorMin;
      yMaxTotal += ySensorMax;

      xMinAverage = xMinTotal / stopCount;
      xMaxAverage = xMaxTotal / stopCount;
      yMinAverage = yMinTotal / stopCount;
      yMaxAverage = yMaxTotal / stopCount;

      Serial.print("\n");
      Serial.print("X_Min_ave = ");
      Serial.print(xMinAverage);
      Serial.print("\t X_Max_ave = ");
      Serial.print(xMaxAverage);
      Serial.print("\t Y_Min_ave = ");
      Serial.print(yMinAverage);
      Serial.print("\t Y_Max_ave = ");
      Serial.print(yMaxAverage);

      Serial.print("\t stop = ");
      Serial.print(stopCount);

      Serial.print("\n");

      //reset min max value:
      xSensorMin = 1023;
      xSensorMax = 0;
      ySensorMin = 1023;
      ySensorMax = 0;

      valueReset = true;
    }
  }
  
  delay(responseDelay);
}

これを書き込んで、シリアルモニタを起動して、マウス起動スイッチを押すと、シリアルに生データを吐き出します。
スライドパッドをぐーるぐーると縁に沿って回してやると、Min と Max に最小値と最大値が残るようになっています。
マウス起動スイッチを押して停止させると、最小値と最大値、停止した回数が表示されるように書き加えてあります。
そしておまけ要素として、
動かして最小と最大を計測する>止める>また動かして計測する、
を繰り返すと、止めた回数を元にした、最小値と最大値の平均値(Max_ave)を表示するようにしてあります。あんまし意味ないですが目安程度にはなるかと。

この値を見ながら、アナログ入力の最小値と最大値 analogInMin と analogInMax を決めると良いと思います。
(平均値から少し内側の数値にするとよろしいかもです)



マウスポインタの移動速度

PC等に渡される、マウスの移動量の数値ですが、Example を始め、今回のプログラム群では、[-6 < 0 < 6]に指定しています。
移動量の最小の値は [ ±1 ] なのですが、移動量が[ 1 ]になっても、下記のような感じでデータが渡されるので、出力された回数の分だけ、ポインタが動いてしまいます。

こんな感じというか雰囲気
...333333222222111111000000111111000000000000111111111111222222...

プログラムの周回ディレイを長めにとることで対応することが可能だと思います。ですが、それをするとプログラム全体の動作が遅くなるのでデータの取りこぼしや遅延が起きてしまいます。

そこで、今回は、以下のような方法を試してみました。
移動量[ 1 ]を渡したあとに、一定時間[ 0 ]を挟み、その後再び[ 1 ]を渡すを繰り返します。
こうすると、ちょっと動いては止まる、ちょっと動いては止まる、となって見かけ上はゆっくり移動することになります。
そして[ 0 ]を挟む時間を変えることで移動速度の調整が出来ます。こんな感じで

...11111111111001001001001000001000001000001000001000000000000000....

空ける間隔(0を挟む時間)は内蔵のタイマーを元にします。
このあたりは Example の Blink Without Delay を参考にしました。

そしてとりあえず作ってみたのが、移動量は[ 1 ]のみで、それを出力する間隔だけ可変させるテストプログラムです。

マウスカーソルがスライドパッドを動かした量に合わせて、ゆぅーっくり動きます。

// set pin numbers for switch, joystick axes, and LED:
const int switchPin = 2;      // switch to turn on and off mouse control

const int mouseLeftButton   = 3;    // input pin for the mouse LEFT pushButton
const int mouseRightButton  = 4;    // input pin for the mouse RIGHT pushButton
const int mouseMiddleButton = 5;    // input pin for the mouse MIDDLE pushButton

const int xAxis = A0;         // joystick X axis  
const int yAxis = A1;         // joystick Y axis

const int ledPin = 13;        // Mouse control LED

// interval time for slow move:
const int pointerMoveInterval = 60;
const int scrollInterval = 0;

// parameters for reading the joystick:

int xAnalogInMin = 160;
int xAnalogInMax = 890;
int yAnalogInMin = 140;
int yAnalogInMax = 860;

int xPointerMoveDirection = 0;
int yPointerMoveDirection = 0;

int mouseMoveMaxValue = 6;                      // 
int range = mouseMoveMaxValue * 2;              // output range of X or Y movement

int responseDelay = 6;                          // response delay of the mouse, in ms

int xSendAxisData = 0;
int ySendAxisData = 0;
int sendWeelData = 0;

// timer:
// unsigned long currentMillis;
unsigned long CurrentMillis;
unsigned long xPreviousMillis = 0;        // will store last time mouse move data was updated
unsigned long yPreviousMillis = 0;        // will store last time mouse move data was updated

// mouse active switch:
boolean mouseIsActive = false;    // whether or not to control the mouse
int lastSwitchState = HIGH;       // previous switch state


void setup() {
  pinMode(switchPin, INPUT_PULLUP);            // the switch pin
  pinMode(ledPin, OUTPUT);                     // the LED pin
  pinMode(mouseLeftButton, INPUT_PULLUP);      // the mouse Left Button pin
  pinMode(mouseRightButton, INPUT_PULLUP);     // the mouse Right Button pin
  pinMode(mouseMiddleButton, INPUT_PULLUP);    // the mouse mode change Button pin

  // initialize serial communications at 9600 bps:
  Serial.begin(9600);
  
  // take control of the mouse:
  Mouse.begin();

}

void loop(){
  // read the switch:
  int switchState = digitalRead(switchPin);
  // if it's changed and it's high, toggle the mouse state:
  if (switchState != lastSwitchState) {
    if (switchState == LOW) {
      mouseIsActive = !mouseIsActive;
      // turn on LED to indicate mouse state:
      digitalWrite(ledPin, mouseIsActive);
    }
  }
  // save switch state for next comparison:
  lastSwitchState = switchState;


  // read for I/O:
  int xReading = readAxis(xAxis, xAnalogInMin, xAnalogInMax);
  int yReading = readAxis(yAxis, yAnalogInMin, yAnalogInMax);

  // pointer move slow:
  CurrentMillis = millis();
  xSendAxisData = pointerSlowMove(xReading, xPreviousMillis);
  if(xSendAxisData != 0) {
    xPreviousMillis = CurrentMillis;
  }
  ySendAxisData = pointerSlowMove(yReading, yPreviousMillis);
  if(ySendAxisData != 0) {
    yPreviousMillis = CurrentMillis;
  }

// set mouse data for sending to USB:

  // if the mouse control state is active, move the mouse:
  if (mouseIsActive) {
    Mouse.move(xSendAxisData, ySendAxisData, 0);
    // read the mouse button and click or not click:
    readMouseButton(mouseLeftButton, MOUSE_LEFT);
    readMouseButton(mouseRightButton, MOUSE_RIGHT);
    readMouseButton(mouseMiddleButton, MOUSE_MIDDLE);
    
    
    // print the results to the serial monitor:
    Serial.print("inputX = ");
    Serial.print(xReading);
    Serial.print("\t inputY = ");
    Serial.print(yReading);

    Serial.print("\t outputX = ");
    Serial.print(xSendAxisData);
    Serial.print("\t outputY = ");
    Serial.print(ySendAxisData);

    Serial.print("\n");
    
  }
  delay(responseDelay);
}


/*
 read axis data:
 */

int readAxis(int thisAxis, int analogInMin, int analogInMax) {
  // read the analog input:
  int analogInRange = analogInMax - analogInMin;
  int center = (analogInRange / 2) + analogInMin;
  
  int reading = analogRead(thisAxis);
  reading = constrain(reading, analogInMin, analogInMax);
  
  int distance = reading - center;
  int threshold = analogInRange / range;        // resting threshold
  // if the output reading is outside from the rest position threshold,  use it:
  if (abs(distance) < threshold) {
    distance = 0;
  } else {
    // map the reading from the analog input range to the output range:
    distance = map(reading, analogInMin, analogInMax, 0, range) - (range / 2);
  }
  // the Y axis needs to be inverted in order to
  // map the movemment correctly:
  if (thisAxis == yAxis) {
    distance = -distance;
  }
  return distance;  // return the distance for this axis:
}


/*
 cursor move low speed:
 */

int pointerSlowMove(int reading, unsigned long previousMillis) {
  unsigned long currentMillis = millis();
  if (reading == 0) {
    return 0;
  } else {
    int interval = pointerMoveInterval / abs(reading);
    int pointerMoveDirection = reading / abs(reading);
    if (currentMillis - previousMillis < interval) {
      return 0;
    } else {
      return pointerMoveDirection;
    }
  }
}


/*
 read the mouse button and click or not click:
 */

int readMouseButton(int mouseInputPin, int mouseButton) {
  // if the mouse button is pressed:
  if (digitalRead(mouseInputPin) == LOW) {
    // if the mouse is not pressed, press it:
    if (!Mouse.isPressed(mouseButton)) {
      Mouse.press(mouseButton);
    }
  }
  // else the mouse button is not pressed:
  else {
    // if the mouse is pressed, release it:
    if (Mouse.isPressed(mouseButton)) {
      Mouse.release(mouseButton);
    }
  }
}

このゆっくり移動と、通常移動を合わせます。

 

これまでのマウスのプログラムではスライドパッドの移動量と出力する移動量をそのまま map 関数で対比させていましたが、それを低速移動の領域と通常移動の領域に分割してみました。
以下の図のようにしてみました。(クリックで拡大します、が、この図は間違ってるかも???)
Analog_1
作ってみたのが以下のテストプログラムです。

// set pin numbers for switch, joystick axes, and LED:
const int switchPin = 2;      // switch to turn on and off mouse control

const int mouseLeftButton   = 3;    // input pin for the mouse LEFT pushButton
const int mouseRightButton  = 4;    // input pin for the mouse RIGHT pushButton
const int mouseMiddleButton = 5;    // input pin for the mouse MIDDLE pushButton

const int xAxis = A0;         // joystick X axis  
const int yAxis = A1;         // joystick Y axis

const int ledPin = 13;        // Mouse control LED

// interval time for slow move:
const int pointerMoveInterval = 60;
const int scrollInterval = 0;

// parameters for reading the joystick:

int xAnalogInMin = 160;
int xAnalogInMax = 890;
int yAnalogInMin = 140;
int yAnalogInMax = 860;

int xPointerMoveDirection = 0;
int yPointerMoveDirection = 0;

int pointerMoveMaxValue = 5;                      // -5 < send data < 5
int pointerSlowMoveRange = 3;
int range = (pointerMoveMaxValue + pointerSlowMoveRange) * 2;  // output range of X or Y movement

int responseDelay = 5;                          // response delay of the mouse, in ms

int xSendAxisData = 0;
int ySendAxisData = 0;
int sendWeelData = 0;

// timer:
// unsigned long currentMillis;
unsigned long CurrentMillis;
unsigned long xPreviousMillis = 0;        // will store last time mouse move data was updated
unsigned long yPreviousMillis = 0;        // will store last time mouse move data was updated

// mouse active switch:
boolean mouseIsActive = false;    // whether or not to control the mouse
int lastSwitchState = HIGH;       // previous switch state


void setup() {
  pinMode(switchPin, INPUT_PULLUP);            // the switch pin
  pinMode(ledPin, OUTPUT);                     // the LED pin
  pinMode(mouseLeftButton, INPUT_PULLUP);      // the mouse Left Button pin
  pinMode(mouseRightButton, INPUT_PULLUP);     // the mouse Right Button pin
  pinMode(mouseMiddleButton, INPUT_PULLUP);    // the mouse Middle Button pin

  
  // initialize serial communications at 9600 bps:
  Serial.begin(9600);
  
  // take control of the mouse:
  Mouse.begin();

}

void loop(){
  // read the switch:
  int switchState = digitalRead(switchPin);
  // if it's changed and it's high, toggle the mouse state:
  if (switchState != lastSwitchState) {
    if (switchState == LOW) {
      mouseIsActive = !mouseIsActive;
      // turn on LED to indicate mouse state:
      digitalWrite(ledPin, mouseIsActive);
    }
  }
  // save switch state for next comparison:
  lastSwitchState = switchState;


  // read for I/O:
  int xReading = readAxis(xAxis, xAnalogInMin, xAnalogInMax);
  int yReading = readAxis(yAxis, yAnalogInMin, yAnalogInMax);

  // set xAxis data for send:
  if (xReading == 0) {
    // center:
    xSendAxisData = 0;
  } else if (abs(xReading) <= pointerSlowMoveRange) {
    // slow:
    CurrentMillis = millis();
    xSendAxisData = pointerSlowMove(xReading, xPreviousMillis);
    if(xSendAxisData != 0) {
      xPreviousMillis = CurrentMillis;
    }
  } else {
    xSendAxisData = (xReading / abs(xReading)) * (abs(xReading) - pointerSlowMoveRange);
  }
  // set yAxis data for send:
  if (yReading == 0) {
    // center:
    ySendAxisData = 0;
  } else if (abs(yReading) <= pointerSlowMoveRange) {
    // slow:
    CurrentMillis = millis();
    ySendAxisData = pointerSlowMove(yReading, yPreviousMillis);
    if(ySendAxisData != 0) {
      yPreviousMillis = CurrentMillis;
    }
  } else {
    ySendAxisData = (yReading / abs(yReading)) * (abs(yReading) - pointerSlowMoveRange);
  }


// set mouse data for sending to USB:

  // if the mouse control state is active, move the mouse:
  if (mouseIsActive) {
    Mouse.move(xSendAxisData, ySendAxisData, 0);
    // read the mouse button and click or not click:
    readMouseButton(mouseLeftButton, MOUSE_LEFT);
    readMouseButton(mouseRightButton, MOUSE_RIGHT);
    readMouseButton(mouseMiddleButton, MOUSE_MIDDLE);
    
    
    // print the results to the serial monitor:
    Serial.print("inputX = ");
    Serial.print(xReading);
    Serial.print("\t inputY = ");
    Serial.print(yReading);  


    Serial.print("\t outputX = ");
    Serial.print(xSendAxisData);
    Serial.print("\t outputY = ");
    Serial.print(ySendAxisData);

    Serial.print("\n");
    
    
  }
  delay(responseDelay);
}


/*
 read axis data:
 */

int readAxis(int thisAxis, int analogInMin, int analogInMax) {
  // read the analog input:
  int analogInRange = analogInMax - analogInMin;
  int center = (analogInRange / 2) + analogInMin;
  
  int reading = analogRead(thisAxis);
  reading = constrain(reading, analogInMin, analogInMax);
  
  int distance = reading - center;
  int threshold = analogInRange / range;        // resting threshold
  // if the output reading is outside from the rest position threshold,  use it:
  if (abs(distance) < threshold) {
    distance = 0;
  } else {
    // map the reading from the analog input range to the output range:
    distance = map(reading, analogInMin, analogInMax, 0, range) - (range / 2);
  }
  // the Y axis needs to be inverted in order to
  // map the movemment correctly:
  if (thisAxis == yAxis) {
    distance = -distance;
  }
  return distance;  // return the distance for this axis:
}


/*
 cursor move low speed:
 */

int pointerSlowMove(int reading, unsigned long previousMillis) {
  unsigned long currentMillis = millis();
  if (reading == 0) {
    return 0;
  } else {
    int interval = pointerMoveInterval / abs(reading);
    int pointerMoveDirection = reading / abs(reading);
    if (currentMillis - previousMillis < interval) {
      return 0;
    } else {
      return pointerMoveDirection;
    }
  }
}


/*
 read the mouse button and click or not click:
 */

int readMouseButton(int mouseInputPin, int mouseButton) {
  // if the mouse button is pressed:
  if (digitalRead(mouseInputPin) == LOW) {
    // if the mouse is not pressed, press it:
    if (!Mouse.isPressed(mouseButton)) {
      Mouse.press(mouseButton);
    }
  }
  // else the mouse button is not pressed:
  else {
    // if the mouse is pressed, release it:
    if (Mouse.isPressed(mouseButton)) {
      Mouse.release(mouseButton);
    }
  }
}

これでマウスカーソルを動かすと、かなり自然に移動させられると自分では感じたのですがどうでしょうか?

 

スクロールホイール

続いてマウスのスクロールホイールの機能の実装です。
Arduino IDE のUSBマウスの関数 Mouse.move() には、スクロールホイールのパラメータも設定出来るようになっています。https://www.arduino.cc/en/Reference/MouseMove

Mouse.move(xVal, yPos, wheel) <?(yPosは本当はyVal?)

但し、この wheel の変数に、具体的に、いったい何を入れればいいかは、リファレンスに書かれていません。
(私が見つけられなかっただけ?)

とにかくググって探してみたところ、以下のような感じの文章が、USBマウス関連のとあるデータシートにあるのを見つけました。

以下では、USBレポートプロトコルのフォーマットで、4番目のバイトにホイールの動きの情報を追加することが可能であることを説明します。
ホイールを前方に移動すると4番目のバイトに「0x01」を報告し、
後方に移動したときは4番目のバイトに「0xFF」を報告します。
ホイールが停止状態のときには、このバイトには「0x00」が割り当てられます。

つまり、ホイールを回して1回カコンと動くたびに、前方の時は「0x01(+1)」を、後方の時は「0xFF(-1)」をデータとして渡して、動いていない時は「0x00」を入れておく、ということらしいです。

というわけで、Mouse.move(xVal, yPos, wheel) 関数の wheel 変数に、この値を入れてみることにしました。
但しホイールを回すスピード、つまりはクリック感(カコカコ)の時間の間隔を変えることでスクロールのスピードが変わるっぽいので、上で使った、マウスポインタのゆっくり移動の方法をそのまま使ってみることにしました。カコカコの間隔をスライドパッドを動かした量に比例させます。

そしで出来たのがスクロールホイールの機能も使えるようにしたプログラム。
ZLボタン(デジタルインプットの5番ピン)をスクロールの切替にしてあります。押すとスクロールホイールモードになり、もう一度押すとマウスモードになるという、トグルにしてあります。
これは以前購入した小型のトラックボールの仕様にあわせてみました。
これ。(これ、どっかから再販売されないかな…)
400ma018_ma

// set pin numbers for switch, joystick axes, and LED:
const int switchPin = 2;      // switch to turn on and off mouse control

const int mouseLeftButton   = 3;    // input pin for the mouse LEFT pushButton
const int mouseRightButton  = 4;    // input pin for the mouse RIGHT pushButton
//const int mouseMiddleButton = 5;    // input pin for the mouse MIDDLE pushButton
const int mouseScrollModeButton = 5;    // input pin for the mouse mode change pushButton

const int xAxis = A0;         // joystick X axis  
const int yAxis = A1;         // joystick Y axis

const int ledPin = 13;        // Mouse control LED

// send data when mouse wheel is move:
const int scrollWheelStop = 0x00;
const int scrollWheelUp   = 0x01;
const int scrollWheelDown = 0xFF;

// interval time for slow move:
const int pointerMoveInterval = 50;
const int scrollInterval = 500;

// parameters for reading the joystick:

int xAnalogInMin = 160;
int xAnalogInMax = 890;
int yAnalogInMin = 140;
int yAnalogInMax = 860;
int threshold = 30;          // resting threshold

int xPointerMoveDirection = 0;
int yPointerMoveDirection = 0;

int pointerMoveMaxValue = 5;
int pointerSlowMoveRange = 3;
int range = (pointerMoveMaxValue + pointerSlowMoveRange) * 2;  // output range of X or Y movement

int responseDelay = 5;                    // response delay of the mouse, in ms

int xSendAxisData = 0;
int ySendAxisData = 0;
int sendWheelData = 0;

// timer:
// unsigned long currentMillis;
unsigned long CurrentMillis;
unsigned long xPreviousMillis = 0;        // will store last time mouse move data was updated
unsigned long yPreviousMillis = 0;        // will store last time mouse move data was updated
unsigned long wheelPreviousMillis = 0;

// mouse active switch:
boolean mouseIsActive = false;    // whether or not to control the mouse
int lastSwitchState = HIGH;       // previous switch state
// mouse mode change switch:
boolean scrollMode = false;       // whether or not to control the mouse
int lastModeSwitchState = HIGH;   // previous switch state


void setup() {
  pinMode(switchPin, INPUT_PULLUP);            // the switch pin
  pinMode(ledPin, OUTPUT);                     // the LED pin
  pinMode(mouseLeftButton, INPUT_PULLUP);      // the mouse Left Button pin
  pinMode(mouseRightButton, INPUT_PULLUP);     // the mouse Right Button pin
//pinMode(mouseMiddleButton, INPUT_PULLUP);    // the mouse Middle Button pin
  pinMode(mouseScrollModeButton, INPUT_PULLUP);     // the mouse mode change Button pin

/*
  // initialize serial communications at 9600 bps:
  Serial.begin(9600);
 */
  
  // take control of the mouse:
  Mouse.begin();

}

void loop(){
  // read the switch:
  int switchState = digitalRead(switchPin);
  // if it's changed and it's high, toggle the mouse state:
  if (switchState != lastSwitchState) {
    if (switchState == LOW) {
      mouseIsActive = !mouseIsActive;
      // turn on LED to indicate mouse state:
      digitalWrite(ledPin, mouseIsActive);
    }
  }
  // save switch state for next comparison:
  lastSwitchState = switchState;


  // read for I/O:
  int xReading = readAxis(xAxis, xAnalogInMin, xAnalogInMax);
  int yReading = readAxis(yAxis, yAnalogInMin, yAnalogInMax);

  // read the mouseModeSwitch:
  int modeSwitchState = digitalRead(mouseScrollModeButton);
  if (modeSwitchState != lastModeSwitchState) {
    if (modeSwitchState == LOW) {
      scrollMode = !scrollMode;
    }
  }
  // save switch state for next comparison:
  lastModeSwitchState = modeSwitchState;

  if (scrollMode == false) {
    // pointer mode:
    sendWheelData = scrollWheelStop;
    
    // set xAxis data for send:
    if (xReading == 0) {
      // center:
      xSendAxisData = 0;
    } else if (abs(xReading) <= pointerSlowMoveRange) {
      // slow:
      CurrentMillis = millis();
      xSendAxisData = pointerSlowMove(xReading, xPreviousMillis);
      if(xSendAxisData != 0) {
        xPreviousMillis = CurrentMillis;
      }
    } else {
      xSendAxisData = (xReading / abs(xReading)) * (abs(xReading) - pointerSlowMoveRange);
    }
    // set yAxis data for send:
    if (yReading == 0) {
      // center:
      ySendAxisData = 0;
    } else if (abs(yReading) <= pointerSlowMoveRange) {
      // slow:
      CurrentMillis = millis();
      ySendAxisData = pointerSlowMove(yReading, yPreviousMillis);
      if(ySendAxisData != 0) {
        yPreviousMillis = CurrentMillis;
      }
    } else {
      ySendAxisData = (yReading / abs(yReading)) * (abs(yReading) - pointerSlowMoveRange);
    }

  } else {
    // scroll mode:
    xSendAxisData = 0;
    ySendAxisData = 0;

    if (yReading == 0) {
      // center:
      sendWheelData = 0;
    } else {
      // scroll up and down:
      CurrentMillis = millis();
      int wheelMoveDirection = yReading / abs(yReading);
      if (CurrentMillis - wheelPreviousMillis < (scrollInterval / abs(yReading)) ) {
        sendWheelData = 0;
      } else {
        if (wheelMoveDirection < 0) {
          // scroll up:
          sendWheelData = scrollWheelUp;
        } else {
          //  scroll down:
          sendWheelData = scrollWheelDown;
        }
      }
      if(sendWheelData != 0) {
        wheelPreviousMillis = CurrentMillis;
      }
    }
  }


// set mouse data for sending to USB:

  // if the mouse control state is active, move the mouse:
  if (mouseIsActive) {

    Mouse.move(xSendAxisData, ySendAxisData, sendWheelData);
    // read the mouse button and click or not click:
    readMouseButton(mouseLeftButton, MOUSE_LEFT);
    readMouseButton(mouseRightButton, MOUSE_RIGHT);
//  readMouseButton(mouseMiddleButton, MOUSE_MIDDLE);
    
/*
    // print the results to the serial monitor:
    Serial.print("inputX = ");
    Serial.print(xReading);
    Serial.print("\t inputY = ");
    Serial.print(yReading);

    Serial.print("\t outputX = ");
    Serial.print(xSendAxisData);
    Serial.print("\t outputY = ");
    Serial.print(ySendAxisData);
    Serial.print("\t outputWheel = ");
    Serial.print(sendWheelData);

    Serial.print("\n");
 */
    
  }
  delay(responseDelay);
}


/*
 read axis data:
 */

int readAxis(int thisAxis, int analogInMin, int analogInMax) {
  // read the analog input:
  int analogInRange = analogInMax - analogInMin;
  int center = (analogInRange / 2) + analogInMin;
  
  int reading = analogRead(thisAxis);
  reading = constrain(reading, analogInMin, analogInMax);
  
  int distance = reading - center;
//int threshold = analogInRange / range;        // resting threshold
  // if the output reading is outside from the rest position threshold,  use it:
  if (abs(distance) < threshold) {
    distance = 0;
  } else {
    // map the reading from the analog input range to the output range:
    distance = map(reading, analogInMin, analogInMax, 0, range) - (range / 2);
  }
  // the Y axis needs to be inverted in order to
  // map the movemment correctly:
  if (thisAxis == yAxis) {
    distance = -distance;
  }
  return distance;  // return the distance for this axis:
}


/*
 cursor move low speed:
 */

int pointerSlowMove(int reading, unsigned long previousMillis) {
  unsigned long currentMillis = millis();
  if (reading == 0) {
    return 0;
  } else {
    int interval = pointerMoveInterval / abs(reading);
    int pointerMoveDirection = reading / abs(reading);
    if (currentMillis - previousMillis < interval) {
      return 0;
    } else {
      return pointerMoveDirection;
    }
  }
}


/*
 read the mouse button and click or not click:
 */

int readMouseButton(int mouseInputPin, int mouseButton) {
  // if the mouse button is pressed:
  if (digitalRead(mouseInputPin) == LOW) {
    // if the mouse is not pressed, press it:
    if (!Mouse.isPressed(mouseButton)) {
      Mouse.press(mouseButton);
    }
  }
  // else the mouse button is not pressed:
  else {
    // if the mouse is pressed, release it:
    if (Mouse.isPressed(mouseButton)) {
      Mouse.release(mouseButton);
    }
  }
}

 

いざ使ってみたら、ちょっと違和感があったので、もうひとつ、ZLボタンを押している間はスクロール、ボタンを離すとマウスというのも作ってみました。

// set pin numbers for switch, joystick axes, and LED:
const int switchPin = 2;      // switch to turn on and off mouse control

const int mouseLeftButton   = 3;      // input pin for the mouse LEFT pushButton
const int mouseRightButton  = 4;      // input pin for the mouse RIGHT pushButton
//const int mouseMiddleButton = 5;    // input pin for the mouse MIDDLE pushButton
const int mouseScrollModeButton = 5;  // input pin for the mouse mode change pushButton

const int xAxis = A0;         // joystick X axis  
const int yAxis = A1;         // joystick Y axis

const int ledPin = 13;        // Mouse control LED

// send data when mouse wheel is move:
const int scrollWheelStop = 0x00;
const int scrollWheelUp   = 0x01;
const int scrollWheelDown = 0xFF;

// interval time for slow move:
const int pointerMoveInterval = 50;
const int scrollInterval = 500;

// parameters for reading the joystick:

int xAnalogInMin = 160;
int xAnalogInMax = 890;
int yAnalogInMin = 140;
int yAnalogInMax = 860;
int threshold = 30;          // resting threshold


int xPointerMoveDirection = 0;
int yPointerMoveDirection = 0;

int pointerMoveMaxValue = 5;
int pointerSlowMoveRange = 3;
int range = (pointerMoveMaxValue + pointerSlowMoveRange) * 2;  // output range of X or Y movement

int responseDelay = 5;                    // response delay of the mouse, in ms

int xSendAxisData = 0;
int ySendAxisData = 0;
int sendWheelData = 0;

// timer:
// unsigned long currentMillis;
unsigned long CurrentMillis;
unsigned long xPreviousMillis = 0;        // will store last time mouse move data was updated
unsigned long yPreviousMillis = 0;        // will store last time mouse move data was updated
unsigned long wheelPreviousMillis = 0;

// mouse active switch:
boolean mouseIsActive = false;    // whether or not to control the mouse
int lastSwitchState = HIGH;       // previous switch state
// mouse mode change switch:
boolean scrollMode = false;
int lastModeSwitchState = HIGH;   // previous switch state


void setup() {
  pinMode(switchPin, INPUT_PULLUP);            // the switch pin
  pinMode(ledPin, OUTPUT);                     // the LED pin
  pinMode(mouseLeftButton, INPUT_PULLUP);      // the mouse Left Button pin
  pinMode(mouseRightButton, INPUT_PULLUP);     // the mouse Right Button pin
//pinMode(mouseMiddleButton, INPUT_PULLUP);    // the mouse Middle Button pin
  pinMode(mouseScrollModeButton, INPUT_PULLUP);     // the mouse mode change Button pin

/*
  // initialize serial communications at 9600 bps:
  Serial.begin(9600);
 */
  
  // take control of the mouse:
  Mouse.begin();

}

void loop(){
  // read the switch:
  int switchState = digitalRead(switchPin);
  // if it's changed and it's high, toggle the mouse state:
  if (switchState != lastSwitchState) {
    if (switchState == LOW) {
      mouseIsActive = !mouseIsActive;
      // turn on LED to indicate mouse state:
      digitalWrite(ledPin, mouseIsActive);
    }
  }
  // save switch state for next comparison:
  lastSwitchState = switchState;


  // read for I/O:
  int xReading = readAxis(xAxis, xAnalogInMin, xAnalogInMax);
  int yReading = readAxis(yAxis, yAnalogInMin, yAnalogInMax);

  // read the mouseModeSwitch:
  int modeSwitchState = digitalRead(mouseScrollModeButton);
  if (modeSwitchState == LOW) {
    scrollMode = true;
  } else {
    scrollMode = false;
  }


  if (scrollMode == false) {
    // pointer mode:
    sendWheelData = scrollWheelStop;
    
    // set xAxis data for send:
    if (xReading == 0) {
      // center:
      xSendAxisData = 0;
    } else if (abs(xReading) <= pointerSlowMoveRange) {
      // slow:
      CurrentMillis = millis();
      xSendAxisData = pointerSlowMove(xReading, xPreviousMillis);
      if(xSendAxisData != 0) {
        xPreviousMillis = CurrentMillis;
      }
    } else {
      xSendAxisData = (xReading / abs(xReading)) * (abs(xReading) - pointerSlowMoveRange);
    }
    // set yAxis data for send:
    if (yReading == 0) {
      // center:
      ySendAxisData = 0;
    } else if (abs(yReading) <= pointerSlowMoveRange) {
      // slow:
      CurrentMillis = millis();
      ySendAxisData = pointerSlowMove(yReading, yPreviousMillis);
      if(ySendAxisData != 0) {
        yPreviousMillis = CurrentMillis;
      }
    } else {
      ySendAxisData = (yReading / abs(yReading)) * (abs(yReading) - pointerSlowMoveRange);
    }

  } else {
    // scroll mode:
    xSendAxisData = 0;
    ySendAxisData = 0;

    if (yReading == 0) {
      // center:
      sendWheelData = 0;
    } else {
      // scroll up and down:
      CurrentMillis = millis();
      int wheelMoveDirection = yReading / abs(yReading);
      if (CurrentMillis - wheelPreviousMillis < (scrollInterval / abs(yReading)) ) {
        sendWheelData = 0;
      } else {
        if (wheelMoveDirection < 0) {
          // scroll up:
          sendWheelData = scrollWheelUp;
        } else {
          //  scroll down:
          sendWheelData = scrollWheelDown;
        }
      }
      if(sendWheelData != 0) {
        wheelPreviousMillis = CurrentMillis;
      }
    }
  }



// set mouse data for sending to USB:

  // if the mouse control state is active, move the mouse:
  if (mouseIsActive) {

    Mouse.move(xSendAxisData, ySendAxisData, sendWheelData);
    // read the mouse button and click or not click:
    readMouseButton(mouseLeftButton, MOUSE_LEFT);
    readMouseButton(mouseRightButton, MOUSE_RIGHT);
//  readMouseButton(mouseMiddleButton, MOUSE_MIDDLE);
    
    
/*
    // print the results to the serial monitor:
    Serial.print("inputX = ");
    Serial.print(xReading);
    Serial.print("\t inputY = ");
    Serial.print(yReading);

    Serial.print("\t outputX = ");
    Serial.print(xSendAxisData);
    Serial.print("\t outputY = ");
    Serial.print(ySendAxisData);
    Serial.print("\t outputWheel = ");
    Serial.print(sendWheelData);

    Serial.print("\n");
 */
    
  }
  delay(responseDelay);
}


/*
 read axis data:
 */

int readAxis(int thisAxis, int analogInMin, int analogInMax) {
  // read the analog input:
  int analogInRange = analogInMax - analogInMin;
  int center = (analogInRange / 2) + analogInMin;
  
  int reading = analogRead(thisAxis);
  reading = constrain(reading, analogInMin, analogInMax);
  
  int distance = reading - center;
//int threshold = analogInRange / range;        // resting threshold
  // if the output reading is outside from the rest position threshold,  use it:
  if (abs(distance) < threshold) {
    distance = 0;
  } else {
    // map the reading from the analog input range to the output range:
    distance = map(reading, analogInMin, analogInMax, 0, range) - (range / 2);
  }
  // the Y axis needs to be inverted in order to
  // map the movemment correctly:
  if (thisAxis == yAxis) {
    distance = -distance;
  }
  return distance;  // return the distance for this axis:
}


/*
 cursor move low speed:
 */

int pointerSlowMove(int reading, unsigned long previousMillis) {
  unsigned long currentMillis = millis();
  if (reading == 0) {
    return 0;
  } else {
    int interval = pointerMoveInterval / abs(reading);
    int pointerMoveDirection = reading / abs(reading);
    if (currentMillis - previousMillis < interval) {
      return 0;
    } else {
      return pointerMoveDirection;
    }
  }
}


/*
 read the mouse button and click or not click:
 */

int readMouseButton(int mouseInputPin, int mouseButton) {
  // if the mouse button is pressed:
  if (digitalRead(mouseInputPin) == LOW) {
    // if the mouse is not pressed, press it:
    if (!Mouse.isPressed(mouseButton)) {
      Mouse.press(mouseButton);
    }
  }
  // else the mouse button is not pressed:
  else {
    // if the mouse is pressed, release it:
    if (Mouse.isPressed(mouseButton)) {
      Mouse.release(mouseButton);
    }
  }
}

こちらのほうが、使いやすいですかね。

 

というわけで、とりあえず、Arduino Micro と、スライド式のジョイスティックパーツを使っての、USBスライドパッドマウス(スクロールホイール機能付き)が出来上がりました。
動作させるソフトを自分で自由にいじれるので、こんな具合に自分が使いやすい、またはみんなが使いやすいデバイスに仕立てることが出来るところが、自作の良いところですね。

各パラメータ類は、できる限り変数にしてありますので、使いやすいように、数値を色々変えてみてください。
また、プログラムの改造も自由に行っていただいて結構です。
なにせ、ほぼ素人の私が組んだプログラムなので、改良して、どんどん使いやすくしてやってください。(Arduinoのライセンスは遵守してくださいね)

あと「ここ変だよ」とか「こうするといいよ」とかありましたら、お気楽にコメントお願いします。

※追記
threshold の値を計算で出すのではなく、決め打ちして初期設定で定義するように、上のスケッチ変更しました。

int threshold = 30;

としてみましたが、これは私の手持ちの部品に合わせた値なので、使っている部品によって、指を離しても動いてしまうようなら値をより大きく、中立位置から指を動かしていって動き出しが遅いようなら値を小さくしてみてください。


さて、では次は、キーボード部分です。

追記:2017/02/06
Arduinoの団体がふたつに分裂していたことを以前書きましたが、この2団体が和解して統合するそうです。
それに伴ってか、https://www.arduino.cc/http://www.arduino.org/ の両サイトで配布されているArduino IDE が、共に 1.8.x に統一?されました。

そこで、1.8.x に合わせるのと同時に若干の変更をして、スケッチを書き直してみました。
1.8.x は 1.6.x の後半のバージョンと同じく、USB HID のライブラリの分離、具体的には、Keyboard.h Mouse.h を必要な時にインクルードするようになっています。

これまでのスケッチから変更して、スライドパッドの中心と、そこからの最大距離を、変数に指定します。あと変数の名前をいくつか変えました。
変数の初期値は各自で変更してください。

/*
2017/02/07 by HisashiKato 
 */

#include "Mouse.h"

// set pin numbers for switch, joystick axes, and LED:
const int switchPin = 2;      // switch to turn on and off mouse control

const int mouseLeftButton   = 3;      // input pin for the mouse LEFT pushButton
const int mouseRightButton  = 4;      // input pin for the mouse RIGHT pushButton
//const int mouseMiddleButton = 5;    // input pin for the mouse MIDDLE pushButton
const int mouseScrollModeButton = 5;  // input pin for the mouse mode change pushButton

const int xAxis = A0;         // joystick X axis  
const int yAxis = A1;         // joystick Y axis

const int ledPin = 13;        // Mouse control LED

// send data when mouse wheel is move:
const int scrollWheelStop = 0x00;
const int scrollWheelUp   = 0x01;
const int scrollWheelDown = 0xFF;

// interval time for slow move:
const int pointerMoveInterval = 20;
const int scrollInterval = 500;

// parameters for reading the joystick:

int xAnalogInCenter = 520;
int yAnalogInCenter = 505;
int xAnalogMaxRange = 240;
int yAnalogMaxRange = 240;

int threshold = 30;          // resting threshold


int xPointerMoveDirection = 0;
int yPointerMoveDirection = 0;

int pointerMoveMaxValue = 2;
int pointerSlowMoveRange = 8;
int division = (pointerMoveMaxValue + pointerSlowMoveRange) * 2;  // division number

int responseDelay = 5;                    // response delay of the mouse, in ms

int xSendAxisData = 0;
int ySendAxisData = 0;
int sendWheelData = 0;

// timer:
// unsigned long currentMillis;
unsigned long CurrentMillis;
unsigned long xPreviousMillis = 0;        // will store last time mouse move data was updated
unsigned long yPreviousMillis = 0;        // will store last time mouse move data was updated
unsigned long wheelPreviousMillis = 0;

// mouse active switch:
boolean mouseIsActive = false;    // whether or not to control the mouse
int lastSwitchState = HIGH;       // previous switch state
// mouse mode change switch:
boolean scrollMode = false;
int lastModeSwitchState = HIGH;   // previous switch state


void setup() {
  pinMode(switchPin, INPUT_PULLUP);            // the switch pin
  pinMode(ledPin, OUTPUT);                     // the LED pin
  pinMode(mouseLeftButton, INPUT_PULLUP);      // the mouse Left Button pin
  pinMode(mouseRightButton, INPUT_PULLUP);     // the mouse Right Button pin
//pinMode(mouseMiddleButton, INPUT_PULLUP);    // the mouse Middle Button pin
  pinMode(mouseScrollModeButton, INPUT_PULLUP);     // the mouse mode change Button pin

  
  // take control of the mouse:
  Mouse.begin();
  
  // initialize serial communications at 9600 bps:
//Serial.begin(9600);

}

void loop(){
  // read the switch:
  int switchState = digitalRead(switchPin);
  // if it's changed and it's high, toggle the mouse state:
  if (switchState != lastSwitchState) {
    if (switchState == LOW) {
      mouseIsActive = !mouseIsActive;
      // turn on LED to indicate mouse state:
      digitalWrite(ledPin, mouseIsActive);
    }
  }
  // save switch state for next comparison:
  lastSwitchState = switchState;


  // read for I/O:
  int xReading = readAxis(xAxis, xAnalogInCenter, xAnalogMaxRange);
  int yReading = readAxis(yAxis, yAnalogInCenter, yAnalogMaxRange);

  // read the mouseModeSwitch:
  int modeSwitchState = digitalRead(mouseScrollModeButton);
  if (modeSwitchState == LOW) {
    scrollMode = true;
  } else {
    scrollMode = false;
  }


  if (scrollMode == false) {
    // pointer mode:
    sendWheelData = scrollWheelStop;
    
    // set xAxis data for send:
    if (xReading == 0) {
      // center:
      xSendAxisData = 0;
    } else if (abs(xReading) <= pointerSlowMoveRange) {
      // slow:
      CurrentMillis = millis();
      xSendAxisData = pointerSlowMove(xReading, xPreviousMillis);
      if(xSendAxisData != 0) {
        xPreviousMillis = CurrentMillis;
      }
    } else {
      xSendAxisData = (xReading / abs(xReading)) * (abs(xReading) - pointerSlowMoveRange);
    }
    // set yAxis data for send:
    if (yReading == 0) {
      // center:
      ySendAxisData = 0;
    } else if (abs(yReading) <= pointerSlowMoveRange) {
      // slow:
      CurrentMillis = millis();
      ySendAxisData = pointerSlowMove(yReading, yPreviousMillis);
      if(ySendAxisData != 0) {
        yPreviousMillis = CurrentMillis;
      }
    } else {
      ySendAxisData = (yReading / abs(yReading)) * (abs(yReading) - pointerSlowMoveRange);
    }

  } else {
    // scroll mode:
    xSendAxisData = 0;
    ySendAxisData = 0;

    if (yReading == 0) {
      // center:
      sendWheelData = 0;
    } else {
      // scroll up and down:
      CurrentMillis = millis();
      int wheelMoveDirection = yReading / abs(yReading);
      if (CurrentMillis - wheelPreviousMillis < (scrollInterval / abs(yReading)) ) {
        sendWheelData = 0;
      } else {
        if (wheelMoveDirection < 0) {
          // scroll up:
          sendWheelData = scrollWheelUp;
        } else {
          //  scroll down:
          sendWheelData = scrollWheelDown;
        }
      }
      if(sendWheelData != 0) {
        wheelPreviousMillis = CurrentMillis;
      }
    }
  }



// set mouse data for sending to USB:

  // if the mouse control state is active, move the mouse:
  if (mouseIsActive) {

    Mouse.move(xSendAxisData, ySendAxisData, sendWheelData);
    // read the mouse button and click or not click:
    readMouseButton(mouseLeftButton, MOUSE_LEFT);
    readMouseButton(mouseRightButton, MOUSE_RIGHT);
//  readMouseButton(mouseMiddleButton, MOUSE_MIDDLE);
    
/*    
    // print the results to the serial monitor:
    Serial.print("inputX = ");
    Serial.print(xReading);
    Serial.print("\t inputY = ");
    Serial.print(yReading);

    Serial.print("\t outputX = ");
    Serial.print(xSendAxisData);
    Serial.print("\t outputY = ");
    Serial.print(ySendAxisData);
    Serial.print("\t outputWheel = ");
    Serial.print(sendWheelData);

    Serial.print("\n");
 */
    
  }
//delay(responseDelay);
}


/*
 read axis data:
 */

int readAxis(int thisAxis, int center, int maxRange) {
  // read the analog input:
  int analogInMin = center - maxRange;
  int analogInMax = center + maxRange;
  int reading = analogRead(thisAxis);
  reading = constrain(reading, analogInMin, analogInMax);
  int distance = reading - center;
  
  // if the output reading is outside from the rest position threshold,  use it:
  if (abs(distance) < threshold) {
    distance = 0;
  } else {
    // map the reading from the analog input range to the output range:
    distance = map(reading, analogInMin, analogInMax, 0, division) - (division / 2);
  }
  // the Y axis needs to be inverted in order to
  // map the movemment correctly:
  if (thisAxis == yAxis) {
    distance = -distance;
  }
  return distance;  // return the distance for this axis:
}


/*
 cursor move low speed:
 */

int pointerSlowMove(int reading, unsigned long previousMillis) {
  unsigned long currentMillis = millis();
  if (reading == 0) {
    return 0;
  } else {
    int interval = pointerMoveInterval / abs(reading);
    int pointerMoveDirection = reading / abs(reading);
    if (currentMillis - previousMillis < interval) {
      return 0;
    } else {
      return pointerMoveDirection;
    }
  }
}


/*
 read the mouse button and click or not click:
 */

int readMouseButton(int mouseInputPin, int mouseButton) {
  // if the mouse button is pressed:
  if (digitalRead(mouseInputPin) == LOW) {
    // if the mouse is not pressed, press it:
    if (!Mouse.isPressed(mouseButton)) {
      Mouse.press(mouseButton);
    }
  }
  // else the mouse button is not pressed:
  else {
    // if the mouse is pressed, release it:
    if (Mouse.isPressed(mouseButton)) {
      Mouse.release(mouseButton);
    }
  }
}

 

こんな感じで。

 

おまけ:Arduino IDE で困った話

今まで Arduino IDE の 1.7.x を使っていたのですが、Arduino 分裂解消ということで、せっかくなので、最新の 1.8.1 に上げてみました。
そしたら、コンパイル時に、「 error: 'USB_EP_SIZE' was not declared in this scope 」とエラーが出て、なんじゃこりゃ!?に。
USBAPI.h を覗いてみたら、ちゃんと宣言されてるし。いろいろ試して以下のようなことに。
・USB HID とかかんけーない、スケッチ例の Blink でさえ、コンパイルエラーが出る。
・ボードで、チップ本体にUSBを積んでない UNO を選ぶとエラーは出ない、Leonardo,Micro でエラー出る。
・1.7.x をインストールしなおすと、エラーは出ない、1.8.1 と 1.6.x をインストールすると駄目。
・別のPCに、1.8.1 を入れてみたら、そっちではエラーは出ずに正常にコンパイル終了した。
てな感じ。
テンポラリとか探しまくって消してみたけど解決せず。
こりゃアンインストールしても、どっかに何かが残留してて、何か悪さしてるのかなー、最悪、メインPCの Windows10 をクリーンインストールかぁ・・・?
などと思っていたのですが、ふと、Arduino の Wiki に書かれていた、「Arduino IDE は Javaアプリケーションだよ」というのを思い出しまして、うちのメインPCでJava関係だと、Java本体と、AndroidStudio が入れてあったなーと。
試しに、Java と AndroidStudio をアンインストールして、再インストール。
そして、 Arduino IDE 1.8.1 をインストールしてみたところ、エラー出なくなりました!!!
何が起きてたんでしょうねぇ何かが不具合を起こしてたんでしょうね、とにかく正常になって良かった。

おしまい 

| | コメント (1) | トラックバック (0)

2015年6月25日 (木)

USBキーボードは自作できるのか下調べをしてみる

HP200LXのキーボードの打ち心地がいまだに忘れられなくて、

HP200LXのキーボード部分の構造を用いた(模倣した、継承した)USBの小型キーボードが欲しい!、と前に書きましたが(過去のblog記事:こんな小型キーボードが欲しい!)、自分好みの物が市販されていない以上、無いものは自分で作る、欲しいものは自分で作る、の精神で行くと、まずは自作してみるしかない、という方向に向かうことになります。

そこで、もし、自分が欲しいと思っているキーボードを自作するとしたら、何が必要なのか、を、ちょっくら調べてみました。

基本的には関連するキーワードでググりまくって、情報を集めて行きます。
はっきりいって、私、素人です。
かなーりてきとーですがご容赦ください。

まず、USBキーボードは自作できるのかという問題。
USB キーボード 自作」で検索するだけで、かなりの数が出てきます。
ちゃんと完成させている人が多いことから、どうやら自作は可能なようです。

次は部品、特にキーボードの中枢になるワンチップマイコン。
(マイコンと言えばむかしのPC-8001やFM-7やMZシリーズを思い出すおっさんです。自分はPC-6001を所有していてベーマガのBASICのリストを手打ちしてゲームを遊んでたりしてました)

市販のキーボードをバラして、中の基板を流用することも考えましたが、私が今欲している小型キーボードの場合、流用しようにも「自分が必要とするキーが付いていない」「自分があまり必要としないキーをFnキーとの同時押しに逃がしたい」等々の制約があり、それらキー配置などが完全に合致する市販キーボードが皆無なことから、既成のキーボードの基板の流用は見送ることにしました。
(標準的な配列で標準的な大きさのキーボードを自作するのなら、市販のキーボードの中身を流用するのが一番手っ取り早いです)

ワンチップマイコンは昔と比べてとても便利で扱いやすくなってたんですね。
汎用I/O内蔵でピンの入出力を自由に設定可能とかA/Dコンバータ内蔵とかPWM出力可能とか、良い時代になりました。

今の電子工作で使うマイコンチップは、どうやら「PIC」と「AVR」あたりが基本のようですね。

今回、USBキーボードやUSBマウスの機能を実現させるために、チップ内にUSB機能が内蔵されているほうがよろしい。
そうなると、PICだと「PIC18F2550」「PIC18F4550」や、AVRだと「ATmega32U4」 あたり?
USBキーボードやマウスなどのUSBの入力機器では「USB HID」という規格がありまして、
USB付きのPICもAVRも、どちらもUSB HIDのライブラリがあるっぽいので、ソフトを組む労力が軽減されそうです。

そんでもって、今、考えているのは、AVRを用いたオープンソースハードウェアのマイコンボード「Arduino」シリーズで、USB搭載の「ATmega32U4」を積んだタイプを使おうかと。
PICはプログラムの書き込みに専用のライターが必要になりますが、「Arduino」ならPCとUSBケーブルで直繋ぎでプログラムの書き込みが行えること(これ、けっこう重要というか手探りで作るのにとても便利)と、今現在は「Arduino」のほうが流行ってるっぽいので、新しい情報が手に入りやすそうなこと、「Arduino」の統合開発環境「Arduino IDE」に「USB HID」の関数群が既に有り、「Arduino」の本家 http://www.arduino.cc/解説したページまであるため、ほぼ初心者の私でもハードルが低そう、などを決め手にしました。

Arduino ATmega32U4」で検索すると「Arduino Micro」や「ダ・ヴィンチ32U」が出てきました、キーボードくらいの大きさの物を作るにはちょうど良さそうです。
USBまわりのソフトウェアに関しては、さらに情報を集めるために、同じくATmega32U4を使用している「Arduino Leonardo」も参考になるので、「Arduino Leonardo USB HID」あたりの文字列でも検索をかけるとよろしいかと。
スイッチサイエンスさんの「Arduino Leonardoへのガイド」にUSB内蔵のArduinoの注意が書かれています。

続いて、これに繋ぐ物理的なキースイッチとマウスポインタですが、「Arduino」の本家のページ http://www.arduino.cc/ の 「Mouse and Keyboard libraries」に関数の詳細、Examplesに簡単な接続方法とサンプルプログラム(スケッチって言うんでしたっけ?)が掲載されています。至れり尽くせり。

ポインティングデバイスですが、アナログ値をA/Dで読みとるのであればX-Yの座標を抵抗値の変化で示す部品「アナログジョイスティック 部品」で出てくるパーツが直付けできそうですね。
トラックボールはパルス出力でしょうから、その信号形式などを読み取って数値に変換してやるプログラムを追加してやれば良さそうです。
超小型トラックボールモジュール」で検索すると出てくる、小型基板にひっついたトラックボールは、ブラックベリーで使われていたトラックボールと同じ仕組みのホール素子(磁気センサ)を使った物です。この超小型トラックボールのパーツはどうやら販売終了っぽいのですが後続品があります。
実は調べていたら、Macintoshの「マイティマウス」のスクロールボールが、どうやら同じ仕組みっぽいので、中古を購入してバラして流用してみるのも手かもしれません。

肝心のキーボード部分ですが、今回の私が欲しいと思っているキーボードでは80keyもありまして、I/Oポートが足りません。Arduino はI/Oが20pin。

キーボードのスイッチ入力には古くから「キーマトリクス」という手法があって、これを使用します。
キースキャンの出力側に汎用ロジックICのデコーダ 3 to 8 line decoder74HC138を使えば使用するピン数を減らせそうです(出来る?) HCはCMOS、74HC138」は出力が負論理(ローアクティブ Active Low)74HC238なら出力が正論理(ハイアクティブ)
負論理の74HC138は使えないっぽい?!試しに74HC138で回路を組んでみたら、同じライン上のキー同時押しが読めかった!(多分、電流逆流防止(電流の引き込み抑制)用のダイオードを入れれば正常に動く、かも?)
正論理の74HC238に交換したら、正常に動きました。ほっとひといき。
DIP(ゲジゲジ)もSOP(ひらべったい)もあるのでよさげです。
これでも足りなければ、4-16 デコーダの、74HC154で。
74HC154は入手困難っぽいです。
74HC138を複数使えばピン数を増やせるけれど、チップが増えちゃうのは困りもの、でも無い袖は振れない。
他の人はシフトレジスタを使ってるので74HC16474HC595を連結するのがいいのかも。データ線とクロックだけで済むし。使用例も多いし。オススメかも。

問題なのは誤入力防止のダイオードとキーマトリクスの配置。マトリクスの配置には何か法則性があるのだろうかと調べてみたけれど分かりませんでした。

なぜダイオードが必要なのかは「キーマトリクス ダイオード」で検索すると出てきます。
押されていないキーが押されていると認識されてしまうゴーストキーの対策。

なんか、ダイオードを入れずにマトリクス上のキーの配置と読み込みルーチンを工夫して対処しているような雰囲気も・・・
このへんの配線は要検討というかもっと調べるか作りながら決めていくかしかなさそうです。

あと、キーボードに「Num Lock」「Caps Lock」「Scroll Lock」のLEDを付けたいのですが、これらのLEDは実はPC本体からの信号で点灯してたりします。
この機能をArduinoに行わせる方法ですが「Arduino HID KEYBOARD LED」で検索して出てくる
Topic: Leonardo keyboard leds emulation? - Arduino Forum」でパッチを当てた人が居るので参考に。

あとは、Arduinoに実装される USB HID は、ちょっと特殊というお話を ksmakotoさんが「Arduino leonardoはUSB機器としてちょっと特殊だ、という話」で書いていらっしゃるので、一度読んでおくとよろしいかと。
USB HID 複合デバイス arduino」も検索して、現在色々と勉強中です。

※素の状態だと「Boot Interface」として使用できない(システムが起動するまで使えない?)ような感じ?もしBIOS等でも使えるような素直なキーボード&マウスにするためには書き換え必須な雰囲気?それともなんか大丈夫っぽい?USBの仕様書をもっと読まないと。えいごめんどくさい

USBキーボードが送信するキーコードは「Universal Serial Bus HID Usage Tables」のp53からと、「USBキーボードのキーコード」に記載があります。

Universal Serial Bus HID Usage Tables」で日本語キーボードに特有のキーを表している p60 の Footnotes 15-20 の表が、最初は?だったのですが、Usage Name の右肩の数字と対応してるとやっと気がつきました(以下の追加の脚注を見ろや!って書いてあるよ・・・ちゃんと読まにゃ)。DOS/V-109 の「¥」のキーのNote17は、Keyboard International 3(17) の「17」、つまりキーコードは89。
確かに「USBキーボードのキーコード」で「¥」は Keyboard International 3 でキーコードは 07 / 89 (07はキーボード)になってます。
日本語キーボードを作る方は参考にしてください。
ちなみにArduinoの素のUSB HID ライブラリではキーコードが101までしか送信できないそうで。101より先のキーコードに割り当てられている「¥」などを送信するには、HID.cpp の HID report descriptor を書き換える必要があります。

Arduino IDE」をインストールした場所の Arduino\hardware\arduino\avr\cores\arduino の中にある「HID.cpp」と「USBAPI.h」を書き換えると、USB関係の機能追加や変更が出来るっぽいです。「HID.cpp」と「USBAPI.h」の素のソースと、機能の追加で変更後のソースを読むと、何やってるのか分かります

例えばですが、「HID.cpp」の素のソースを覗くと、「Keyboard.press()」関数の、Shiftキーが押された状態で入力されるであろう文字の処理や、USB HIDキーボードのReportで特別扱いになっている修飾キー(modifier Key :左右のCTRL,SHIFT,ALT,GUIキー)の処理とかが書いてあります。

Arduino IDE の 1.6.x の後半と、1.8.x では、Arduino\liblaries\ の Keyboard と Mouse に、ライブラリの形で分離されています。src の中の、*.h と *.cpp を読んでみてください。

USBのHIDデバイスの解説は「USB.org」の「HID Information」にあります。
Device Class Definition for HID 1.11
HID Usage Tables 1.12

 

なんとなく、回路構成は、こんな感じになりそうな予感。
まだ全くの仮です。組んでみないと動くかわかんないし。
(クリックで拡大します)

Photo

追記:2015/10/29
ネットを検索して見て回っていたところ、次のファイルを見つけました。

USB and PS/2 Multimedia Keyboard Interface Designer - Reference Manual
http://cache.freescale.com/files/microcontrollers/doc/ref_manual/DRM014.pdf

これは、 Freescale社の 68HC08 というマイコンチップを使用してPS/2 & USBキーボードを構成するための、リファレンスボードのマニュアルです。
これには、USBキーボードとして動作させるための回路構成、具体的なキーマトリクス、フローチャート、マルチメディアキーのUSBでの扱い、等が書かれています。

※これに記載されているキーボードマトリクスなのですが、私の手元にあって分解した無名メーカー製のUSBキーボードのキーマトリクスとかなり似通っていました。全く異なる2個をバラしたのですがその2個が全く同一のマトリクスで驚きました(但しメンブレンシートの配線のパターン図は異なってました)
もしかしたらですが、今出回っているUSBキーボードに使われているいくつかのマイコンチップとそれのファームウェアは、このリファレンスの68HC08とソースを使っているか、またはセカンドソースか、互換品か、または他のチップに置き換えたコピーだったりするのかもしれませんね。あくまで想像ですけれど。
今のほとんどのUSBキーボードの中の制御チップは、チップ表面の刻印が無くて不明だったり、COB実装(Chip on board、半導体チップをそのまま基板に載せて配線して上から黒い樹脂をかぶせちゃう)なので、中身までは分からないのですよね。

このリファレンスボードの、ファイルの参照元になるページはどうやら無くなっているようなのですが、

HC08K: 8-bit USB K MCUs
http://www.freescale.com/products/more-processors/8-bit-mcus/hc08/8-bit-usb-k-mcus:HC08K

の、Software & Tools に、

USB Hub Keyboard Reference Design
http://www.freescale.com/products/more-processors/8-bit-mcus/hc08/usb-hub-keyboard-reference-design:RD68HC08USBHKEYBD

を見つけました。どうやらこちらが現行のページっぽいです。
これは、USBのHUB及びUSBキーボードのリファレンスと開発ボードで、USBキーボードの部分は上記 DRM014.pdf の内容が含まれています。

Documentation に、リファレンスマニュアルのPDFがあります。
USB Hub Keyboard for the MC68HC08KH12 Designer - Reference Manual
http://cache.freescale.com/files/microcontrollers/doc/ref_manual/DRM015.pdf

そして、Software & Tools からは、回路図とソースファイル一式がダウンロード可能になっています。

やった!ソースファイルが手に入った!、これで具体的な処理内容が分かる、特に不明だったゴーストキーの対処法が分かるぞ!、と思ったのですが、

プログラムはアセンブリ言語で書かれていました。当然ですが。

68HC08 のアセンブリ言語なんて読んだことないし(Z80なら学生の頃にかじったのですが)ニーモニックやらレジスタやらアドレスやら定義されているラベルやら訳分からん状態です。

解読するしか無いかなぁ、、、68HC08って、どうやらMC6800 の系統らしいのですが、、、

追々、調べますか。

以上、追記終わり。

 

さて、今後、色々そろって、いざハードの実験や試作をするとなると、キースイッチを用意しないといけないのですよね。
私が目指しているキーボードは小型の物なので、とりあえずの試作は市販のタクトスイッチでも使いますか。
問題は大きさ、想定しているキーピッチが1cm X 1cm なので2.54ピッチのユニバーサル基板だとズレちゃうんですよ。
なので2mmピッチのユニバーサル基板を買わないと。
ガラスエポキシの基板って堅くって、切るのしんどいんですよね・・・

最終的にはバラの部品を乗っけられる専用の基板を作り起こすことになりそうです。
基板 試作」で検索すると企業向けだけでなく個人向けにも小ロットで作ってくれる会社があるみたいです。

これで回路周りは作れそうなのですが、一番の問題はキートップ!!!
キートップとその構造は、HP200LXのキーボードの要とも言えます。
ここを再現してみないことにはどうにもなりませんし始まりません。
3DCADでデータを起こして、3Dプリンタで試作品を出力するのが順当でしょうが、
私、2次元の機械図面しか引いたことがないのです、私が学校に通ってた頃は、まだCADは普及していませんでした。
なので紙の上にえんぴつで機械図面を引くのは出来るのですが(成績そこそこ良かったんよ)CADは全く触っていないのです。
フリーソフトの3DCADを入手はしてあるので、早く使えるようにならないと。
数ヶ月前に10年使ったマザーボードとCPUを買い換えたので、やっと普通の速度でソフトが動くようになったはずなので。

あぁ、はよやらにゃ。

追記:とりあえず作り初めてみました。
Arduino Micro を使って、試しにUSB スライドパッド マウスを作ってみた(小型USBキーボード自作のための準備色々)
ネットの情報をかき集めるだけで、かなーり簡単にポインティングデバイスが自作出来ます(出来ました)。一応普通に動作してます使えます。
皆さんの参考になれば。
Pict0007

 
追記:2016/02/01
今、出来てる、キーボード部分の回路とソフトをテストするための機材一式。
キーマトリクス配線をしたタクトスイッチ基板。
 
20160122pict0003
 
74HC138x2を使ったのですが、動かすソフトのお試し版を作って、そのバグ取りが終わって動かしたら、電気的に一部が動いてないことが判明。(具体的には同一ライン上のキーの複数押しを認識しない、けれどそこ以外はちゃんと動いていて、一応キー入力出来てます
「あーそうか、そーゆーことなのか?」という思い当たる部分があるので、今、別の汎用ロジックICをネット通販で注文中。
届いたら交換して再検証します。

追記:2016/02/10
部品交換で一応正常に動いたので、暫定ですが現状をブログに書きました。
Arduino Micro を使って、USB 小型キーボードを自作(製作途中、暫定公開)
今のところ普通に動いてるっぽいです。
是非、上記キーボードのページを覗いていってくださいませ。

こちらもネットの情報を集めるだけで、かなーり簡単に、普通に動くUSBキーボードが自作出来ます(出来ました)。
Arduino様々です。電子機器DIYにとって良い時代になりました。
皆さんの参考になれば。
 

2016/06/18 現在、停滞している作業
・検証用プラ製キートップの手作り
・キーボード、マウスとして、実際に動作している様子を動画で撮影

今後予定される、予定している、行いたい、出来ればいいな、作業
・ゴーストキー対策の実装
・NumLockの実装
・Arduinoの標準ライブラリの改変
 ・日本語キーボード特有のキーの有効化
 ・キーボードLEDの機能追加
 ・マルチメディアキーの実装
・キーバックライト用のLED点灯まわりの電子回路の検討、実験
・ATmega32U4(TQFP)と74HC164(SOP)などを用いて、キーボード自作専用のArduino下位互換基板の製作
 ・回路図CADの操作習得と、回路図の描画
  ・動作試作回路の製作(手半田で)
 ・回路基板CADの操作習得と、基板のデザイン製作
 ・※実際に試作基板を発注するところまで行けるかは不透明
・Arduinoの標準ライブラリの、さらなる改変
(Arduinoはかなりの電気食い。USBのConfigurationDescriptorでMaxPowerが500mAと定義されていて、なおかつ実際に消費電力が多いためにモバイルに不向き)
(Arduinoには省電力のためのライブラリ等もあるらしいので、ソフト的にもハード的にも電力を出来るだけ抑えたい)
・3Dモデリングソフトの操作習得と、樹脂製キートップのモデリングおよび試作の3Dプリント

とりあえず思いついているのはこのくらい。
以下、完全に未定

 

| | コメント (0) | トラックバック (0)

2014年12月 2日 (火)

ガンプラHG「G-アルケイン」を差し替えパーツ無し変形(完全変形?)に改造してみた

富野由悠季監督の新作アニメ「ガンダム Gのレコンギスタ」のプラモデル(ガンプラ)

「1/144 HG ガンダム G-アルケイン」

を発売日に購入して、ぱちぱちとパチ組み(説明書通りに組み立てただけ)してみました。
このキット、組んでみて
「なるほどーこうしたかー」と感心するところと、
「ちょっとここちがうでしょ、なんでこうしちゃったの?」な部分が混在していて、
組み立てている間、複雑な気分になりました。
デザイン画から拾えていないラインが多いんですよ。面構成が違ってたり。
デザイナーが意図する部分に関節の軸位置が来てない足首関節とか。
あちこち出来がいいだけにちょっと残念。

でも、良く出来ています。満足です。
(画像クリックで拡大します)

G_a_01

外形をいじろうとすると、あちらこちら大工事になってしまって、いつもの完成しない病に陥りそうだったので、やめました。

その代わり、キットではパーツ差し替えでの変形だったので、差し替えパーツ無しでの変形に挑戦することにしました。そのほうが楽しそうだったので。

まずはリアアーマーのスカートに着目。
ここなのですが、とっても可働しそうなパーツ構成とパーツ形状をしているにもかかわらず、MS形態で全く可動しないのですよ。

そこで、干渉する部分を削ってやって、可動するようにしてみました。

G_a_02

デザインナイフ(オルファのアートナイフ)でカリカリ削って、軸の動きを瞬間接着剤で少し渋めにしただけです。

G_a_03

このようにスカート周りの変形を再現することが出来ました。
ここで気づいたこと、

G_a_04

機首のパーツとスカートのパーツが綺麗にかみ合ったのです、この辺りは設計の良さを感じました、感心しました。

続いて問題の、差し替えパーツを使う股関節の変形。

G_a_05

股関節軸を移動させないといけないのですが、距離がある、遠い!
この移動量をどうやって稼ぐかが難しい。
本当ならば、色々と構想を練って、図面を引いたり試作を何度もやるなどしないといけないところなのですが、
今回は、作例とかコンテストに出したりとかではなくて、完全に遊びなので、
あーだこーだー考えながら行き当たりばったりで、とにかくそれっぽくなればいいや、な自己満足を優先して、作ることにしました。
試行錯誤しながらどんどん形にしていくのが楽しいので、難しいことは考えずに勢いだけでどんどん手を動かしました。

んで、とにかく形になったのがこれです。
土曜日に買って日曜日の夜にこんな感じになりました。

G_a_06

差し替えパーツ使用時とは脚の位置がちょっとずれてしまっているのですが、程よく収まったので、これで良しとしました。

G_a_07

こんな感じで動きます。股関節軸の移動距離を稼ぐために、無駄に複雑怪奇です。
行き当たりばったりです。
ちゃんと考えて煮詰めればもっとシンプルに出来ると思います。
今回はとにかく変形させたかったのでこれで良しです。
 
追記:変形後に股関節パーツが緩くてぷらんぷらんだったので、応急処置でロックするパーツを追加。ついでに股関節を左右に1mmずつ広げました。

G_a_16

G_a_08

G_a_09

G_a_10

後ろから見ると大変なことに・・・・

今回の変形機構だと、サイドスカートがそのままなので、変形後に腕が平行になりません。
とある画像掲示板の某模型裏ではスカートを内側に逃がす機構を入れていた人が居たので、やってみるといいかもしれません。
私は面倒なのでもうこのままで。

腕が平行にならないので腕にライフルとシールドを付けると内側を向いてしまいます。
ですのでライフルとシールドの軸接続基部にポリキャップとポリボールジョイントを入れてみました。

G_a_11

無事平行に。

G_a_12

G_a_13

G_a_14

G_a_15

ライフルとシールドを付けたら、めっちゃかっこいい。

とっても満足、楽しかったです。

(けれど、アニメでは結局、一度も変形が無かったのは、作った身としてはやっぱりちょっとさみしい)

| | コメント (0) | トラックバック (0)

2014年11月30日 (日)

こんな小型キーボードが欲しい!

20年前「HP200LX」という小型の情報端末が、電子小物好きの間で深く広く愛されていました。
Hp200lx01

こちらに詳しい解説があります↓
ユーザーが育て愛した手のひらサイズのPC - YHP「HP-200LX」
http://news.mynavi.jp/column/history/007/

私もその前進のHP100LXと、HP200LXを購入して、仕事や実生活で活用していました。

今は液晶が劣化してしまい、予備の液晶パネルを持ってはいるものの、交換が面倒なので修理せず使っていませんが予備として保存してあった液晶も、経年劣化で真っ黒になってました!交換用の液晶を手に入れないことには修理不可能になってしまいました。
※修理できました。
液晶が生きていた2年前までは、テキスト打ち専用機として現役でした。
683314922

今現在、時代が変わって、HP200LXで行っていたことのほとんどは、スマートフォンなどで可能になりました。
但し、唯一、どうしてもHP200LXに敵わないところがあるのです。それがテキスト入力。

HP200LXのハードウェアQWERTYキーボードはとても良く考えられて作られていて、HP200LXを手に持ったまま親指でポチポチ入力するのに加えて、机などに置いて普通のキーボードのように両手の指で入力が出来てしまうのです。
その打ち心地は、とても良くて、私は未だにこのキーボードを超える小型のキーボードに出会えていません。

※追記
黒く変色してしまった液晶の偏光板を自分で交換することで、復活させました。
(けっこうかんたんでした)
せっかくなので、キーボードでの日本語入力の様子を動画で撮影してみました。

本体基板無改造(倍速化改造はしていません)
オカヤ・システムウェアのキットで日本語化済み
SDカード8MB使用中(PCカードタイプのSDカードアダプタ使用)
VzエディタとWX2で日本語入力
両手で持っての親指入力と、机に置いての入力

追記終わり。

 

小型の外付けの無線キーボードはそこそこ売ってるのですけれどね。
これとか
41pfkfc7fgl
http://www.amazon.co.jp/dp/B00CZCNJSQ

これとか
41zq4jutwl
http://www.amazon.co.jp/dp/B00L1HU5D8

これとか
81ddfa5wpjl_sl1500_  
http://www.amazon.co.jp/dp/B00KW3OFG0

こんなのとか
51smgusrx7l
http://www.amazon.co.jp/dp/B00ALC6DNK

最近だとこれが発売になったのですが、
4194399116880
http://www.donya.jp/item/26896.html
発売されたらすぐに売り切れました。

 

HP200LXの本体自体の用途は今のスマートフォンなどで十分ですが、キーボードだけ、HP200LXと同等かそれ以上のものが欲しいのです。
出来ることなら自分で作ってしまいたいくらいなのです。

イメージとしては、HP200LXのキーボード部分をそのまんま独立させた感じ

これ(画像クリックで拡大)
Hp200lx02
これにポインティングデバイスを追加した感じで。

Image1
(Windowsキーを右に寄せないほうがいいかな)
Image2

横幅は14cm~16cmで。15cmあたりが適正かも。
キー配列はQWERTY英語配列、キーの省略は極力避けたい。
上図は仮のキー配列。
キーピッチは10mm
キートップは7mm x 5mm
キーボードの構造はHP200LXをほぼそのまま模倣、

HP200LXのキーボードの詳しい構造は以下のページにまとめてあります。
LXのキーボードのひみつ
http://kato-h.cocolog-nifty.com/khweblog/2014/06/lx-f990.html

出来ればキーボードバックライトを付けたい。

ポインティングデバイスは
・トラックボール
・スティック式ポインティングデバイス
・光学式ポインティングデバイス
のどれか。
スクロール機能も付けたい。

端末との接続は
・有線USB接続
・専用2.4GHz無線接続
・Bluetooth接続

厚さは電池無しの有線USB接続で1cm以内。
乾電池を使う場合でも2cm以下。

机に置いても、手に持っても、使えるように。

基本はただの板状だけれど、オプションのパーツやケースを付けると、スマートフォンと一体化が出来るように。
(スライドやクラムシェル。あとシステム手帳のリフィル型とか)

と、まぁ、こんな感じのキーボードが欲しいのです。

 

ではなぜ上記の仕様にしたいのか説明を。

横幅ですが、HP200LXの外形寸法が160mm x 86.4mm x 25.4mmなので、最大幅160mmでも良いところですが、auのIS01の横幅が149mmだったので。

キー配列は英語配列なのは、日本語配列だとキー数が増えるので。
あと、繋ぐハードウェア側が日本語配列に対応していない場合がちょこちょこあるので。
LXのキーボードと同じ配列にせずに、一般的なキー配列にする理由は、LXのキー配列はLX用に特化されているので(内蔵アプリケーションの起動キーがあったりカーソルキーの使用頻度か高いので両手で持った時に親指が来る右上の部分にカーソルキーが配置されていたり)
ShiftとCtrlとAltは右側にも欲しい。
Windowsキーとアプリケーションキーは必要ないかもしれませんが、例えばAndroid端末に繋いだUSBキーボードのアプリケーションキーを押すとメニューが開いたりとかするので、個人的には付けときたいのです。

キーボードの構造はHP200LXと同じにしてしまって、樹脂キートップとドームシートとプリント基板によるクリックのあるスイッチに。
Hp200lx03
LXではシートにパターンが引かれていましたが、
Hp200lx04
今回はキーボード単体なので、直にプリント基板の片面にパターンを引いてしまおうかと。
制御チップとか裏面でもいいですし。

ドームシートは「ドームシート スイッチ」で検索をかけるといろいろ出てきます。
40
携帯電話のテンキーとかで使われています。
メンブレンスイッチとは│キーボード│メンブレンスイッチ解説
ドームシート - 不二電子工業株式会社
メンブレンスイッチの主流-クリックエンボス

HP200LXと同じ打鍵感にしようとすると金属ドームのシート貼りよりもドーム加工して裏に導電剤塗布したPETシートのほうがいいかも。
Memcl_p3

キーボードバックライトは、キートップを光が透過する樹脂で成形して、基板表面にLEDを点在させれば良さそうです。

ポインティングデバイス
トラックボールは光学式で小型のだとこんなのがありました。
741870
http://www.donya.jp/item/10499.html

400ma018_ma
http://direct.sanwa.co.jp/ItemPage/400-MA018

品切れ

あと小型のトラックボールでは、BlackBerry(ブラックベリー)に使用されていたトラックボールがあります。

226_1
https://www.switch-science.com/catalog/226/
これ。ホール素子を使っているそうです。

そうそう、トラックボールを搭載するなら、ボールと受けの掃除がしやすくしたいですね。

スティック型のポインティングデバイスといえば、ThinkPadシリーズのトラックポイント。
むかしの日本IBMのPC110では、ほぼ同じポインティングヘッドが使われていました。
Pc110a_11


光学式でセンサーの上を指でなぞるタイプのポインティングデバイスだと、上のほうで紹介した
4194399116880_2
http://www.donya.jp/item/26896.html
がありますね。

むかしシャープからNetWalkerという情報端末が発売されたのですが、
Product_big_b
http://www.sharp.co.jp/netwalker/pcz1j/index.html
これのポインティングデバイスも光学式で、
http://www.sharp.co.jp/netwalker/pcz1j/feature/operation/index.html
当時店頭で触ったところ、カーソル移動はとてもスムーズでした。
ただし、このNetWalker、キーボードの作りがあまりに駄目駄目だったのと、ソフトの動作速度がとてつもなく遅かったのです、店頭で触った限り。

マウスクリックのボタンはポインタ側に集めずに離して両手操作。
ポインタ周辺にボタンが集まっていると片手操作が出来るものの、ドラッグとかがとても苦しくなってしまうので。

 

キーボードと端末(スマートフォンやタブレット、PCなど)との接続ですが、バリエーションを作りたいです。

:有線USB接続
利点
・キーボード側がちゃんとHID準拠で作ってあれば、PCでもUSBホスト対応のスマートフォンでもゲーム機でも、USBのキーボードとマウスが使える機械なら、なんにでも使える。
・ケーブルを接続するだけですぐに使える。
・電池がいらない。
欠点
・有線であること。

:専用2.4GHz無線接続
利点
・無線なので当然ケーブルレス。
・有線のUSBと同じ扱いになるので、通常のUSBキーボード等が使える機器なら使用可能。
欠点
・専用のUSBドングル(USB送受信機)が必要。
・キーボード側に電池が必要。

:Bluetooth接続
利点
・無線なので当然ケーブルレス。
・本体がBluetoothを搭載していれば、Bluetooth USBドングル(USB送受信機)は必要ない。
欠点
・本体側がBluetoothに対応している必要がある。
・本体側のBluetoothが有効になるまで使用不可。
・キーボード側に電池が必要。

 

※ページの上の方で紹介した小型キーボードって、みーんな無線なんですよね。自分としては、有線USBの小型キーボードが欲しいのです。

 

以上、こんな外付け小型キーボードが欲しい、を書き殴ってみました。
本当はもういっそ自分で作ってしまいたいくらいなのですが、時間はあるものの、知識や環境やコネクション(人脈、お付き合い)やおかねが全く無くて、どうしようもなくて・・・
どこかの会社で作ってはもらえないでしょうかね。

追記:自分、個人で、試作くらいは作れないか、下調べ始めました。
 ↓
USBキーボードは自作できるのか下調べをしてみる

 

追記:画像を作ってみました。

Kb_00

こんな感じ。
画像を切り貼り。

Kb_01

バリエーション。USB有線は汎用性がとても高いと思うのです。
このページの上の方に羅列した小型キーボードってどれも無線なんですよね。USB有線なら、USBキーボード対応機器であれば何にでも繋げられるという、とても大きなメリットがあるのですよ。

Kb_02

こんな感じでオプションでスマートフォンと合体させたりとか。
両手で持っても、机に置いても使えるような感じで。

Kb_03

バッテリーもモジュール化したりとか。
迷走。

これらはキーボードを使用する時だけ取り付けるイメージ。

常に一体化させておくのであれば、機種決め打ちで専用にして、クラムシェルにしたり。

Kb_04

色々迷走しましたが、スマホとの合体はあくまで応用例。

基本は

・USB有線接続
・小型(横幅15cm以下)
・HP200LXと同等の構造で、
両手で持っても、机においても快適に打鍵が出来る。
・マウス機能装備
・キーの省略は出来るだけ無し


といったキーボードを希望します。

ほしい。

けれど、どこからも発売されそうにないので、いっそ自分で作ってみようかと考え始めています。

USBキーボードは自作できるのか下調べをしてみる

| | コメント (1) | トラックバック (1)

«スマホで名古屋の電車バスに乗ろう(モバイルSuicaを東海圏内で使う)