Arduino Pro Micro(の互換機)でマウスクリッカー(ノーマル、連打)を作る

#Arduino Pro Micro(の互換機)でマウスクリッカー(ノーマル、連打)を作ります
##はじめに
AliExpressにて購入したPro Micro互換機を使って、マウスの左ボタンクリッカーを作成します。
通常のクリック機能に加え連打モードを有します。連打モードはTAP INという機能を使用しフレキシブルな連打間隔を設定可能です。
![DSD_2371.JPG](https://qiita-image-store.s3.amazonaws.com/0/211703/ad120ff2-1e31-1b5d-7679-374c416c3d10.jpeg)

##機能
通常モードと連打モードを有します。
###通常モード
通常のマウスと同じく、ボタン押下すればクリック、離したらクリック解除。完全に通常のマウスクリックと同等になります。
押下中は左LEDが点灯します。
###連打モード
セットボタン(右側に配置したボタン)で連打間隔を設定して、左側のボタンを押下している間クリックを続けます。連打間隔はTAP INという機能を使用して設定できるようにしています。
連打モードに入ると右LEDが点灯し、クリック間隔に応じたタイミングでLEDが一瞬消灯し、タイミングを教えます。
クリックのタイミングで左LEDが点灯します。ボタンのエミュレーションは、押下->40ms待ち->リリースとなります。
####TAP INとは?
TAP INとはYAMAHAのメトロノームで採用されている機能で、TAP INボタンをリズムに合せて押下するだけでテンポ設定できるという機能です。
TAP INを使用することによって、プリセットされた連打間隔を選択するよりも柔軟な連打間隔を設定できることになります。
![14D35F755CC74D8BB933C976CAA5F400_12073_1500x1500_87e6b86f14412bfc5c667a14d728838e.jpg](https://qiita-image-store.s3.amazonaws.com/0/211703/d5995f96-ac79-3bf7-5c45-8bf7abbdd1cd.jpeg)
###連打モード解除
連打モードを解除するには、セットボタンを3秒長押します。
右LEDが5回素早く点滅し、LEDが消灯します。(=通常モード)


##回路
図面書くほどの回路ではないので、メモ程度に。PIN2,3は内部プルアップ指定していますので、GNDに落とすだけで抵抗は不要です。
PIN 2 <-> タクトスイッチ <-> GND
PIN 5 <-> タクトスイッチ <-> GND
PIN 6 <-> 抵抗(330Ω) <-> LED <-> GND
PIN 7 <-> 抵抗(330Ω) <-> LED <-> GND

##プログラム

```C++:MouseClicker.cpp
////////////////////////////////////////////////////////////////
//
// Mouse clicker 
// 2018-07-07 first
//
#include <Mouse.h>

#define ButtonPin1  (2)  // LEFT CLICK
#define ButtonPin2  (3)  // RIGHT
#define ButtonPin3  (4)  // MIDDLE
#define ButtonPin4  (5)  // CLICK SPAN KEY
#define ButtonPin5  (6)  // LED OUT1
#define ButtonPin6  (7)  // LED OUT2
#define ButtonPin7  (8)  // RESERVE


unsigned long uptime;     // 起動からの時間 ms
unsigned long uptimeLastKey;
unsigned long timeSpan;
unsigned long LastTimerActive;
unsigned long ModeResetTimeLast;
unsigned long ModeSpan;
int StatFlg;

void setup() {
  // put your setup code here, to run once:
  pinMode(ButtonPin1, INPUT_PULLUP);    // MOUSE LEFT BTN
  pinMode(ButtonPin2, INPUT_PULLUP);    // RESERVE (MOUSE RIGHT BTN)
  pinMode(ButtonPin3, INPUT_PULLUP);    // RESERVE (MOUSE CENTRE BTN)
  pinMode(ButtonPin4, INPUT_PULLUP);    // CLICK SPAN SETTING BTN
  pinMode(6, OUTPUT);         // BTN CLICKING LED
  pinMode(7, OUTPUT);         // SPAN INDICATER LED

  digitalWrite( 6, LOW);

  uptime = millis();
  uptimeLastKey = uptime;
  timeSpan = 0;
  StatFlg = 0;  // Normal

  // initialize mouse control:
  Mouse.begin();
  Mouse.release(MOUSE_LEFT );
  Mouse.release(MOUSE_RIGHT);
  Mouse.release(MOUSE_MIDDLE);
}


/////////////////////////////////////////
// Button Status variable
int nSetBtnSts = 0;
int nSetBtnPrev = 0;
int nLftBtnSts = 0;
int nLftBtnPrev = 0;

/////////////////////////////////////////
// Read Button Status
void ReadBtnStatus()
{
  nLftBtnPrev = nLftBtnSts;
  nLftBtnSts = !digitalRead(ButtonPin1);    // Read LEFT BTN
  nSetBtnPrev = nSetBtnSts;
  nSetBtnSts = !digitalRead(ButtonPin4) ;   // Setting button

}

void loop() {
  // Read Button status
  ReadBtnStatus();
  
  // put your main code here, to run repeatedly:
  LEDStatusIndicater(0,7);
  
  CheckSetBtnForSpanSetting();    // 連続クリック間隔の設定
  CheckSteBtnForModeReset();      // 連続クリックモードリセット監視

  if ( StatFlg == 0 ) // 通常モード
    NormalFire(); // 通常処理
  else if ( StatFlg == 1 ) // 連打モード
    RapidFire();  // 連打処理

  LEDStatusIndicater(1,7);

}

/////////////////////////////////////////
// 連打間隔設定の処理
void CheckSetBtnForSpanSetting()
{
  if ( nSetBtnSts == 1 && nSetBtnPrev == 0) // BTN押下時の場合のみ
  {
    timeSpan = millis() - uptimeLastKey;
    if ( timeSpan > 10000 ) // 10秒より長いならば、10秒にする
      timeSpan = 10000;
    uptimeLastKey = millis(); // 現在時間保存
    LastTimerActive = uptimeLastKey;  // タイマー用時間もリセット

    if ( StatFlg == 0 ) //通常モード
    {
      StatFlg = 1 ;  // 連打モードON
    }
    if ( StatFlg == 1 ) //連打モード中
    {
      // 処理なし?
    }
    delay(40);  // LEDを明く光らせる
  }  // 牡丹押下 END

}
/////////////////////////////////////////
// 連打モード解除監視用の処理
void CheckSteBtnForModeReset()
{
  if( nSetBtnSts == 1 && nSetBtnPrev == 1 ) // 設定ボタンが押されっぱなし
  {
    ModeSpan += (millis() - ModeResetTimeLast);
    if( ModeSpan > 3000 ) // 3秒でリセット
    {
      StatFlg = 0;      // 連打モードを解除
      NoticeBlinkLED( 7) ;  // LED通知
      ModeSpan = 0;
    }
  }
  else
  {
    // カウンターをリセットする
    ModeSpan = 0;  // おしっぱリセット
  }
  ModeResetTimeLast = millis();

}
/////////////////////////////////////////
// 通常時のクリック処理
void NormalFire()
{
  {
    if (nLftBtnSts != nLftBtnPrev ) // マウス押下状態が変化したら
    {
      if( nLftBtnSts )
      {
        Mouse.press(MOUSE_LEFT);
      }
      else
      {
        Mouse.release(MOUSE_LEFT);
      }
      digitalWrite( 6, nLftBtnSts );
    }
  }
}
/////////////////////////////////////////
// 連打モード時の処理
void RapidFire()
{
  if ( StatFlg == 1 ) //連打モード
  {
    unsigned long TimeSpent;
    TimeSpent = millis() - LastTimerActive;
    if ( ( TimeSpent > timeSpan ) )
    {
      LastTimerActive = millis();
      // タイムのアクション
      digitalWrite( 7, LOW);      // 点滅LEDは目立たないので一旦
      if (nLftBtnSts == 1) // 左ボタン押下時 連打する
      {
        // マウス押下処理をここに
        MouseLeftKeyDown(6);  // マウスクリック & LED表示
        // 表示がズレるのと時間がかかるので上記にLED表示処理統合 NoticeHighlightLED(6);
        // マウス押下処理 end
      }
      else
      {
        delay(40);    // LED 7のOFFを目立たせる
      }
      digitalWrite( 7, HIGH);
    }
  }

}
/////////////////////////////////////////
// LEDを点滅させる(通知目的)
void NoticeBlinkLED(int pin)
{
  int i;
  for(i = 0; i< 5; i++ )
  {
    digitalWrite( pin, HIGH);
    delay(20);
    digitalWrite( pin, LOW);  
    delay(20);
  }
}

/////////////////////////////////////////
// LEDを明く点灯させる(通知目的)
void NoticeHighlightLED(int LEDPin)
{
  int pinSts;
  pinSts = digitalRead( LEDPin );
  digitalWrite(LEDPin, HIGH); // LED点灯
  delay(40);  // 点灯時間セット
  digitalWrite(LEDPin, pinSts); // 元の点灯状態に戻す

}
/////////////////////////////////////////
// LEDを明く点灯させる(通知目的)
// LEDPin : LED点灯させるピン番号、0なら点灯させない
void MouseLeftKeyDown(int LEDPin)
{
  int pinSts;
  pinSts = digitalRead( LEDPin );
  digitalWrite(LEDPin, HIGH); // LED点灯
  Mouse.press(MOUSE_LEFT);
  delay(40);  // 押下時間&点灯時間セット
  Mouse.release(MOUSE_LEFT);
  digitalWrite(LEDPin, pinSts); // 元の点灯状態に戻す
}

/////////////////////////////////////////
// LED発光状態で状態を通知する
void LEDStatusIndicater(int position, int LEDPin)
{
  // status 0 
  const int time[3][4] = {
    { 0, 0, LOW, LOW},    // status 0 : 通常モード (点灯させない)
    { 2,10, HIGH,LOW},    // stasus 1 : 連打モード
    { 0, 0, LOW, LOW},    // status 2 : reserved
  };
  if( 0 == position ) // ループ最初
  {
    digitalWrite( LEDPin, time[StatFlg][2]);
    delay(time[StatFlg][0]);
  }
  else if( 1 == position )  // ループ最後
  {
    digitalWrite( LEDPin, LOW);
    delay( time[StatFlg][3] );
  }
}
```

##テスト
SimCityで除染用に草植える時マウスクリックがうるさいというクレームが開発のきっかけでした。音も無く草植えることが可能なことを確認できました。
![mouseclicke01.png](https://qiita-image-store.s3.amazonaws.com/0/211703/aa2ecf66-9bc5-b9d0-c9f6-35c9e7990bba.png)
![mouseclicker03.png](https://qiita-image-store.s3.amazonaws.com/0/211703/7f46684c-c770-a1da-8440-a4cc23068058.png)

##おわりに
私としては草植える以外使い道がないのですが、なにかの参考になれば幸いです。

2018/07/07 Ikeda