【iOS対応】「 連打機能付きマウスコントローラ」を作製した件

# はじめに

:::note info
この記事は、「M5Stack Japan Creativity Contest 2023」に応募するためにProto Pediaに投稿した記事を、Qiita用に移植・再編集したものです。
:::

https://protopedia.net/prototype/4644

# 概要

M5Stack社のM5StampC3Uを用いて、Bluetoothマウスコントローラーを作成しました。
コントローラーを端末と接続することで、Bluetoothマウスとして使用することが出来ます。
又、連打機能を搭載しており、プログラム次第で好きな連打間隔でクリックを打つことが出来ます。
尚、本コントローラーはiOSで使用することを念頭に開発しているため、しっかりiOSに対応しております。

![IMG_6879.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/8426c029-c0f1-c190-176f-4ccb17263ba9.jpeg)

M5StampC3UはコントローラとしてESP32を内包しており、それをマスター側にBluetoothマウスとして認識させるという方法を取っています。

![S__317767821_0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/22ef4c8b-18cb-dcd8-63f4-6b14a0b8e654.jpeg)

マウスカーソルの移動にはアルプス電気のジョイスティック`RKJXV1224005`を採用しており、固定の都合上DIP化キットを使用しています。

[ジョイスティックＲＫＪＸＶ１２２４００５　ＤＩＰ化キット](https://akizukidenshi.com/catalog/g/gK-15433/)

又、マウスの左右クリックはそれぞれ白と赤のボタンがその機能を担っており、ミヤマ電器の`DS-663C-R-W-S-K`を採用しました。採用理由は、**押し心地がめちゃくちゃ良いから**です。

[丸型プッシュスイッチ モーメンタリー赤](https://eleshop.jp/shop/g/gE2A366/)
[丸型プッシュスイッチ モーメンタリー白](https://eleshop.jp/shop/g/gE2A367/)

その他、電源オンオフ用、連打機能オンオフ用のスライドスイッチ、インジケーターとしてのLEDが3つ(左から<b style="color:green;">緑</b>・<b style="color:blue;">青</b>・<b style="color:red;">赤</b>)があります。

![S__317767813_0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/04eb06b9-a22f-4426-2de8-5485d647a756.jpeg)

## 作製した経緯

大学の友人に、ゲームに使う連打ツールを作ってほしいと言われたのがきっかけでした。
iOSにはサードパーティー製アプリの連打ツールが無く、アクセシビリティの機能を用いるとそれっぽいものが使えるのですが、それも使い勝手の悪いものでした。

## 連打する方法について

連打について、今回は**ソフトウェア的にクリックの信号を送る**こととしました。 物理的にタッチパネルを叩く方法もありますが、スマホのディスプレイは静電容量式なのでタッチモジュールを使わないといけないことや、上下する機構を作らないといけないなど、色々と面倒なことがあったので採用しませんでした。

一方、ESP32をBluetoothマウスとして認識させる方法は、ライブラリが既に用意されているため非常に簡単に実装することが出来ました。何より、以前にも同ライブラリを使って遊んだ経験があったことが大きかったです。

# 回路設計

## システム構成図(回路図)

![回路図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/64556cbb-0ee2-714b-b1db-15cddb2b84a7.png)


## 説明

ジョイスティックは縦方向、横方向にそれぞれ`10kΩ`の可変抵抗となっています。
それらをX軸方向は5番、Y軸方向は4番ピンにアナログ入力し、電圧を読み取るようにしています。
また、ジョイスティックには押し込みスイッチが搭載されていため、それを3番ピンにデジタル入力しています。

インジケーター用のLEDは、Bluetooth用の青色LEDは6番ピン、連打機能用の緑色LEDは7番ピンからデジタル出力しています。

連打機能を切り替えるスライドスイッチは10番ピンにデジタル入力しています。

マウスのクリックの機能となる白・赤のボタンはそれぞれ21番・20番ピンにデジタル入力しています。

ジョイスティック押し込み以外のデジタル入力は、内部プルアップ抵抗を用いています。

尚、今回は単４電池2本で駆動させるため、電圧を`3.0V`から`3.3V`に昇圧する必要があります。
(ESP32は最低3.3Vあれば動作はします。ただし、5Vピンに電圧は発生しません。)

そこで、秋月電子の`３．３Ｖ出力昇圧ＤＣＤＣコンバーター`を利用することとしました。
これにより非常に小さい実装面積で、適正電圧まで昇圧することが出来ました。

![K-13066.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/4b1ed39f-e138-7c15-047d-400e99e8feff.jpeg)

[３．３Ｖ出力昇圧ＤＣＤＣコンバーター](https://akizukidenshi.com/catalog/g/gK-13066/)

今回は電力損失を少なくするために、`3.0V`からあえて`3.3V`への昇圧にとどめましたが、マイコンによっては`5V`への昇圧が必要な場合もあるかと思います。
その場合は、同じような商品で5V昇圧版があるので、こちらも非常に有用なパーツかと思います。

[５Ｖ出力昇圧ＤＣＤＣコンバーター](https://akizukidenshi.com/catalog/g/gK-13065/)

# 筐体および配線

筐体にはダイソーのカードケースを用いて、ジョイスティック・ボタン・スライドスイッチ・LEDの部分に穴をあけました。
ケース状のため簡単に内部にアクセスすることが出来、電池交換のしやすさを含めたメンテナンス性に長けております。

![1j8Tmr2SDZOYBMxBPWdTH7f1QUbqjYvls_700x.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/7860b7d7-00e9-8d69-5c08-a61e99bd2e9a.jpeg)

[カードケース 深型](https://jp.daisonet.com/collections/stationery0291/products/4906137326715)

又、配線について、今回はすべて手作業で配線を行いました。
総はんだ箇所は…数え切れません…(ﾂｶﾚﾀ)

# プログラム

プログラミングには**Arduino IDE 2.0**を使用しました。
当方、2.0になってからArduino IDEを使うのは初めてです。
それまでは Platform IOを使用していましたが、せっかくの機会なので上ソフトを使用することとしました。

## ライブラリ
`M5StampC3U`をBluetoothマウスとして認識させるために、`ESP32-BLE-Mouse`ライブラリを使用しました。
今までは、以下のGithubページのものを使用していましたが、テストしてみたところiOSでは動作しませんでした。(同様の報告がネット上で散見されます。)
[T-vK/ESP32-BLE-Mouse](https://github.com/T-vK/ESP32-BLE-Mouse)

そこで、別の方がNimBLEに対応させた別のライブラリを使用しました。
[wakwak-koba/ESP32-NimBLE-Mouse](https://github.com/wakwak-koba/ESP32-NimBLE-Mouse)

こちらでは問題なくiOSでも動作しました。

ライブラリはzipファイルを解凍し、`ドキュメント>Arduino>libraries`内に保存して使用するものとします。

## ソースコード

```C:renda_tool.c
#include <BleMouse.h>

BleMouse BleMouse("C3U_Mouse");

const int INPUT_JOY_BTN = 3;
const int INPUT_Y_PIN = 4;
const int INPUT_X_PIN = 5;
const int BT_LED = 6;
const int RND_LED = 7;
const int RND_SW = 10;
const int Single_L = 20;
const int Single_R = 21;
int X_val, Y_val;                  // 変数を整数型で宣言

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(INPUT_JOY_BTN, INPUT); 
  pinMode(BT_LED, OUTPUT);
  pinMode(RND_LED, OUTPUT);
  pinMode(RND_SW, INPUT_PULLUP); 
  pinMode(Single_L, INPUT_PULLUP); 
  pinMode(Single_R, INPUT_PULLUP); 
  BleMouse.begin();
}

void loop() {
  // put your main code here, to run repeatedly:
  X_val = analogRead(INPUT_X_PIN);  // アナログ値の読み取り
  Y_val = analogRead(INPUT_Y_PIN);  // アナログ値の読み取り

  double move_x, move_y;

  if (2440 - X_val > 0){
    move_x = ((2440 - X_val) / 1290.0) * 5;
  }
  else{
    move_x = ((2440 - X_val) / 1575.0) * 5;
  }

  if (Y_val - 2360 > 0){
    move_y = ((Y_val - 2360) / 1310.0) * 5;
  }
  else{
    move_y = ((Y_val - 2360) / 1280.0) * 5;
  }
  
	// シリアルモニタに出力
  // Serial.print("X: ");       
  // Serial.print(X_val);
  // Serial.print(", ");
  // Serial.print(move_x);
  // Serial.print(", ");
  // Serial.print("Y: ");
  // Serial.print(Y_val);
  // Serial.print(", ");
  // Serial.println(move_y);

  if (BleMouse.isConnected()) {
    digitalWrite(BT_LED, HIGH);
    
    //ミドルボタンのオンオフ
    if(digitalRead(INPUT_JOY_BTN)==LOW){
      BleMouse.press(MOUSE_MIDDLE);
    }
    else{
      BleMouse.release(MOUSE_MIDDLE);
    }

    //左ボタンのオンオフ
    if(digitalRead(Single_L)==LOW){
      BleMouse.press(MOUSE_LEFT);
    }
    else{
      BleMouse.release(MOUSE_LEFT);
    }

    //右ボタンのオンオフ
    if(digitalRead(Single_R)==LOW){
      BleMouse.press(MOUSE_RIGHT);
    }
    else{
      BleMouse.release(MOUSE_RIGHT);
    }

    //カーソルの移動
    BleMouse.move(move_x, move_y);
    
    //連打
    if(digitalRead(RND_SW)==LOW){
      int count = 0;
      while(digitalRead(RND_SW)==LOW){
        if(count<100){
          digitalWrite(RND_LED, HIGH);
          BleMouse.click();
          delay(500);
          count++;
        }
        else{
          digitalWrite(RND_LED, LOW);
          delay(500);
        }
      }
    }
    else{
      digitalWrite(RND_LED, LOW);
    }
    
  }
  else{
    digitalWrite(BT_LED, LOW);
  }

  delay(1);
}

```

## 解説

### 1~3行目
BleMouseのライブラリを読み込み、`C3U_Mouse`という名前で認識させます。

### 5~12行目
各GPIOピンの番号を変数に格納します。

### 13行目
ジョイスティックの位置を格納する変数`X_val`と`Y_val`を宣言します。

### 15~25行目 setup関数
17行目でシリアル通信の起動
18~23行目で各GPIOピンのモード設定
24行目でBluetoothを起動します。

### 27~114行目 loop関数
初めに、29~30行目でジョイスティックの位置を電圧値として読み込みます。

32行目では、カーソルの移動量を格納する変数`move_x`と`move_y`を宣言しています。

34~39行目では横軸方向の位置調整を行っており、読み込んだ値に対して真ん中より右にあるか、左にあるかを判断し、その傾き具合を割合として算出します。そこに5をかけて、マウスカーソルのスピードを決定しています。
ジョイスティックの傾き具合に応じて、カーソルの移動速度が変わる仕様となっています。

41~46行目では、同様の処理を縦軸方向に対して行っています。

48~56行目はデバッグ用にX,Y軸の傾きの電圧値(`X_val`と`Y_val`)、およびそれをもとにしたカーソル移動の値(`move_x`と`move_y`)をシリアルで表示します。

58行目以降は、Bluetoothによりマスター(例えばiPhone)と接続している時のみ動作します。

62~67行目は、ジョイスティックの押し込みを検出し、押し込まれた場合はマウスのミドルボタン(マウスホイールの押し込み)に対応させています。
ただし、iOSの場合はOSの都合上特に動作しませんでした。

70~75行目は白ボタンの押しを検出し、押された場合は左クリックを行います。

78~83行目は赤ボタンの押しを検出し、押された場合は右クリックを行います。

86行目では先ほど計算したカーソル移動量(`move_x`と`move_y`)に基づき、実際にマウスカーソルを移動させています。

89~106行目では、本体上面真ん中の赤色のスライドスイッチのオンオフを検出し、オンの場合は連打機能を起動させます。
連打には`BleMouse.click()`関数を用いており、上の`BleMouse.press()`や`BleMouse.release()`と異なり一回分のクリックを一つの関数で行ってくれます。
連打間隔は500ミリ秒としており、100回連打を行うと自動的に停止するように設定しています。

尚、本体上面上部にLEDが3つありますが、本体を起動すると右の赤のLEDが、Bluetoothでマスターと接続すると真ん中の青のLEDが、連打機能を起動させると左の緑のLEDが点灯します。

# 動作動画

M5Stack Japan Creativity Contest 2023 に応募する関係上、動作動画を作製しました。
短い動画ですが、是非ご覧ください。

<iframe width="560" height="500" src="https://www.youtube.com/embed/Ac9RTKvWGdE?si=uuYQUiVo1lKiyzQ9" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

# その他の画像

電源スイッチは、奥側の側面にあります。

![S__317767815_0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/b5974fef-f367-54ed-f062-9b21eb4f5516.jpeg)

M5StampC3UのUSB-Cポートに直接アクセスできるよう、コネクタ部分も穴をあけています。

![S__317767818_0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/7f6573bc-4398-e6a6-9bd3-8048b072ef98.jpeg)