X68000Z 用のゲームコントローラーとコンバーターを作る

# はじめに

『X68000Z』で動作するゲームコントローラーとしては、『メガドライブミニ/2』用の『ファイティング 6B』を所持していましたが、X68000 のゲームは格ゲーを除けば 8 方向 + 2 ボタンのゲームコントローラーで動作するものが殆どで、連射装置も欲しい所でした。

X68000Z はゲームコントローラーのボタンをカスタマイズする機能を持っておらず、公式に対応しているのはゲーム機ミニシリーズ用のコントローラーです。

:::note
『X68000Z PRODUCT EDITION (BLACK MODEL)』には専用コントローラー『[X68000 Z ジョイカード (ZKJC-009-BK)](https://www.zuiki.co.jp/x68000z/black/product_detail.html?id=AIF80003)』が付属します。単体でも販売されています。
:::

それ以外の多ボタンコントローラーでは、とんでもない位置のボタンに A ボタンと B ボタンが割り当てられる事があります (2 ボタンなら大丈夫)。『Logicool F310r』のように全く動作しないコントローラーもあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/8ff47268-739e-98e3-06d8-e11904f09a6d.png)

連射機能付きのコントローラーで X68000Z と相性が良さそうだったのは『[HORI ターボパッド for PCエンジン mini](https://hori.jp/products/htg/turbopad/)』でしたが、気付いた時には終売＆プレミア価格でした。

本記事は自身のゲームコントローラー事情をどうにかしようと、X68000Z のコミュニティサイトである [「Z-CLUB」](https://dev.zuiki.com/project-z/community/) に投稿した内容のダイジェスト版です。

 - [連射パッドを持ってないので (その1) (Z-Club)](https://dev.zuiki.com/project-z/community/post/detail/365)
 - [連射パッドを持ってないので (その2) (Z-Club)](https://dev.zuiki.com/project-z/community/post/detail/505)
 - [連射パッドを持ってないので (その3) (Z-Club)](https://dev.zuiki.com/project-z/community/post/detail/509)
 - [連射パッドを持ってないので (その4) (Z-Club)](https://dev.zuiki.com/project-z/community/post/detail/530)
 - [連射パッドを持ってないので (その5) (Z-Club)](https://dev.zuiki.com/project-z/community/post/detail/537)
 - [連射パッドを持ってないので (その6) (Z-Club)](https://dev.zuiki.com/project-z/community/post/detail/542)
 - [連射パッドを持ってないので (その7) (Z-Club)](https://dev.zuiki.com/project-z/community/post/detail/737)

:::note
Z-CLUB の記事はログインしないと閲覧もできません。
:::

# ゲームコントローラーとコンバーター

プレイするゲームは『GRADIUS』を想定しています。

## ■ フットスイッチ

最初は自作の USB フットスイッチに連射機能を付けてみました。USB フットスイッチのマイコンボードは『Da Vinci 32U』です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/d3f21172-beae-65dd-1329-acd7a74db5df.png)

HID キーボードとして振る舞い、ペダルを踏んでいる間 `〔X〕`(ショット) と `〔C〕`(ミサイル) を連射するものです。ゲームコントローラーは別に接続してあり、自機の移動とパワーアップはそちらで行ないます。

 - [Da Vinci 32U (ht-deko.com)](https://ht-deko.com/arduino/davinci32u.html)

### スケッチ

```cpp:KEY_TURBO.ino
#include<Keyboard.h>

/* DIGITAL PIN */
const int BTN_PIN_POWERUP =  2;
const int BTN_PIN_SHOT    =  3;
const int BTN_PIN_MISSILE =  4;

void setup() {
  pinMode(BTN_PIN_POWERUP , INPUT_PULLUP);
  pinMode(BTN_PIN_SHOT    , INPUT_PULLUP);
  pinMode(BTN_PIN_MISSILE , INPUT_PULLUP);
  Keyboard.begin();
}

void loop() {
  /* KEY SEND */
  if (digitalRead(BTN_PIN_SHOT   ) == LOW)
    Keyboard.press('x');
  if (digitalRead(BTN_PIN_MISSILE) == LOW)
    Keyboard.press('c');
  delay(8);
  Keyboard.releaseAll();
  delay(8);
}
```

## ■ コンバーター

X68000Z や MSX で使っていた連射パッドを発掘したので、これを接続できるコンバータを作る事にしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/dad6093a-c430-545d-0861-f31dd524c1e1.png)

マイコンボードには『Pro Micro (5V / 16MHz)』を使う事にしました。

結線は次の通りです。最初のテストに『Arduboy』を使ったため、それに倣っています。

| DSUB9 | Arduino |
|:-:|:-:|
| 1  | A0 |
| 2  | A3 |
| 3  | A2 |
| 4  | A1 |
| 5  | 5V |
| 6  | D7 |
| 7  | D8 |
| 8  | GND |
| 9  | GND |

3D プリンタでケースも作りました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/d1a7a668-e4ea-044d-1905-474d1aad13b7.png)

実機の連射パッドが使えていい感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/ffd5ca61-f8ea-3aa6-794c-d706adacf5d6.png)

原材料費は USB ケーブル抜きで 1,000円くらいでしょうか。

 - [(Arduino) Pro Micro (ht-deko.com)](https://ht-deko.com/arduino/promicro.html)
 - [Adduboy (ht-deko.com)](https://ht-deko.com/arduino/arduboy.html)
 - [D-Sub 9P Hood for Pro Micro (thingiverse.com) ](https://www.thingiverse.com/thing:6067917)

### スケッチ

:::note
・スケッチのコンパイルには『[Arduino Joystick Library 2.x](https://github.com/MHeironimus/ArduinoJoystickLibrary/tree/version-2.0)』が必要です。
::: 

```cpp:JOY_CONV.ino
#include <Joystick.h>

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_GAMEPAD, 2, 1, 
                   false, false, false, false, false, false, 
                   false, false, false, false, false);
    
/* PINS */
const int PIN_UP    = A0;
const int PIN_DOWN  = A3;
const int PIN_LEFT  = A2;
const int PIN_RIGHT = A1;
const int PIN_A     = 7;
const int PIN_B     = 8;

const int16_t Angles[16] = {
    JOYSTICK_HATSWITCH_RELEASE, 180, /* Down */             
    270, /* Left */             225, /* Left-lower */       
     90, /* Right */            135, /* Right-lower */      
    JOYSTICK_HATSWITCH_RELEASE, JOYSTICK_HATSWITCH_RELEASE,  
      0, /* Up */               JOYSTICK_HATSWITCH_RELEASE, 
    315, /* Left-upper */       JOYSTICK_HATSWITCH_RELEASE,  
     45, /* Right-upper */      JOYSTICK_HATSWITCH_RELEASE, 
    JOYSTICK_HATSWITCH_RELEASE, JOYSTICK_HATSWITCH_RELEASE};

void setup() {
  pinMode(PIN_UP   , INPUT_PULLUP);
  pinMode(PIN_DOWN , INPUT_PULLUP);
  pinMode(PIN_LEFT , INPUT_PULLUP);
  pinMode(PIN_RIGHT, INPUT_PULLUP);
  pinMode(PIN_A    , INPUT_PULLUP);
  pinMode(PIN_B    , INPUT_PULLUP);
  Joystick.begin();
}

void loop() {
  /* Arrows */
  int HatState = (!digitalRead(PIN_UP)   << 3) |
                 (!digitalRead(PIN_RIGHT)<< 2) |
                 (!digitalRead(PIN_LEFT) << 1) |
                 (!digitalRead(PIN_DOWN)     );
  Joystick.setHatSwitch(0, Angles[HatState]);
  /* Buttons */
  Joystick.setButton(0, !digitalRead(PIN_A));
  Joystick.setButton(1, !digitalRead(PIN_B));
}
```

上下の同時押し、左右の同時押しはできないようになっています。

:::note
「テストに Arduboy を使った」と書きましたが、このスケッチを『Arduboy』に転送すると『Arduboy』をゲームコントローラーとして使う事ができるようになります (操作性はイマイチですが)。
:::

:::note warn
X68000 / MSX 用ゲームパッドをお持ちの方にオススメですが、『マジカルパッド』等はこのスケッチでは変換できません。
:::

## ■ ゲームコントローラー
コンバーターでは基本的に 2 ボタンしか使えないので、ゲームコントローラー自体を作る事にしました。マイコンボードには『Pro Micro (5V / 3.3V) 』を使う事にしました。ゲームコントローラーのガワは、調達が容易な『スーパーファミコン』用のコントローラーを使う事にしました。程度の良さそうなものがハードオフで一つ 330円でした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/3b0ecfe3-914d-4cf4-3282-b74c17308739.png)

中古のコントローラーは変色している？[Retr0bright](https://togetter.com/li/537939) でキレイになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/2a16d650-1b62-377f-72dc-004b221eb908.png)

USB ケーブルと『ProMicro』を別途用意。『ProMicro』は現在 1,000円前後。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/36c4b8f2-bcf1-1c00-02c6-8e068f3edd4c.png)

このコントローラーは初期型のようです。IC (W545) を剥がして、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/4b02bbb2-54bb-2c35-8123-c3342bbbda8a.png)

『ProMicro』のコネクタを外して、そこへジュンフロン線をハンダ付け。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/bf814d73-6c3e-a86d-fb09-10b16f2ba47d.png)

パターンを剥離してしまったら、リカバリーポイントに接続してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/2d25908c-8a81-a8c3-a8f1-d20ea275faef.png)

USB ケーブルと仮にハンダ付けして動作が確認できたら、ホットボンドで固定。USB ケーブルも熱収縮チューブを被せて本ハンダ付け。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/fde8c0eb-ccc1-1c15-9562-1837c5ea8e07.png)

基板側にハンダ付けして…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/1ab55739-01d6-6405-e91d-82918732e980.png)

『Pro Micro』にもハンダ付けして…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/76614d7b-586e-c5a4-b8fb-17bcb7fb67eb.png)

プログラマブルな USB ゲームコントローラーの完成です！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/307d3b45-7ce9-d67a-a002-dd894fe9528c.png)

原材料費は 1,800円くらいでしょうか。パッドのゴムを交換しても 2,000円チョイで済むと思います。

ベースがゲーム機用のコントローラーなので、操作性はバツグンです。

### 結線 (W545 の場合)
結線の詳細は次の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/f724efd9-3596-794e-5881-0fc9c4d75c78.png)

|Button|W545|Pin|Pro Micro|
|:---|:---:|:---:|:---:|
|UP     |ICA |3  |A0   |
|DOWN   |ICA |4  |A3   |
|LEFT   |ICA |5  |A2   |
|RIGHT  |ICA |6  |A1   |
|A      |ICB |1  |7    |
|B      |ICA |1  |8    |
|X      |ICB |13 |9    |
|Y      |ICA |13 |10   |
|L      |ICB |12 |3    |
|R      |ICB |11 |4    |
|SELECT |ICA |12 |5    |
|START  |ICA |11 |6    |
|(GND)  |ICA |7  |GND  |
|(GND)  |ICB |7  |GND  |

### 結線 (V520 の場合)
結線の詳細は次の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/63a604f1-50a0-310c-9964-5feaebda1572.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/7160b8fa-25c2-e866-3fee-260b48aaac56.png)

|Button|Pin|Pro Micro|
|:---|:---:|:---:|
|UP     |19  |A0   |
|DOWN   |1  |A3   |
|LEFT   |2  |A2   |
|RIGHT  |3  |A1   |
|A      |15  |7    |
|B      |14 |8    |
|X      |16 |9    |
|Y      |13 |10   |
|L      |18 |3    |
|R      |17 |4    |
|SELECT |4 |5    |
|START  |5 |6    |
|(GND)  |10  |GND  |

### リセットボタン
『Pro Micro』はトラブルが起きた時にリセットを行う必要があります。ケースを開けて RST と GND をショートするだけなのですが、コントローラーの実験を行っている際にいちいちケースを開けるのは非効率的です。そこでいくつか作ったコントローラーの一つにリセットボタンを付ける事にしました。

ボトムケースに 4mm の穴を開けます。場所は〇の位置ですが「あぁ、ここか」という位置なので間違える事はないでしょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/052354fe-5486-cdc0-649e-231aa1778abb.png)

タクトスイッチと取り付けステーを用意します。タクトスイッチを交換できるようにしたかったので 3D プリンタでステーを作りましたが、面倒であればホットボンドで固定すればいいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/6c9d18da-5958-7442-e1e2-69431e4fcca4.png)

タクトスイッチを RST と GND に接続。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/31442359-6224-d59d-02c6-d6dc399457b9.png)

取り付けステーはこのように使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/94f2820c-ae9b-44eb-82b0-ea28515342f3.png)

表面からみた所です。操作に支障をきたす事もありませんし、誤って押す事もありません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/113a595d-4e4d-e4ee-b2b9-b24e1797197b.png)

ダブルリセット (リセットボタンの 2 回押しも簡単になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/0cfecd6e-c189-d21e-53c8-fa5bace034c6.png)

### スケッチ (X68000Z 用)
X68000Z での利用に特化したゲームコントローラーのスケッチです。

:::note
・スケッチのコンパイルには『[Arduino Joystick Library 2.x](https://github.com/MHeironimus/ArduinoJoystickLibrary/tree/version-2.0)』が必要です。
・スケッチのコンパイルには『MsTimer2』(ライブラリの追加で検索してください) が必要です。
::: 

```cpp:JOY_X68000Z.ino
#include <Keyboard.h>
#include <Joystick.h>
#include <MsTimer2.h>

// Numeric keypad
#define KEY_NUM_LOCK      0xDB
#define KEY_KP_SLASH      0xDC
#define KEY_KP_ASTERISK   0xDD
#define KEY_KP_MINUS      0xDE
#define KEY_KP_PLUS       0xDF
#define KEY_KP_ENTER      0xE0
#define KEY_KP_1          0xE1
#define KEY_KP_2          0xE2
#define KEY_KP_3          0xE3
#define KEY_KP_4          0xE4
#define KEY_KP_5          0xE5
#define KEY_KP_6          0xE6
#define KEY_KP_7          0xE7
#define KEY_KP_8          0xE8
#define KEY_KP_9          0xE9
#define KEY_KP_0          0xEA
#define KEY_KP_DOT        0xEB

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_GAMEPAD, 2, 1, 
                   false, false, false, false, false, false, 
                   false, false, false, false, false);
    
/* PINS */
const int PIN_UP    = A0;
const int PIN_DOWN  = A3;
const int PIN_LEFT  = A2;
const int PIN_RIGHT = A1;
const int PIN_A     =  7;
const int PIN_B     =  8;
const int PIN_X     =  9;
const int PIN_Y     = 10;
const int PIN_L     =  3;
const int PIN_R     =  4;
const int PIN_SEL   =  5;
const int PIN_START =  6;

const int16_t Angles[16] = {
    JOYSTICK_HATSWITCH_RELEASE, 180, /* Down */             
    270, /* Left */             225, /* Left-lower */       
     90, /* Right */            135, /* Right-lower */      
    JOYSTICK_HATSWITCH_RELEASE, JOYSTICK_HATSWITCH_RELEASE,  
      0, /* Up */               JOYSTICK_HATSWITCH_RELEASE, 
    315, /* Left-upper */       JOYSTICK_HATSWITCH_RELEASE,  
     45, /* Right-upper */      JOYSTICK_HATSWITCH_RELEASE, 
    JOYSTICK_HATSWITCH_RELEASE, JOYSTICK_HATSWITCH_RELEASE};

static bool pressState   = false;
static bool prev_state_L = false;
static bool prev_state_R = false;
static bool state_L      = false;
static bool state_R      = false;

int gameMode = 0; // 汎用 (GameCard)

void setup() {
  pinMode(PIN_UP   , INPUT_PULLUP);
  pinMode(PIN_DOWN , INPUT_PULLUP);
  pinMode(PIN_LEFT , INPUT_PULLUP);
  pinMode(PIN_RIGHT, INPUT_PULLUP);
  pinMode(PIN_A    , INPUT_PULLUP);
  pinMode(PIN_B    , INPUT_PULLUP);
  pinMode(PIN_X    , INPUT_PULLUP);
  pinMode(PIN_Y    , INPUT_PULLUP);
  pinMode(PIN_L    , INPUT_PULLUP);
  pinMode(PIN_R    , INPUT_PULLUP);
  pinMode(PIN_SEL  , INPUT_PULLUP);
  pinMode(PIN_START, INPUT_PULLUP);
  delay(100);

  gameMode = (!digitalRead(PIN_START) << 5) |
             (!digitalRead(PIN_SEL)   << 4) |
             (!digitalRead(PIN_Y)     << 3) |
             (!digitalRead(PIN_X)     << 2) |
             (!digitalRead(PIN_B)     << 1) |
             (!digitalRead(PIN_A)         );
  
  Keyboard.begin();  
  Joystick.begin();

  MsTimer2::set(31, toggleSwitch);
  MsTimer2::start();  
}

void loop() {
  /* Arrows */
  int HatState = (!digitalRead(PIN_UP)   << 3) |
                 (!digitalRead(PIN_RIGHT)<< 2) |
                 (!digitalRead(PIN_LEFT) << 1) |
                 (!digitalRead(PIN_DOWN)     );
  Joystick.setHatSwitch(0, Angles[HatState]);
  /* Buttons */
  bool state_A     = !digitalRead(PIN_A);
  bool state_B     = !digitalRead(PIN_B);
  bool state_X     = !digitalRead(PIN_X);
  bool state_Y     = !digitalRead(PIN_Y);
  prev_state_L     = state_L;
       state_L     = !digitalRead(PIN_L);
  prev_state_R     = state_R;
       state_R     = !digitalRead(PIN_R);
  bool state_SEL   = !digitalRead(PIN_SEL);
  bool state_START = !digitalRead(PIN_START);
  
  if (state_X) { 
    Joystick.setButton(0, pressState);
  } else {  
    Joystick.setButton(0, state_A);
  }
  if (state_Y) { 
    Joystick.setButton(1, pressState);
  } else { 
    Joystick.setButton(1, state_B);
  }  

  // LR 同時押しでソフトリセット
  if (prev_state_L && prev_state_R && state_L && state_R) { 
    Keyboard.press(KEY_LEFT_CTRL);
    Keyboard.press(KEY_F11);
    Keyboard.press(KEY_DELETE);
    delay(100);
    Keyboard.releaseAll();
    delay(500);
    return;
  }

  switch (gameMode % 2){
    case 0: // 汎用 (GameCard) 
      // R でポーズメニュー
      if (prev_state_R && state_R) {
        Keyboard.press(96);
        Keyboard.releaseAll();
        delay(500);
        return;    
      }
      break;
    case 1: // [A] 汎用 (ポーズメニューなし)
      break;
  }
  
  switch (gameMode){
    case 2: // [B]    GRADIUS (GameCard) 
    case 3: // [B][A] GRADIUS (ポーズメニューなし)
      // START で一時停止/解除
      if (state_START) { 
        Keyboard.press(KEY_ESC);
        delay(100);
        Keyboard.releaseAll();
        delay(100);
        return;
      }
      // L でコンティニュー
      if (prev_state_L && state_L) { 
        Keyboard.press(KEY_KP_8);
        delay(100);
        Keyboard.releaseAll();
        Keyboard.press(KEY_KP_2);
        delay(100);
        Keyboard.releaseAll();
        Keyboard.press(KEY_KP_4);
        delay(100);
        Keyboard.releaseAll();
        Keyboard.press(KEY_KP_6);
        delay(100);
        Keyboard.releaseAll();
        Keyboard.press('x');
        delay(100);
        Keyboard.releaseAll();
        Keyboard.press('z');
        delay(100);
        Keyboard.releaseAll();
        Keyboard.press(KEY_KP_8);
        delay(100);
        Keyboard.releaseAll();
        Keyboard.press(KEY_KP_2);
        delay(100);
        Keyboard.releaseAll();
        delay(500);
        return;
      }
      break;
    case 4: // [X]    超連射68K (GameCard) 
    case 5: // [X][A] 超連射68K (ポーズメニューなし)
      // SELECT でメニューの選択
      if (state_SEL) { 
        Keyboard.press(KEY_TAB);
        delay(100);
        Keyboard.releaseAll();
        delay(100);
        return;
      }
      // START でメニューの決定、一時停止/解除
      if (state_START) { 
        Keyboard.press(KEY_ESC);
        delay(100);
        Keyboard.releaseAll();
        delay(100);
        return;
      }
      break;
  }
}

void toggleSwitch() {
  pressState = !pressState;
}
```

連射ボタン付きの 2 ボタンコントローラーとして認識されます。方向キー+２ボタン以外の機能は HID キーボードとして振る舞う事で実現しています。

| ボタン | 機能 |
|:---:|:---|
| 十字キー | 移動 |
| A | A ボタン |
| B | B ボタン |
| X | A ボタン連射 |
| Y | B ボタン連射 |
| L+R | ソフトリセット |

USB コネクタを挿す際に特定のボタンを押しっぱなしにしていると、そのゲームに適したプロファイルが使われます。リセットボタンを実装しているのなら、特定のボタンを押しながらリセットボタンを押しても OK です。

| No | 起動時 | 対象 | SELECT | START | L | R |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 0 | (なし) | 汎用 |   |   |   | ポーズメニュー |
| 1 | [A]  | 汎用 |   |   |   |   |
| 2 | [B]  | GRADIUS |   | 一時停止/解除 | コンティニューコマンド | ポーズメニュー |
| 3 | [B][A]  | GRADIUS |   | 一時停止/解除 | コンティニューコマンド |  |
| 4 | [X] | 超連射68K  |  メニューの選択 | メニューの決定<br>一時停止/解除  |   | ポーズメニュー |
| 5 | [X][A] | 超連射68K  | メニューの選択  | メニューの決定<br>一時停止/解除  |   |   |
| 6 | [X][B] | (未登録) |  |  |   | ポーズメニュー |
| 7 | [X][B][A] | (未登録) |  |  |   | |
| 8 | [Y] | (未登録) |  |  |   | ポーズメニュー |
| 9 | [Y][A] | (未登録) |  |  |   | |

ポーズメニューは`〔BREAK〕`キーで出るので、GameCard 起動でないゲームで `[R]` ボタンを押した場合には最悪ゲームが強制終了されてしまいます。例えば、`[A]` ボタンを押しながらケーブルを挿すと、`[R]` ボタンによるポーズメニューが出なくなり、事故を防止する事ができます。

### スケッチ (X68000Z ジョイカード互換)
『X68000Z ジョイカード (ZKJC-009-BK)』互換の 12 ボタン (使えないボタンあり) コントローラーです。瑞起のミニ系ゲーム機用コントローラと同じ配置です。但し、方向キーは POV (Hat) で実装してあります。

:::note
・スケッチのコンパイルには『[Arduino Joystick Library 2.x](https://github.com/MHeironimus/ArduinoJoystickLibrary/tree/version-2.0)』が必要です。
::: 

```cpp:JOY_X68000Z_COMPATIBLE.ino
#include <Joystick.h>
#include <MsTimer2.h>

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_GAMEPAD, 12, 1, 
                   false, false, false, false, false, false, 
                   false, false, false, false, false);
    
/* PINS */
const int PIN_UP    = A0;
const int PIN_DOWN  = A3;
const int PIN_LEFT  = A2;
const int PIN_RIGHT = A1;
const int PIN_A     =  7;
const int PIN_B     =  8;
const int PIN_X     =  9;
const int PIN_Y     = 10;
const int PIN_L     =  3;
const int PIN_R     =  4;
const int PIN_SEL   =  5;
const int PIN_START =  6;

const int16_t Angles[16] = {
    JOYSTICK_HATSWITCH_RELEASE, 180, /* Down */             
    270, /* Left */             225, /* Left-lower */       
     90, /* Right */            135, /* Right-lower */      
    JOYSTICK_HATSWITCH_RELEASE, JOYSTICK_HATSWITCH_RELEASE,  
      0, /* Up */               JOYSTICK_HATSWITCH_RELEASE, 
    315, /* Left-upper */       JOYSTICK_HATSWITCH_RELEASE,  
     45, /* Right-upper */      JOYSTICK_HATSWITCH_RELEASE, 
    JOYSTICK_HATSWITCH_RELEASE, JOYSTICK_HATSWITCH_RELEASE};

static bool pressState = false;

void setup() {
  pinMode(PIN_UP   , INPUT_PULLUP);
  pinMode(PIN_DOWN , INPUT_PULLUP);
  pinMode(PIN_LEFT , INPUT_PULLUP);
  pinMode(PIN_RIGHT, INPUT_PULLUP);
  pinMode(PIN_A    , INPUT_PULLUP);
  pinMode(PIN_B    , INPUT_PULLUP);
  pinMode(PIN_X    , INPUT_PULLUP);
  pinMode(PIN_Y    , INPUT_PULLUP);
  pinMode(PIN_L    , INPUT_PULLUP);
  pinMode(PIN_R    , INPUT_PULLUP);
  pinMode(PIN_SEL  , INPUT_PULLUP);
  pinMode(PIN_START, INPUT_PULLUP);
  Joystick.begin();

  MsTimer2::set(31, toggleSwitch);
  MsTimer2::start();  
}

void loop() {
  /* Arrows */
  int HatState = (!digitalRead(PIN_UP)   << 3) |
                 (!digitalRead(PIN_RIGHT)<< 2) |
                 (!digitalRead(PIN_LEFT) << 1) |
                 (!digitalRead(PIN_DOWN)     );
  Joystick.setHatSwitch(0, Angles[HatState]);
  /* Buttons */
  bool state_A     = !digitalRead(PIN_A);
  bool state_B     = !digitalRead(PIN_B);
  bool state_X     = !digitalRead(PIN_X);
  bool state_Y     = !digitalRead(PIN_Y);
  bool state_L     = !digitalRead(PIN_L);
  bool state_R     = !digitalRead(PIN_R);
  bool state_SEL   = !digitalRead(PIN_SEL);
  bool state_START = !digitalRead(PIN_START);
  
  if (state_X) { 
    Joystick.setButton(2, pressState);
  } else {  
    Joystick.setButton(2, state_A);
  }
  if (state_Y) { 
    Joystick.setButton(1, pressState);
  } else { 
    Joystick.setButton(1, state_B);
  }  
  Joystick.setButton(4, state_L);
  Joystick.setButton(5, state_R);
  Joystick.setButton(8, state_SEL);
  Joystick.setButton(9, state_START);
}

void toggleSwitch() {
  pressState = !pressState;
}
```

| ボタン | 機能 |
|:---:|:---|
| 十字キー | 移動 |
| A | ボタン 3 |
| B | ボタン 2 |
| L | ボタン 5 |
| R | ボタン 6 |
| SELECT | ボタン 9  |
| START | ボタン 10  |

X / Y ボタンは A / B ボタンの連射となっています。

| ボタン | 機能 |
|:---:|:---|
| X | A ボタン連射 |
| Y | B ボタン連射 |

### スケッチ (汎用)
こちらは汎用の 8 ボタンコントローラーです。ボタンの動作テストにはこちらの方がいいかもしれません。

:::note
・スケッチのコンパイルには『[Arduino Joystick Library 2.x](https://github.com/MHeironimus/ArduinoJoystickLibrary/tree/version-2.0)』が必要です。
::: 

```cpp:JOY_GENERAL.ino
#include <Joystick.h>

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_GAMEPAD, 8, 1, 
                   false, false, false, false, false, false, 
                   false, false, false, false, false);
    
/* PINS */
const int PIN_UP    = A0;
const int PIN_DOWN  = A3;
const int PIN_LEFT  = A2;
const int PIN_RIGHT = A1;
const int PIN_A     =  7;
const int PIN_B     =  8;
const int PIN_X     =  9;
const int PIN_Y     = 10;
const int PIN_L     =  3;
const int PIN_R     =  4;
const int PIN_SEL   =  5;
const int PIN_START =  6;

const int16_t Angles[16] = {
    JOYSTICK_HATSWITCH_RELEASE, 180, /* Down */             
    270, /* Left */             225, /* Left-lower */       
     90, /* Right */            135, /* Right-lower */      
    JOYSTICK_HATSWITCH_RELEASE, JOYSTICK_HATSWITCH_RELEASE,  
      0, /* Up */               JOYSTICK_HATSWITCH_RELEASE, 
    315, /* Left-upper */       JOYSTICK_HATSWITCH_RELEASE,  
     45, /* Right-upper */      JOYSTICK_HATSWITCH_RELEASE, 
    JOYSTICK_HATSWITCH_RELEASE, JOYSTICK_HATSWITCH_RELEASE};

boolean pressState = false;

void setup() {
  pinMode(PIN_UP   , INPUT_PULLUP);
  pinMode(PIN_DOWN , INPUT_PULLUP);
  pinMode(PIN_LEFT , INPUT_PULLUP);
  pinMode(PIN_RIGHT, INPUT_PULLUP);
  pinMode(PIN_A    , INPUT_PULLUP);
  pinMode(PIN_B    , INPUT_PULLUP);
  pinMode(PIN_X    , INPUT_PULLUP);
  pinMode(PIN_Y    , INPUT_PULLUP);
  pinMode(PIN_L    , INPUT_PULLUP);
  pinMode(PIN_R    , INPUT_PULLUP);
  pinMode(PIN_SEL  , INPUT_PULLUP);
  pinMode(PIN_START, INPUT_PULLUP);
  Joystick.begin();
}

void loop() {
  /* Arrows */
  int HatState = (!digitalRead(PIN_UP)   << 3) |
                 (!digitalRead(PIN_RIGHT)<< 2) |
                 (!digitalRead(PIN_LEFT) << 1) |
                 (!digitalRead(PIN_DOWN)     );
  Joystick.setHatSwitch(0, Angles[HatState]);
  /* Buttons */
  Joystick.setButton(0, !digitalRead(PIN_A    ));
  Joystick.setButton(1, !digitalRead(PIN_B    ));
  Joystick.setButton(2, !digitalRead(PIN_X    ));
  Joystick.setButton(3, !digitalRead(PIN_Y    ));
  Joystick.setButton(4, !digitalRead(PIN_L    ));
  Joystick.setButton(5, !digitalRead(PIN_R    ));
  Joystick.setButton(6, !digitalRead(PIN_SEL  ));
  Joystick.setButton(7, !digitalRead(PIN_START));
}
```

| ボタン | 機能 |
|:---:|:---|
| 十字キー | 移動 |
| A | ボタン 1 |
| B | ボタン 2 |
| X | ボタン 3 |
| Y | ボタン 4 |
| L | ボタン 5 |
| R | ボタン 6 |
| SELECT | ボタン 7  |
| START | ボタン 8  |


### スケッチ (XInput 連射)
X68000Z では使えませんが、XInput 方式の連射コントローラーです。Steam なんかで使えます。

:::note
・スケッチのコンパイルには『MsTimer2』(ライブラリの追加で検索してください) が必要です。
・コンパイルには『[Arduino XInput Library](https://github.com/dmadison/ArduinoXInput)』ライブラリが必要です。
・ボードマネージャで『[SparkFun AVR Boards w/ XInput](https://github.com/dmadison/ArduinoXInput_SparkFun)』をインストールする必要があります。
・2 回目以降の書き込みにはリセットのダブルクリックが必要です (要リセットボタン)。
::: 

```cpp:JOY_XINPUT_TURBO.ino
#include <XInput.h>
#include <MsTimer2.h>

// Setup
const boolean UseLeftJoystick   = false;  // set to true to enable left joystick
const boolean InvertLeftYAxis   = false;  // set to true to use inverted left joy Y
const boolean UseRightJoystick  = false;  // set to true to enable right joystick
const boolean InvertRightYAxis  = false;  // set to true to use inverted right joy Y
const boolean UseTriggerButtons = true;   // set to false if using analog triggers

/* PINS */
const int PIN_UP    = A0;
const int PIN_DOWN  = A3;
const int PIN_LEFT  = A2;
const int PIN_RIGHT = A1;
const int PIN_A     =  7;
const int PIN_B     =  8;
const int PIN_X     =  9;
const int PIN_Y     = 10;
const int PIN_L     =  3;
const int PIN_R     =  4;
const int PIN_SEL   =  5;
const int PIN_START =  6;

static bool pressState   = false;

void setup() {
  pinMode(PIN_UP   , INPUT_PULLUP);
  pinMode(PIN_DOWN , INPUT_PULLUP);
  pinMode(PIN_LEFT , INPUT_PULLUP);
  pinMode(PIN_RIGHT, INPUT_PULLUP);
  pinMode(PIN_A    , INPUT_PULLUP);
  pinMode(PIN_B    , INPUT_PULLUP);
  pinMode(PIN_X    , INPUT_PULLUP);
  pinMode(PIN_Y    , INPUT_PULLUP);
  pinMode(PIN_L    , INPUT_PULLUP);
  pinMode(PIN_R    , INPUT_PULLUP);
  pinMode(PIN_SEL  , INPUT_PULLUP);
  pinMode(PIN_START, INPUT_PULLUP);

  XInput.setAutoSend(true);
  XInput.begin();
  
  MsTimer2::set(31, toggleSwitch);
  MsTimer2::start();  
}

void loop() {
  /* Arrows */
  bool state_AU     = !digitalRead(PIN_UP);
  bool state_AR     = !digitalRead(PIN_RIGHT);
  bool state_AL     = !digitalRead(PIN_LEFT);
  bool state_AD     = !digitalRead(PIN_DOWN);

  if ((state_AU && state_AD) || (state_AL && state_AR)) { 
    XInput.setDpad(false, false, false, false, false);
  } else {
    XInput.setDpad(state_AU, state_AD, state_AL, state_AR, false);
  }
  
  /* Buttons */
  bool state_A     = !digitalRead(PIN_A);
  bool state_B     = !digitalRead(PIN_B);
  bool state_X     = !digitalRead(PIN_X);
  bool state_Y     = !digitalRead(PIN_Y);
  bool state_L     = !digitalRead(PIN_L);
  bool state_R     = !digitalRead(PIN_R);
  bool state_SEL   = !digitalRead(PIN_SEL);
  bool state_START = !digitalRead(PIN_START);
  
  if (state_X) { 
    XInput.setButton(BUTTON_A, pressState);
  } else {  
    XInput.setButton(BUTTON_A, state_A);
  }
  if (state_Y) { 
    XInput.setButton(BUTTON_B, pressState);
  } else { 
    XInput.setButton(BUTTON_B, state_B);
  }  
  XInput.setButton(BUTTON_BACK , state_SEL  );
  XInput.setButton(BUTTON_START, state_START);
  XInput.setButton(BUTTON_LB   , state_L    );
  XInput.setButton(BUTTON_RB   , state_R    );
}

void toggleSwitch() {
  pressState = !pressState;
}
```

| ボタン | 機能 |
|:---:|:---|
| (なし) | ロゴボタン |
| A | A ボタン |
| B | B ボタン |
| X | A ボタン連射 |
| Y | B ボタン連射 |
| (なし) | X ボタン |
| (なし) | Y ボタン |
| L | L ボタン |
| R | R ボタン |
| SELECT | Back ボタン |
| START | Start ボタン |
| (なし) | L3 ボタン [^1] |
| (なし) | R3 ボタン [^2] |
| 十字キー | デジタル方向パッド |
| (なし) | L トリガー |
| (なし) | R トリガー |
| (なし) | アナログ方向スティック L |
| (なし) | アナログ方向スティック R |

**See also:**

 - [Windows アプリケーション上の XInput の概要 (learn.microsoft.com)](https://learn.microsoft.com/ja-jp/windows/win32/xinput/getting-started-with-xinput)
 - [Pro Micro を Xinput コントローラーとして動作させる (Qiita: @sh00t)](https://qiita.com/sh00t/items/fee21d8d893bf134ac4f)

### スケッチ (XInput 汎用)
X68000Z では使えませんが、XInput 方式の汎用コントローラーです。Steam なんかで使えます。

:::note
・コンパイルには『[Arduino XInput Library](https://github.com/dmadison/ArduinoXInput)』ライブラリが必要です。
・ボードマネージャで『[SparkFun AVR Boards w/ XInput](https://github.com/dmadison/ArduinoXInput_SparkFun)』をインストールする必要があります。
・2 回目以降の書き込みにはリセットのダブルクリックが必要です (要リセットボタン)。
::: 


```cpp:JOY_XINPUT_GENERAL.ino
#include <XInput.h>

// Setup
const boolean UseLeftJoystick   = false;  // set to true to enable left joystick
const boolean InvertLeftYAxis   = false;  // set to true to use inverted left joy Y
const boolean UseRightJoystick  = false;  // set to true to enable right joystick
const boolean InvertRightYAxis  = false;  // set to true to use inverted right joy Y
const boolean UseTriggerButtons = true;   // set to false if using analog triggers

/* PINS */
const int PIN_UP    = A0;
const int PIN_DOWN  = A3;
const int PIN_LEFT  = A2;
const int PIN_RIGHT = A1;
const int PIN_A     =  7;
const int PIN_B     =  8;
const int PIN_X     =  9;
const int PIN_Y     = 10;
const int PIN_L     =  3;
const int PIN_R     =  4;
const int PIN_SEL   =  5;
const int PIN_START =  6;

void setup() {
  pinMode(PIN_UP   , INPUT_PULLUP);
  pinMode(PIN_DOWN , INPUT_PULLUP);
  pinMode(PIN_LEFT , INPUT_PULLUP);
  pinMode(PIN_RIGHT, INPUT_PULLUP);
  pinMode(PIN_A    , INPUT_PULLUP);
  pinMode(PIN_B    , INPUT_PULLUP);
  pinMode(PIN_X    , INPUT_PULLUP);
  pinMode(PIN_Y    , INPUT_PULLUP);
  pinMode(PIN_L    , INPUT_PULLUP);
  pinMode(PIN_R    , INPUT_PULLUP);
  pinMode(PIN_SEL  , INPUT_PULLUP);
  pinMode(PIN_START, INPUT_PULLUP);

  XInput.setAutoSend(true);
  XInput.begin();
}

void loop() {
  /* Arrows */
  bool state_AU     = !digitalRead(PIN_UP);
  bool state_AR     = !digitalRead(PIN_RIGHT);
  bool state_AL     = !digitalRead(PIN_LEFT);
  bool state_AD     = !digitalRead(PIN_DOWN);

  if ((state_AU && state_AD) || (state_AL && state_AR)) { 
    XInput.setDpad(false, false, false, false, false);
  } else {
    XInput.setDpad(state_AU, state_AD, state_AL, state_AR, false);
  }
  
  /* Buttons */
  XInput.setButton(BUTTON_A    , !digitalRead(PIN_A    ));
  XInput.setButton(BUTTON_B    , !digitalRead(PIN_B    ));
  XInput.setButton(BUTTON_X    , !digitalRead(PIN_X    ));
  XInput.setButton(BUTTON_Y    , !digitalRead(PIN_Y    ));
  XInput.setButton(BUTTON_LB   , !digitalRead(PIN_L    ));
  XInput.setButton(BUTTON_RB   , !digitalRead(PIN_R    ));
  XInput.setButton(BUTTON_BACK , !digitalRead(PIN_SEL  ));
  XInput.setButton(BUTTON_START, !digitalRead(PIN_START));
}
```

| ボタン | 機能 |
|:---:|:---|
| (なし) | ロゴボタン |
| A | A ボタン |
| B | B ボタン |
| X | X ボタン |
| Y | Y ボタン |
| L | L ボタン |
| R | R ボタン |
| SELECT | Back ボタン |
| START | Start ボタン |
| (なし) | L3 ボタン [^1] |
| (なし) | R3 ボタン [^2] |
| 十字キー | デジタル方向パッド |
| (なし) | L トリガー |
| (なし) | R トリガー |
| (なし) | アナログ方向スティック L |
| (なし) | アナログ方向スティック R |

# おわりに

『[X68000Z PRODUCT EDITION (BLACK MODEL)](https://www.zuiki.co.jp/x68000z/black/)』から『[X68000 Z ジョイカード](https://www.zuiki.co.jp/x68000z/black/product_detail.html?id=AIF80003)』が付属し、単体で購入もできるようになったので、ゲームコントローラーの問題は解消されつつあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/4006523e-8cd7-6a86-0b93-55a9061f7a19.png)
『Hacker's Edition』『Early Access Kit』ユーザーの方は一つ確保しておく事をオススメします。

**See also:**

 - [Arduino Leonardo(Pro Micro)を利用した旧型パッド(DB9)USB接続アダプタ(X68000Z用)｜ichibey](https://note.com/ichibey/n/n328b74947302)
 - [レトロゲーマーに送る～スーパーファミコンコントローラをPCで使う方法～ (Qiita: @odanny)](https://qiita.com/odanny/items/686d2e3f2061c12f156f)
 - [じょいぽーとＵ君 (TNB製作所)](http://tnb216.html.xdomain.jp/%E3%81%98%E3%82%87%E3%81%84%E3%81%BD%E3%83%BC%E3%81%A8U%E5%90%9B.html)
 - [昔のゲーム用コントローラでメガドライブミニを楽しみたい まとめ (HD61700 SPIRITS)](http://hd61700.yukimizake.net/RETRO-GAMEPAD/Retro06.htm)
 - [X68000Z版グラディウスをXboxコントローラーでSteam版グラディウス風の操作で遊ぶ (Z-Club: @nobu24)](https://dev.zuiki.com/project-z/community/post/detail/563)

[^1]: 左アナログ方向スティック押し込み
[^2]: 右アナログ方向スティック押し込