t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

Arduino LEONARDOでPS4コントローラーをマウスとして使う

今回はPS4コントローラーのジョイスティックでマウスカーソルを操作するArduinoコードのご紹介。

もともとPS4コントローラーはマクロキーボードとして使っていたのだが、シューティングゲーム(東方シリーズ)でも使いたくなってモード切り替えできるようなコードを作成していた。
thom.hateblo.jp

今回はそこにマウスモードを機能追加してリクライニングチェアにもたれながら快適にPC操作ができるようにするのが目的。

コード

モード追加されたコード全量を載せると500行近くになるのでマウスモード単体機能で検証した際のコードを掲載する。

#include <Keyboard.h>
#include <Mouse.h>
#include <PS4USB.h>
#include <math.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>
float accm_x = 0;
float accm_y = 0;
float accm_w = 0;

USB Usb;
PS4USB PS4(&Usb);

uint8_t oldL2Value, oldR2Value;
uint8_t oldRightHatX, oldRightHatY;

void setup() {
  if (Usb.Init() == -1) {
    while (1); // Halt
  }
  Mouse.begin();
}

void loop() {
  Usb.Task();
  float r = 0;
  float x = 0;
  float y = 0;
  int dev = 64;
  
  if (PS4.connected()) {
    y = PS4.getAnalogHat(LeftHatY)-128;
    x = PS4.getAnalogHat(LeftHatX)-128;
    r = sqrt(pow(x,2) + pow(y,2));
    if (PS4.getButtonPress(R1)) {
      dev = 64;
    } else {
      dev = 8;
    }
    if (r > 5) {
     accm_x += x / dev;
     accm_y += y / dev;
     Mouse.move((int)accm_x, (int)accm_y, 0);
     accm_x -= (int)accm_x;
     accm_y -= (int)accm_y;
    }
    if (PS4.getButtonPress(CROSS)) {
      Mouse.press();
    } else {
      Mouse.release();
    }
    if (PS4.getButtonPress(L1)) {
      Keyboard.press(KEY_LEFT_CTRL);
    } else {
      Keyboard.releaseAll();
    }
    
    int r2 = PS4.getAnalogButton(R2);
    int l2 = PS4.getAnalogButton(L2);
    if (l2) {
      accm_w += (float)l2/4096;
      Mouse.move(0, 0, (int)accm_w);
      accm_w -= (int)accm_w;
    }
    if (r2) {
      accm_w += (float)r2/4096;
      Mouse.move(0, 0, -(int)accm_w);
      accm_w -= (int)accm_w;
    }
    if (PS4.getButtonClick(OPTIONS)) {
      Keyboard.press('f');
      delay(40);
      Keyboard.releaseAll();
    }
    if (PS4.getButtonPress(UP)) {
      Keyboard.press(KEY_PAGE_UP);
    } else {
      Keyboard.release(KEY_PAGE_UP);
    }
    if (PS4.getButtonPress(RIGHT)) {
      Keyboard.press(KEY_RIGHT_ARROW);
    } else {
      Keyboard.release(KEY_RIGHT_ARROW);
    }
    if (PS4.getButtonPress(DOWN)) {
      Keyboard.press(KEY_PAGE_DOWN);
    } else {
      Keyboard.release(KEY_PAGE_DOWN);
    }
    
    if (PS4.getButtonPress(LEFT)) {
      Keyboard.press(KEY_LEFT_ARROW);
    } else {
      Keyboard.release(KEY_LEFT_ARROW);
    }
  }
}

カーソル移動のアイデアと解説

座標取得

まずPS4.getAnalogHat(LeftHatY)の部分はPS4の左アナログスティックのY軸の値を求めているコード。
0(左端)~255(右端)で取得されるので半分の128を引くことで中央が0となり、-128 ~ 127の座標に変換できる。
(Xも理屈は同じなので説明省略)

r = sqrt(pow(x,2) + pow(y,2))は、三平方の定理を使ってスティックの押し込み距離を求めているコード。
これは以下の記事で紹介したコードと同じである。
thom.hateblo.jp

r値はジョイスティック未入力時の誤反応を防ぐために遊びを持たせる距離を測るのに使っている。

カーソル変速

R1ボタンの押し下げ状態でdevという値を64と8で変えている部分は、カーソルスピードを変更しているコード。
ちなみにdevはスペルミス。。(divisionのdivとするつもりだった) 手元のコードはそのうち修正しようと思う。
座標軸の最大値がマイナス方向に128なのでそれを64で割ると2となる。
これはスティックを左に倒し切っているときループ1回に2ドットカーソルが動くということになる。8で割ると16なので16ドット/ループになる。
つまり変数devが小さい方が速い。

このように高速・低速を切り替えるアイデアはシューティングゲーム(東方シリーズ)からヒントを得た。
これでマウス並みとはいかないまでも速度と精度のバランスを取って扱いやすくなった。

移動時の実数の取り扱い

前述したとおり、スティックを左に倒し切っているときにループ1回に2ドット移動する。
ということは、スティックの押し込みが半分ならループ1回に1ドットであるが、更に半分だとどうなるか。
0.5ドットという単位は存在しないので、移動できない。

十字方向だけなら1ドット単位でもなんとかなるが、任意の角度に移動しようと思うとやはり実数で移動させたい。
そこでaccm_x, accm_yというfloat形の変数に移動距離情報を一旦実数でストックするというアイデアを思いついた。

accm_x += x / dev;
accm_y += y / dev;
Mouse.move((int)accm_x, (int)accm_y, 0);
accm_x -= (int)accm_x;
accm_y -= (int)accm_y;

そして、カーソルはaccm_x, accm_yの整数部だけを参照して移動を行い、移動した整数量はaccm_x, accm_yから取り除く。
そうすれば小数部を捨てずに貯め続けて整数になったら移動に使うということができる。

これは画期的!と暫く自画自賛してたんだけど、よくよく考えたら市販のマウスかOSのドライバー側でも似たような処理をしてるはずで、そうでないと斜め移動できないよな。。

以上

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。