t-hom’s diary

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

Arduino Pro MicroでPS4コントローラーをマクロキーボードとして使う(小型化)

今回は以前に紹介したPS4コントローラーをマクロキーボードとして使う件の小型化について紹介する。
thom.hateblo.jp

f:id:t-hom:20201121010920p:plain

わざわざ私が書かなくても、Arduino Pro MicroとミニUSB Host Sheildを組み合わせた事例はネットで検索できるが、ハマりどころが2か所ほどあったのでそこを強調する意味で記事にしようと思う。

サイズ感

左が従来型で右が今回作成したもので、機能的には全く同じである。
f:id:t-hom:20201120214349p:plain
これでケーブルの向きも良い感じになった。

購入したもの(要注意)

Arduino Pro Micro 3.3V 8MHz

上記は5個セットだが、単品でも購入できるかと思う。
Arduino Pro Microには次の2種類があるので、購入時に要注意である。
今回の目的で使用する場合は、必ず3.3V版を利用する必要がある。

動作電圧 動作クロック
5V版 5V 16MHz
3.3V版 3.3V 8MHz

見た目はほぼ同じで、ルーペで確認しないと分からないレベルなので形で判断はできない。
私は間違えて5V版の5個セットを購入してしまい、全く動作しなかった。

同じページから購入しても2個セットを選択したら5V版だったりするので改めて購入しようとする商品が3.3V版であることを確かめる必要がある。

私が買った商品のAmazon説明では3.3vの16MHzになっていたが、実際には3.3vの8MHzが届いた。
ちなみに表記間違いはAmazonへレポート済である。

なお、単品購入の場合は同じメーカーのものは見つからないかもしれないが、Arduino Pro Microと書いてあって3.3Vで部品の配置が同じだったらメーカーが違えど基盤の色が違えど動作するはず。(自己責任でお願いします。)
大事なことなので何度も繰り返すが、5V版と3.3V版は部品配置もカラーリングも同じなので表記で見分けるしかない。

※拡大写真があれば、水晶発振器に8MHzと書いてあるはずだが、両方販売してる場合に画像を使いまわしてる場合もあるので過信はできない。

届いてから見分ける方法としては、電圧レギュレーターの部品型番と水晶発振器のクロック数表記が違う。
f:id:t-hom:20201120220447p:plain
ルーペがあるなら個包装を開封する前に確認すれば返品できるかもしれない。

ミニUSB ホストシールド

こちらはPrimeじゃなかったので海外から到着するのに約3週間かかった。
単品でPrime商品もあるのでそっちで良いと思う。
これもメーカー違いが複数出てるけど商品画像の部品配置が同じだったら商品も同じだと思う。(自己責任でお願いします。)

加工

パターンカット

今回は3.3V版のArduino Pro Microを使うが、その理由はミニUSBホストシールドのチップが3.3Vにしか対応していない為である。ただそのままだとUSBに給電される電圧も3.3Vなので、通常5Vで動作する機器が動作しなくなる。よって別の場所から5Vを給電するために3.3Vの給電線をカットするという作業が必要になる。

下図に赤線で示したところにカッターナイフで何度も傷をつけて、基盤パターンをカットする。
f:id:t-hom:20201120222155p:plain

下図赤丸で示す抵抗の端のハンダと、もうひとつの赤丸のスルーホールにそれぞれテスターの針を当てて導通してなければ正しくカットできている。
f:id:t-hom:20201120222451p:plain

実際に測っているところ。
f:id:t-hom:20201120223055p:plain

カット跡はこんな感じ。
f:id:t-hom:20201120223207p:plain

ArduinoとUSB Host Shieldの接続

まずは接続箇所を説明し、実際の接続手順はその後に解説する。

接続関係の説明

基本的にはUSB接続口が左右逆向きになるように重ね合わせることができるが、下図の赤×印の部分は上下のホールを繋いではいけない。また、赤線、青線はそれぞれワイヤーで接続する必要がある。
f:id:t-hom:20201120224928p:plain

まず赤線は、ArduinoのRawからUSBホストシールドのUSB接続口へ給電するためのケーブルである。
Rawは生という意味で、Arduino自体のUSBから給電された生の5Vが直接出力されている。これは3.3V版を購入しても同様なので、この電圧をUSB機器へ直接流すことで5V駆動の周辺機器を繋ぐことができる。

青線はArduinoのリセットとUSBホストシールドのリセットを繋ぐケーブルである。

そしてUSBホストシールド側の上段の2つの×印はArduino側ではそれぞれのケーブルが繋がれる位置のため、何も接続しない。
下段の×印はよく見るとリセットと接続されていることが分かると思う。そしてArduino側はGNDになっている。リセットとGNDを接続してしまうと機器にリセットがかかってしまうので、ここは接続しない。

接続手順

接続にはArduino Pro Microに付属する12ピンを利用する。
f:id:t-hom:20201120231316p:plain

このうち1つは、端から3つ目のピンをラジオペンチで抜き取っておく。これは先ほど説明したArduinoのGNDとUSB Host Shieldのリセットを接続させないための処置である。
f:id:t-hom:20201120231510p:plain

ちなみに、ピンを抜く代わりに基盤のパターンをカットすると紹介されているページもあった。どちらの方法でも、GNDとRSTを接続させないという目的は達成できるのでOK。
f:id:t-hom:20201120233148p:plain

さて、もう一方はラジオペンチで9、1、2に分けて、このうち2は破棄する。
f:id:t-hom:20201120231855p:plain

ミニブレッドボードに次のように差し込む。USB側はどうしても浮き上がった形になってしまうが仕方がない
f:id:t-hom:20201120232047p:plain

上段の×印は接続させたくないだけなので、ピンを抜いただけで黒い土台は残している。
下段の×印はそこにケーブルが収まるので土台もない状態。

上からArduino Pro Microを重ねる。
f:id:t-hom:20201120232522p:plain

そしてピンのある個所だけを半田付けする。

半田付けできたら一旦ブレッドボードから抜いて分離する。
f:id:t-hom:20201121001344p:plain

次に、ワイヤーをUSB Host Shieldのスルーホールに半田づけする。
赤線はパターンカットしたUSB給電へ。青線はそこから2つ飛ばしてRSTへ接続する。
裏側から半田付けするが、ケーブルが途中で抜けないようにマスキングテープを活用する。
f:id:t-hom:20201121001534p:plain

次に、赤線をArduinoのRawに、青線をArduinoのRSTへ接続する。この時ケーブルが長すぎるとこのあと2つの基盤を重ねた時に間に収まらなくなるので、以下の写真くらいの長さにとどめておく。
f:id:t-hom:20201121001853p:plain

表からみた写真。
f:id:t-hom:20201121002004p:plain

次に、2つの基盤を重ね合わせる。完全に差し込む前に、ワイヤーの根本をラジオペンチなどでなるべく奥側へ倒しておく(下図矢印の方向)。半田付けされているので割と固いけど押し込んでおかないと基盤を合体させたときに最後までささらない。
f:id:t-hom:20201121002320p:plain

最後まで押し込むとコンパクトにピッタリ収まる。
f:id:t-hom:20201121003111p:plain

次に基盤どおしが浮かないようにマスキングテープで固定する。
f:id:t-hom:20201121003156p:plain

そしてマスキングテープが無い場所からハンダ付けをして、マスキングテープをはがして残りのピンをハンダ付けする。
※ピンが出ていないところはハンダ付けしないので注意。

これで接続は完成。
f:id:t-hom:20201121003347p:plain

参考サイト

https://ht-deko.com/arduino/shield_usbhost_mini.htmlht-deko.com
参考というかまるっとそのまま真似したのだが、初心者の私にとって細かいニュアンスで色々とつまずく点もあったので今回こちらでも解説してみた。

プログラム書き込み

プログラムの書き込みでも重要な注意点がある。
5V版のArduinoでは単にボード選択でLeonardoを選べば良いが、今回使用する3.3V版はLeonardoとして書き込んでしまうと不具合が発生して動かなくなる。復旧は少し面倒なので間違えないようにしたい。

まずボードが登録されていないと思うので環境設定から追加ボードマネージャーのURLを追加しておく。
f:id:t-hom:20201121003732p:plain

追加するURLは次のとおり

https://raw.githubusercontent.com/sparkfun/Arduino_Boards/master/IDE_Board_Manager/package_sparkfun_index.json

ツール→ボード→ボードマネージャーを開き、SparkFun AVR Boards by SparkFun Electronicsをインストールしておく。
f:id:t-hom:20201121004246p:plain

ツールから、ボードをSparkFun Pro Micro、プロセッサをATMega32U4(3.3V, 8MHz)を選択する。ここでプロセッサの電圧と周波数の選択を間違えるとまた苦労するので注意。
f:id:t-hom:20201121004523p:plain

あとはシリアルポートを選択し、普通に書き込むだけである。

トラブルシューティング

違うボードを選択して書き込んでしまった場合、リセット処理が必要になる。
まずArduino IDEでは正しいボードを選びなおしておく。
それから何も追加コードを書いていない新規のArduinoファイルで書き込み処理を開始し、コンパイルが開始された直後にGNDとRSTをすばやく2回ショートさせる。そうするとArduinoは数秒間だけ書き込み可能になる。ちょうどコンパイルが終わって書き込み開始される頃にリセットが完了している必要があり、シビアなタイミングが要求される。というか運ゲーである。10回に1回くらいは成功するので、さほど悲観に暮れる必要はないが面倒なのでボード選択は間違えないようにしたい。

GNDとRSTをショートさせるのはハンダで輪っかを作るとやりやすい。
f:id:t-hom:20201121010300p:plain

コード

前回から少しバージョンUPして、CTRL+マウスホイールによるズームイン・ズームアウトができるようになったのでコードを掲載しておく。コントローラー中央のプレイステーションボタンを押しながら右ジョイスティックを時計回りに回すとズームイン・半時計回りならズームアウトというコードになっている。

また、画面スクロールもアナログレバー(L2・R2)に割り当てた。押し込み具合によってスクロール量が変わる。

#include <Keyboard.h>
#include <Mouse.h>
#include <PS4USB.h>

// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>

// #include "Arduino.h"
// #include "DFRobotDFPlayerMini.h"
// DFRobotDFPlayerMini myDFPlayer;

USB Usb;
PS4USB PS4(&Usb);

bool printAngle, printTouch;
uint8_t oldL2Value, oldR2Value;
uint8_t oldRightHatX, oldRightHatY;
uint8_t redundant;

void setup() {
  if (Usb.Init() == -1) {
    while (1); // Halt
  }
  Mouse.begin();
  redundant = 1;
  /* Serial1.begin(9600);
  if (!myDFPlayer.begin(Serial1)) {  //Use softwareSerial to communicate with mp3.
    while(true);
  }
  myDFPlayer.volume(30);  //Set volume value. From 0 to 30
  */
}

void loop() {
  Usb.Task();

  if (PS4.connected()) {
    if (PS4.getButtonPress(PS)&& ((abs(128 - PS4.getAnalogHat(RightHatX)) + abs(128 - PS4.getAnalogHat(RightHatX))) >= 127)) {
      //TopRight
      if (PS4.getAnalogHat(RightHatX) >= 128 && PS4.getAnalogHat(RightHatY) < 128 ) {
        if (oldRightHatX + redundant < PS4.getAnalogHat(RightHatX) && oldRightHatY + redundant < PS4.getAnalogHat(RightHatY)) {
          //RightRotate
          Keyboard.press(KEY_LEFT_CTRL);
          Mouse.move(0, 0, 1);
          Keyboard.releaseAll();
        }
        if (oldRightHatX - redundant > PS4.getAnalogHat(RightHatX) && oldRightHatY - redundant > PS4.getAnalogHat(RightHatY)) {
          //LeftRotate
          Keyboard.press(KEY_LEFT_CTRL);
          Mouse.move(0, 0, -1);
          Keyboard.releaseAll();
        }
      }
      
      //BottomRight
      if (PS4.getAnalogHat(RightHatX) >= 128 && PS4.getAnalogHat(RightHatY) >= 128 ) {
        if (oldRightHatX - redundant > PS4.getAnalogHat(RightHatX) && oldRightHatY + redundant < PS4.getAnalogHat(RightHatY)) {
          //RightRotate
          Keyboard.press(KEY_LEFT_CTRL);
          Mouse.move(0, 0, 1);
          Keyboard.releaseAll();
        }
        if (oldRightHatX + redundant < PS4.getAnalogHat(RightHatX) && oldRightHatY - redundant > PS4.getAnalogHat(RightHatY)) {
          //LeftRotate
          Keyboard.press(KEY_LEFT_CTRL);
          Mouse.move(0, 0, -1);
          Keyboard.releaseAll();
        }
      }
      
      //BottomLeft
      if (PS4.getAnalogHat(RightHatX) < 128 && PS4.getAnalogHat(RightHatY) >= 128 ) {
        if (oldRightHatX - redundant > PS4.getAnalogHat(RightHatX) && oldRightHatY - redundant > PS4.getAnalogHat(RightHatY)) {
          //RightRotate
          Keyboard.press(KEY_LEFT_CTRL);
          Mouse.move(0, 0, 1);
          Keyboard.releaseAll();
        }
        if (oldRightHatX + redundant < PS4.getAnalogHat(RightHatX) && oldRightHatY + redundant < PS4.getAnalogHat(RightHatY)) {
          //LeftRotate
          Keyboard.press(KEY_LEFT_CTRL);
          Mouse.move(0, 0, -1);
          Keyboard.releaseAll();
        }
      }
      
      //TopLeft
      if (PS4.getAnalogHat(RightHatX) < 128 && PS4.getAnalogHat(RightHatY) < 128 ) {
        if (oldRightHatX + redundant < PS4.getAnalogHat(RightHatX) && oldRightHatY - redundant > PS4.getAnalogHat(RightHatY)) {
          //RightRotate
          Keyboard.press(KEY_LEFT_CTRL);
          Mouse.move(0, 0, 1);
          Keyboard.releaseAll();
        }
        if (oldRightHatX - redundant > PS4.getAnalogHat(RightHatX) && oldRightHatY + redundant < PS4.getAnalogHat(RightHatY)) {
          //LeftRotate
          Keyboard.press(KEY_LEFT_CTRL);
          Mouse.move(0, 0, -1);
          Keyboard.releaseAll();
        }
      }
    }
    oldRightHatX = PS4.getAnalogHat(RightHatX);
    oldRightHatY = PS4.getAnalogHat(RightHatY);
    
    if (PS4.getAnalogButton(R2)) {
      Keyboard.press(KEY_DOWN_ARROW);
      delay(256-PS4.getAnalogButton(R2));
      Keyboard.releaseAll();
    }
    
    if (PS4.getAnalogButton(L2)) {
      Keyboard.press(KEY_UP_ARROW);
      delay(256-PS4.getAnalogButton(L2));
      Keyboard.releaseAll();
    }

    if (PS4.getButtonClick(PS)) {
      
    }
    
    if (PS4.getButtonClick(TRIANGLE)) {
      Keyboard.press('a');
      delay(100);
      Keyboard.releaseAll();
    }
    if (PS4.getButtonClick(CIRCLE)) {
      Keyboard.press(KEY_LEFT_CTRL);
      Keyboard.press(KEY_LEFT_SHIFT);
      Keyboard.press('1');
      delay(100);
      Keyboard.releaseAll();
    }
    if (PS4.getButtonClick(CROSS)) {
      Keyboard.press(KEY_LEFT_CTRL);
      Keyboard.press('d');
      delay(100);
      Keyboard.releaseAll();
    }
    if (PS4.getButtonClick(SQUARE)) {
    }

    if (PS4.getButtonPress(UP)) {
      Keyboard.press(KEY_PAGE_UP);
      delay(40);
      Keyboard.releaseAll();
    } if (PS4.getButtonClick(RIGHT)) {
      Keyboard.press(KEY_LEFT_CTRL);
      Keyboard.press('.');
      delay(100);
      Keyboard.releaseAll();
    } if (PS4.getButtonPress(DOWN)) {
      Keyboard.press(KEY_PAGE_DOWN);
      delay(40);
      Keyboard.releaseAll();
    } if (PS4.getButtonClick(LEFT)) {
      Keyboard.press(KEY_LEFT_CTRL);
      Keyboard.press(',');
      delay(100);
      Keyboard.releaseAll();
    }

    if (PS4.getButtonClick(L1)) {
      Keyboard.press(KEY_LEFT_CTRL);
      Keyboard.press(',');
      delay(100);
      Keyboard.releaseAll();
      //myDFPlayer.play(1);
    }
    if (PS4.getButtonClick(L3)) {
      
    }
    if (PS4.getButtonClick(R1)) {
      Keyboard.press(KEY_LEFT_CTRL);
      Keyboard.press('.');
      delay(100);
      Keyboard.releaseAll();
      //myDFPlayer.play(2);
    }
    if (PS4.getButtonClick(R3)) {
    }
 
    if (PS4.getButtonClick(SHARE)) {
    }
    if (PS4.getButtonClick(OPTIONS)) {
    }
    if (PS4.getButtonClick(TOUCHPAD)) {
    }
  }
}

以上

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