1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Seeed XIAO RP2350でNintendo Switchコントローラーを作成する

1
Last updated at Posted at 2026-10-26
Page 1 of 2

Seeed XIAO RP2350にいくつかのタクトスイッチとSSD1306のディスプレイを接続した基盤を作成したので、Nintendo Switchのコントローラーを作ってみました。

基板の作成

Seeed XIAO RP2350は画像の左上が0番ピン、左下が6番ピン、右下が7番ピン、右上が13番ピンとなっています。
0~3を左側のタクトスイッチの上、右、下、左と接続し、4番をSSD1306のSDA、5番をSCK(SCL)、6番を基盤の右上、7~10を右側のタクトスイッチの上、右、下、左と接続しました。11番はSSD1306のVDD、12番はGNDでSSD1306のGNDと全てのタクトスイッチの片方(右側)に接続しました。
13番ピンは5Vですがどこにも接続していません。

image.png

機能について

ボタンが9個しかないので、全部のNintendo Switch用のボタンに対応させるには到底足りません。その為、右上のタクトスイッチをファンクションキーとして使用し、この右上を押していないときは左側のタクトスイッチ4個を十字キー、右側のタクトスイッチをプロコン等と同様に上がX、右がA、下がB、左がYとなるようにし、ファンクションキーを押しているときは、左側の上をR、右がZR、下がZL、左がL、右側の上が「+」、右がHOME、下がキャプチャー、左が「ー」となるようにしました。

image.png

ソースコードの作成

Google AIモードを使用して作成しました。
概ね出力されたソースでそのまま動いたのですが、RP2350の場合「Adafruit_SSD1306」の初期化で「&Wire」ではなく「&Wire1」を使う必要があるので、指摘して「&Wire1」に変更してもらっています。また、ZLとL、ZRとRが入れ替わっている現象が起きていたのですが、ビットマスクの指定に誤りがあったらしく、「正しい割り当てに修正」という形で修正されています。他に「con」という余分な文字列が含まれていた部分を指摘し「不要なconを削除済み」というコメントが入っていたりします。
Google AIモードはかなり優秀ですが、ある程度は人間側でコードレビューできた方が良いように思いました。

#include "Adafruit_TinyUSB.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// --- SSD1306 ディスプレイ設定 ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET    -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire1, OLED_RESET);

// --- ピン配置定義 ---
const int PIN_A     = D8;  
const int PIN_B     = D9;  
const int PIN_UP    = D0;
const int PIN_DOWN  = D2;
const int PIN_LEFT  = D3;
const int PIN_RIGHT = D1;
const int PIN_Y     = D10; 
const int PIN_X     = D7;  
const int PIN_FN    = D6;  // Fn(同時押しトリガー)

// --- Switch コントローラーのデータ構造 ---
typedef struct {
  uint16_t Button; 
  uint8_t  Hat;    
  uint8_t  LX;     
  uint8_t  LY;
  uint8_t  RX;
  uint8_t  RY;
  uint8_t  Vendor;
} SwitchReport_t;

// --- 💡 Switch用ボタンビットマスク(正しい割り当てに修正) ---
#define SWITCH_Y       0x0001
#define SWITCH_B       0x0002
#define SWITCH_A       0x0004
#define SWITCH_X       0x0008
#define SWITCH_L       0x0010 // 👈 0x0010 が正しい L ボタン
#define SWITCH_R       0x0020 // 👈 0x0020 が正しい R ボタン
#define SWITCH_ZL      0x0040 // 👈 0x0040 が正しい ZL ボタン
#define SWITCH_ZR      0x0080 // 👈 0x0080 が正しい ZR ボタン
#define SWITCH_MINUS   0x0100
#define SWITCH_PLUS    0x0200
#define SWITCH_HOME    0x1000 
#define SWITCH_CAPTURE 0x2000 

// 十字キー(Hat)の値定義
#define HAT_UP         0x00
#define HAT_UP_RIGHT   0x01
#define HAT_RIGHT      0x02
#define HAT_DOWN_RIGHT 0x03
#define HAT_DOWN       0x04
#define HAT_DOWN_LEFT  0x05
#define HAT_LEFT       0x06
#define HAT_UP_LEFT    0x07
#define HAT_CENTER     0x08

uint8_t const desc_hid_report[] = {
  0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x15, 0x00, 0x25, 0x01, 0x35, 0x00, 0x45, 0x01, 0x75, 0x01,
  0x95, 0x10, 0x05, 0x09, 0x19, 0x01, 0x29, 0x10, 0x81, 0x02, 0x05, 0x01, 0x25, 0x07, 0x46, 0x3b,
  0x01, 0x75, 0x04, 0x95, 0x01, 0x65, 0x14, 0x09, 0x39, 0x81, 0x42, 0x65, 0x00, 0x75, 0x04, 0x95,
  0x01, 0x81, 0x01, 0x26, 0xff, 0x00, 0x46, 0xff, 0x00, 0x75, 0x08, 0x95, 0x04, 0x09, 0x30, 0x09,
  0x31, 0x09, 0x32, 0x09, 0x35, 0x81, 0x02, 0x05, 0x0c, 0x75, 0x08, 0x95, 0x01, 0x09, 0x00, 0xb1,
  0x03, 0xc0
};

Adafruit_USBD_HID usb_hid;
SwitchReport_t joyReport;

void reset_report() {
  joyReport.Button = 0;
  joyReport.Hat = HAT_CENTER;
  joyReport.LX = 128;
  joyReport.LY = 128;
  joyReport.RX = 128;
  joyReport.RY = 128;
  joyReport.Vendor = 0;
}

void setup() {
  int pins[] = {PIN_A, PIN_B, PIN_UP, PIN_DOWN, PIN_LEFT, PIN_RIGHT, PIN_Y, PIN_X, PIN_FN};
  for (int i = 0; i < 9; i++) {
    pinMode(pins[i], INPUT_PULLUP);
  }

  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
    for(;;); 
  }
  
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("Switch Pad Init...");
  display.display();

  TinyUSBDevice.setID(0x0f0d, 0x00c1);
  
  usb_hid.setPollInterval(2);
  usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report));
  usb_hid.begin();

  reset_report();
}

void loop() {
  if (!TinyUSBDevice.ready()) return;

  reset_report();

  String pressedButtons = "";
  String dirText = "CENTER";

  // 各物理ピンの生状態を取得 (LOW = 押されている)
  bool p_a     = (digitalRead(PIN_A) == LOW);
  bool p_b     = (digitalRead(PIN_B) == LOW);
  bool p_up    = (digitalRead(PIN_UP) == LOW);
  bool p_down  = (digitalRead(PIN_DOWN) == LOW);
  bool p_left  = (digitalRead(PIN_LEFT) == LOW);
  bool p_right = (digitalRead(PIN_RIGHT) == LOW);
  bool p_y     = (digitalRead(PIN_Y) == LOW);
  bool p_x     = (digitalRead(PIN_X) == LOW);
  bool p_fn    = (digitalRead(PIN_FN) == LOW); 

  // --- 1. D6(Fn)が押されているときの特殊同時押し判定 ---
  if (p_fn) {
    // 💡 D6 + D3(LEFT) + D0(UP) = L + R 同時押し (競合を防ぐため一番最初に判定)
    if (p_left && p_up) {
      joyReport.Button |= (SWITCH_L | SWITCH_R);
      pressedButtons += "L+R ";
    } 
    // 💡 D6 + D3(LEFT) = L ボタン
    else if (p_left) {
      joyReport.Button |= SWITCH_L;
      pressedButtons += "L ";
    }
    // 💡 D6 + D0(UP) = R ボタン
    else if (p_up) {
      joyReport.Button |= SWITCH_R;
      pressedButtons += "R ";
    }

    // 💡 D6 + D2(DOWN) = ZL ボタン
    if (p_down) {
      joyReport.Button |= SWITCH_ZL;
      pressedButtons += "ZL ";
    }
    // 💡 D6 + D1(RIGHT) = ZR ボタン
    if (p_right) {
      joyReport.Button |= SWITCH_ZR;
      pressedButtons += "ZR ";
    }

    // D6 + D10(Y) = マイナス(-)
    if (p_y) {
      joyReport.Button |= SWITCH_MINUS;
      pressedButtons += "- ";
    }
    // D6 + D7(X) = プラス(+)
    if (p_x) {
      joyReport.Button |= SWITCH_PLUS;
      pressedButtons += "+ ";
    }

    // D6 + D8(A) = HOME
    if (p_a) {
      joyReport.Button |= SWITCH_HOME;
      pressedButtons += "HOME ";
    }
    // D6 + D9(B) = CAPTURE
    if (p_b) {
      joyReport.Button |= SWITCH_CAPTURE;
      pressedButtons += "CAPT ";
    }

  } 
  // --- 2. D6(Fn)が押されていないときの通常判定 ---
  else {
    // 通常のA, B, Y, Xボタン
    if (p_a) { joyReport.Button |= SWITCH_A; pressedButtons += "A "; }
    if (p_b) { joyReport.Button |= SWITCH_B; pressedButtons += "B "; }
    if (p_y) { joyReport.Button |= SWITCH_Y; pressedButtons += "Y "; }
    if (p_x) { joyReport.Button |= SWITCH_X; pressedButtons += "X "; }

    // 通常の十字キー判定(不要なconを削除済み)
    if (p_up && p_right)        { joyReport.Hat = HAT_UP_RIGHT;   dirText = "UP-RIGHT"; }
    else if (p_down && p_right) { joyReport.Hat = HAT_DOWN_RIGHT; dirText = "DN-RIGHT"; }
    else if (p_down && p_left)  { joyReport.Hat = HAT_DOWN_LEFT;  dirText = "DN-LEFT"; }
    else if (p_up && p_left)    { joyReport.Hat = HAT_UP_LEFT;    dirText = "UP-LEFT"; }
    else if (p_up)              { joyReport.Hat = HAT_UP;         dirText = "UP"; }
    else if (p_down)            { joyReport.Hat = HAT_DOWN;       dirText = "DOWN"; }
    else if (p_left)            { joyReport.Hat = HAT_LEFT;       dirText = "LEFT"; }
    else if (p_right)           { joyReport.Hat = HAT_RIGHT;      dirText = "RIGHT"; }
  }

  // --- SSD1306 ディスプレイ表示処理 ---
  display.clearDisplay();
  display.setCursor(0, 0);
  display.setTextSize(1);
  display.println("[ XIAO RP2350 PAD ]");
  display.println("---------------------");
  
  display.print("Pressed: ");
  display.setTextSize(2); 
  display.println(pressedButtons);
  
  display.setTextSize(1);
  display.print("Dir    : ");
  display.setTextSize(2);
  display.println(dirText);
  
  display.display(); 

  // レポート送信
  usb_hid.sendReport(0, &joyReport, sizeof(joyReport));
  delay(5);
}

Arduino IDEでコンパイル、ビルドする方法

以下のサイト等を参考にArduino IDEをインストールし、ボードマネージャで「SEEED XIAO RP2350」を指定します。
https://wiki.seeedstudio.com/ja/xiao_rp2350_arduino/

①基本設定で以下のURLを追加する

https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json

image.png

②「ボードを選択」で「Seeed XIAO RP2350」を選択する。

image.png

③「ツール」「USB Stack」で「Adafruit TinyUSB」を選択する。

image.png

④上記の①~③でコンパイル可能になると思います。
「スケッチ」→「コンパイル済みバイナリをエクスポート」を実行します。

image.png

⑤コンパイルが完了すると、ソースファイルの置いてあるフォルダに「build」フォルダが作成され、その中に「~.uf2」ファイルが作成されます。

image.png

Seeed XIAO RP2350へ書き込む方法

①「B」のボタンを押しながらUSBケーブルを接続します。

image.png

②下記のような形でUSBメモリーと同様にフォルダが開きます。ここに作成したuf2ファイルをドロップしてコピーすると書き込まれます。

image.png

動作確認

スーパーマリオブラザーズで動作確認しました。
十字キーの右とAボタンを押して、マリオがちょっとジャンプしています。

image.png

最後に

Seeed XIAO RP2350は背面にD11~D18の8個の拡張ピンのパッドがあるので、増やそうと思えばあと8つボタンを足すことができます。ただし、多数のボタンが必要な自作アケコン等をつくるのであれば、本家のRaspberry Pi Pico 2を使ったりした方がよさそうです。また、RP2350はアナログ入力が0~2の3個しかないので、アナログ入力を4軸以上使う場合は80ピン版のRP2350Bを搭載したボードを使用する必要があります。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?