8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

自作マウス作ってみた

Posted at

これは、裏TUTアドベントカレンダーの9日目です。

どうも、B1 ロボコンに所属してます。さとうです。

突然ですが、トラックボールマウスってかっこよくない?

例えばこんなの↓

誰か買って…

大学生になってから明らかにパソコンで作業をすることが増えまして、狭い机で作業したいなってなったときやっぱりマウスを動かす範囲にスペースを取られるのは結構だるい。

そこで登場するのがこのマウス。親指だけで済むのがいいよね。

そしてこっちが本命の理由

””かっこいい””

やっぱ、他のマウスよりもワンランク上な感じがしてかっこいい。

でも高いんだよなー。かっこいいものは値段も高い。

そう思ってたとき、秋葉原にある秋月電子でジョイスティック(ゲームのコントローラーのスティック)の基盤が1個300円ぐらいで売ってるのを見つけたんですよ。これでそれっぽいものつくれるくね!?発明の神が舞い降りました。

前置きが長くなりましたが、今回は自作マウスの中でもジョイスティックマウスを作ってみた!という記事を書いていきます。

前例を調べる

まず、ジョイスティックマウスでパソコンのマウスカーソルを動かした前例がないか調べてみました。そしたら、ちょこちょこある。

今回はこちらの記事に乗ってるコードを参考、というか使用させていただきながら作っていきます。

ちなみに「ジョイスティックマウス」とアマゾンで調べたらでてくるのはだいたいこんな感じ

僕が作りたいのはトラックボールがジョイスティックに置き換わったやつだからちょっと違う

外側を作ってみる

外側を友達の3Dプリンターを借りてつくって見ます。いつか私物でほしい

CADを使って設計

かなり苦戦した

  • 外側

スクリーンショット 2024-12-08 215349.png

実際に3Dプリンターで出力したが、ちょっとでかすぎたのでいい感じにカットして穴を開け直した。力技すぎだろ

IMG_20241124_180537358.jpg

剛性もかなりあってサイズも穴の位置もいい感じに開けれたため耐え

  • ボタン部分
    スクリーンショット 2024-12-08 215847.png

手の角度に合うように意識した

  • 底面
    スクリーンショット 2024-12-08 215442.png

でっぱりはマイコン設置用

  • ジョイスティックの台座
    スクリーンショット 2024-12-08 220002.png

ジョイスティックを固定するための台座。

コードを書いてみる

次にマウスを動かすプログラミングを書く。
マイコンはPro microを使っているので、書き込みはArduinoIDEを使った。

Mouse.ino

#include <Mouse.h>
#include <Keyboard.h>
#include "myLambdas.h"

constexpr auto MOUSE_LEFT_BUTTON(8);
constexpr auto MOUSE_RIGHT_BUTTON(7);
constexpr auto MOUSE_BACK_BUTTON(5);
constexpr auto X_AX(18);  // X軸の出力をピン18につないでます。
constexpr auto Y_AX(20);  // Y軸の出力をピン20につないでます。

int buttonState1 = 0;
int buttonState2 = 0;

constexpr auto THRESHOLD(1);      //マウスを動かす最低距離。これ未満は反応しない。
constexpr auto RANGE(10);         //マウスを動かす最大距離
constexpr auto RESPONSE_TIME(5);  // 動作安定と速度調整のための遅延
constexpr auto CENTER_X(517);     // X軸の中心値。
constexpr auto CENTER_Y(498);     // Y軸の中心値。

auto smoothX(myLambdas::smoothF());  // X軸のデータを平滑化する関数
auto smoothY(myLambdas::smoothF());  //  Y軸のデータを平滑化する関数

auto debounceButton(myLambdas::debounceF());    // ボタンのデータをデバウンスする関数
auto isRaisedButton(myLambdas::isRaisedF());    //ボタンのデータが falseー>true に変化したか を返す関数
auto isDroppedButton(myLambdas::isDroppedF());  //ボタンのデータが trueー>false に変化したか を返す関数

//X軸、Y軸のデータとTHRESHOLDよってマウスを動かす関数
auto mouseActionByDataF(
  [](const int THRESHOLD) {
    return [=](const int DATA_X, const int DATA_Y) mutable -> void {
      if (abs(DATA_X) < THRESHOLD && abs(DATA_Y) < THRESHOLD) return;
      Mouse.move(DATA_X, -DATA_Y, 0);
      return;
    };
  });
auto mouseActionByData(mouseActionByDataF(THRESHOLD));

//0 ~ CENTER ~ 1023 のデータを-RANGE ~ 0 ~ RANGE にマッピングする関数
//正負それぞれ3ブロックに分け、傾きを変えることでAカーブを近似する
auto mapToRangeF(
  [](const int CENTER, const int RANGE) {
    return [=](const int DATA) mutable -> int {
	    const auto OFFSET(DATA - CENTER);
      const auto IN_MAX(1024);
      const auto EDGE_M3(-CENTER);
      const auto EDGE_M2(EDGE_M3 * 3 / 4);
      const auto EDGE_M1(EDGE_M3 * 1 / 2);
      const auto EDGE_P3(IN_MAX - CENTER);
      const auto EDGE_P2(EDGE_P3 * 3 / 4);
      const auto EDGE_P1(EDGE_P3 * 1 / 2);
      if (OFFSET < EDGE_M2) return map(OFFSET, EDGE_M2, EDGE_M3, -RANGE / 2, -RANGE);
      if (OFFSET < EDGE_M1) return map(OFFSET, EDGE_M1, EDGE_M2, -RANGE / 4, -RANGE / 2);
      if (OFFSET < 0) return map(OFFSET, 0, EDGE_M1, 0, -RANGE / 4);
      if (OFFSET <= EDGE_P1) return map(OFFSET, 0, EDGE_P1, 0, RANGE / 4);
      if (OFFSET <= EDGE_P2) return map(OFFSET, EDGE_P1, EDGE_P2, RANGE / 4, RANGE / 2);
      return map(OFFSET, EDGE_P2, EDGE_P3, RANGE / 2, RANGE);
    };
  });
auto mapToRangeX(mapToRangeF(CENTER_X, RANGE));
auto mapToRangeY(mapToRangeF(CENTER_Y, RANGE));

void setup() {
  pinMode(MOUSE_LEFT_BUTTON, INPUT_PULLUP);
  pinMode(MOUSE_RIGHT_BUTTON, INPUT_PULLUP);
  pinMode(MOUSE_BACK_BUTTON, INPUT_PULLUP);
  Mouse.begin();

  Keyboard.begin();
  delay(2000);// システムがデバイスを認識するための時間を確保(2秒間待機)
}

void loop() {

  buttonState1 = digitalRead(MOUSE_RIGHT_BUTTON);  //ボタンの状態を入れる
  buttonState2 = digitalRead(MOUSE_BACK_BUTTON);
  //マウスボタンのピンを読んでデバウンスしたデータでボタンを動かす
  if (isDroppedButton(debounceButton(digitalRead(MOUSE_LEFT_BUTTON)))) {
    //ボタンが押された瞬間、プレス
    Mouse.press(MOUSE_LEFT);
  }
  if (isRaisedButton(debounceButton(digitalRead(MOUSE_LEFT_BUTTON)))) {
    //ボタンが離された瞬間、リリース
    Mouse.release(MOUSE_LEFT);
  }
  if (buttonState1 == LOW) {
    //右クリック
    Mouse.click(MOUSE_RIGHT);
  }
  if (buttonState2 == LOW) {
    //戻るボタン(ALT+←)
    Keyboard.press(KEY_LEFT_ALT);
    Keyboard.press(KEY_LEFT_ARROW);
    delay(100);             // 100ミリ秒間待機
    Keyboard.releaseAll();  // 全てのキーを離す
    delay(1000);
  }

  //X軸、Y軸のピンを読んで平滑化して移動距離にマッピングしたデータでマウスを動かす
  mouseActionByData(mapToRangeX(smoothX(analogRead(X_AX))), mapToRangeY(smoothY(analogRead(Y_AX))));

  delay(RESPONSE_TIME);
}

myLambdas.h (参考のやつをそのまま使わせていただきました。感謝)

namespace myLambdas
{
  //以前の値と新しい値の加重平均を返すクロージャ
  auto smoothF ( 
    []( const long RATE = 20 ){
      long previousValue ( 0 );
      return [=]( const long CURRENT_VALUE ) mutable -> long {       
        return previousValue = (RATE * CURRENT_VALUE + (100 - RATE) * previousValue) / 100;
      };
    } 
  );
  
  //以前の値かける100と新しい値かける100の荷重平均を取り、
  //その値と閾値によってtrueまたはfalseを返すクロージャ
  auto debounceF (
    []( const long RATE = 25, const long THRESHOLD_L = 10, const long THRESHOLD_H = 90 ){
      long previousValueMul100 ( 100 );
      bool debouncedValue ( true ); 
      return [=]( const long CURRENT_VALUE ) mutable -> bool {
        previousValueMul100 = RATE * CURRENT_VALUE + (100 - RATE) * previousValueMul100 / 100;
        if( debouncedValue ) {
          if( previousValueMul100 < THRESHOLD_L ) return debouncedValue = false;
          return true;
        }
        if( previousValueMul100 > THRESHOLD_H ) return debouncedValue = true;
        return false;
      };
    }
  );
  
  //以前の値がfalseで新しい値がtrueか? を返すクロージャ
  auto isRaisedF(
    [](){
      bool previousValue ( false );
      return [=]( const bool CURRENT_VALUE ) mutable -> bool{
        const bool RESULT ( ! previousValue && CURRENT_VALUE );
        previousValue = CURRENT_VALUE;
        return RESULT;      
      };
    }
  );
  
  //以前の値がtrueで新しい値がfalseか? を返すクロージャ
  auto isDroppedF(
    [](){
      bool previousValue ( false );
      return [=]( const bool CURRENT_VALUE ) mutable -> bool{
        const bool RESULT ( previousValue && !CURRENT_VALUE );
        previousValue = CURRENT_VALUE;
        return RESULT;    
      };
    }
  );
  
}

Mouse.inoは、参考のやつに右クリック、戻るボタンを追加しました。マウスの速度などもmapToRangeFの関数の中にある範囲をちょこちょこいじると変えられます。(ぼくはちょこちょこ変更してみた結果そのままが使いやすかったのでそのまま。)

配線して動作確認

ブレッドボード上で動くことを確認(部室で拾ったなんかついてるブレッドボード)

組み立て

接着剤でパーツを貼り付けてはんだ付け

完成✨


まとめ

意外とちゃんとしたものが作れて嬉しかった。この文章書いてる間も使いながら書いてます。まだ、市販されてるマウスには使用感は程遠いが調整次第でもっと使いやすくなりそう。今回は設計ミスとかも若干妥協した部分があるし、実は穴が小さくてジョイスティックがフルで動かせてないし、中の基盤を隠しきれてなかったりと、かなりズタズタではあるけど、愛着を加味すれば全然使える範囲で作れたのでよかった。スクロール機能の実装が間に合わなかったので、ジョイスティックを押し込みながらの操作によるスクロールの実装はまた今度行いたい。

目標

次の目標は、自作キーボードを作ること。

みんなの憧れ、分割キーボード+トラックボールの変態キーボード(高級)

分割キーボード+ジョイスティックとか作ってみたいなーって考え中。
  

ここまで拙く知識不足な文を読んでくださり、ありがとうございました!!

五煎目の紅茶さんの「青空フリーパスのススメ」です!旅行行きたい!!!

8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?