はじめに
こんにちは。IIDX, BMS用のコントローラーを作ってみました。
実は、以前にも作ったことがあるのですが(下の画像)、出来が悪かったので作り直してみました。

様々な方が素晴らしいコントローラーを作られていますが、今回はそれらを使わずに、ほぼ自分だけで頑張ってみようと思います。
CADも回路もコーディングも入門レベルなので、そこらへんのご理解はお願いします。
仕様決め
部品選定
主要な部品を抜粋
- ボタン
以前作ったコントローラーのものを流用します。アリエクで買った中華ボタンです。
かなり前に買ったものなので、商品ページは消えていました。
バネは三和の20gのものを買いました。
- マイクロスイッチ
Omron VX-01-1C23を使います。
- ロータリーエンコーダ
スクラッチ部に使う部品です。回転を検知するものです。
ターンテーブルを固定するためのカプラも一緒に購入します。
これもアリエクで買いました。
600ppr, 6mmのものです。
- LED
ボタンのLEDは電子工作でよく使う小さいやつを使いました。家に転がっていたやつ。
スクラッチのLEDは円形のものをアリエクで購入しました。フルカラーLEDが60個ついていますが、そんなにいらない気はします。この大きさのままLEDが少ないものが欲しかったのですが、見つからなかったので諦めました。
- 外装
セリアのMDF(30cm x 30cm)とホムセンの安い木材を使おうと思います。
ターンテーブル部は3Dプリンタで作ります。
- マイコン
ボタンの押下やスクラッチの検知などをPCに伝えるためのものです。
Raspberry Pi Pico (RP2040)を使います。
外装設計
とりあえず大まかに外装を設計しました。


本当はスクラッチ部をフルサイズにしたかったのですが、自分の3Dプリンタの最大造形サイズを超えてしまうため諦めました。直径19cmなので、DAO FP7と同じぐらいのサイズ感になる予定です。
いろいろ加工する
仕様がある程度決まったので、さっそく作業をしていきます。
外装づくり
レーザー加工機で天板の加工をします。


外枠も作っておきます。
1×4のSPF材をホームセンターで買ってきました。
木工用接着剤でくっつけます。

とりあえず出来たものを組み合わせてみました。いい感じかも?
MDFは水に弱く塗料を吸ってしまうようなので、防水材を天板に塗ってみました。

スクラッチ部づくり
こんな感じの構造にしてみました。
LEDの光を通したいので、下部5mm程度は光を通す素材で作ります。

これらの部品を3Dプリンターで印刷していきます。
LEDの光を通してほしい部分だけPETGで印刷し、それ以外はPLA+で印刷しました。


スクラッチの抵抗を増やすために、ハードフェルトをなかにいれておきます。

できたものがこちら。LEDも光らせてみました。

塗装するよ
塗装過程はまじで1枚も撮っていませんでした。まじかよ。
完成したものがこちら!

ムラができていますが、自分用なので気にしない。
塗料は100均のものを使っています。(防水材除く)
防水材→ プライマー → 水性塗料 の順で塗りました。
天板固定用の鬼目ナットもついでに入れておきました。

くそだるい配線作業
配線作業です。これも完成品の写真しか撮っていませんでした。
配線後の画像はこちら。

画像左側のものがボタン類とRaspberry Pi Picoです。
マイクロスイッチ用のケーブルは平型187端子, LED用の端子は平型250端子で接続します。
ボタンのLEDはマイクロスイッチに繋がず、マイコン側に繋いで制御することにしました。
こうすることで、常時点灯なども可能になります。
RP2040はGPIOが多くてありがたいです。
画像右側の真ん中あたりに見えるのがロータリーエンコーダです。
今回購入したロータリーエンコーダは5Vで動作するものなので、Picoにそのまま繋いではいけません。(Pico側は3.3V)
また、フルカラーLED (WS2812B)も5V動作のため、信号線を直接繋いではいけません。
そのため、電圧を変換してあげる必要があります。今回はMOSFETを使ったロジックレベルシフトで変換しました。
配線の対応表はこんな感じ。
GPIO番号は仮のものです。
| Pico側 | 中間配線 | エンコーダ側 |
|---|---|---|
| VBUS (5V) | – | VCC (5V) |
| GPIO12 (3.3V) | レベルシフタ | A相 (5V) |
| GPIO13 (3.3V) | レベルシフタ | B相 (5V) |
| GND | – | 0V |
| GND | – | GND |
| Pico側 | 中間配線 | フルカラーLED側 |
|---|---|---|
| (5Vの外部電源推奨) | – | 5V |
| GPIO27 (3.3V) | レベルシフタ | DIN (5V) |
| GND | – | GND |
画像右端にみえるのがレベルシフトモジュールとUSB-Cレセプタクルです。
コントローラーとPCに繋ぐケーブルは着脱できるような仕組みになっています。
USB-CのレセプタクルをPicoに繋ぎ、Type-C接続ができるようにしています。
USB-Cの受電側はCCピンを5.1kΩ抵抗でプルダウンしなければならないことに気を付けてください。
フルカラーLEDは電力消費量が大きいので、電源を分けた方が良いと思います。
今回はUSB-Cレセプタクルを2つ用意し、片方をLED専用にしました。
データ通信用と、LEDの電力供給用です。DAOコンもこんな感じだったよね。たぶん。
データ通信用USBポートの配線はこんな感じ。
USBのGNDはTP1に繋いだほうがいいらしいです。
| Pico側 | 中間配線 | USB-C側 |
|---|---|---|
| VBUS | – | VCC |
| TP3 | – | D+ |
| TP2 | – | D- |
| GND | 5.1kΩ抵抗 | CC1 |
| GND | 5.1kΩ抵抗 | CC2 |
| TP1 | – | GND |
配線例 (一部省略)
| Pico側 | 中間配線 | 部品 |
|---|---|---|
| GPIO2 | – | B1 (ボタン1) |
| GPIO3 | – | B2 |
| GPIO4 | – | B3 |
| GPIO5 | – | B4 |
| GPIO6 | – | B5 |
| GPIO7 | – | B6 |
| GPIO8 | – | B7 |
| GPIO10 | – | B8 (START) |
| GPIO11 | – | B9 (SELECT) |
| GPIO12 | レベルシフタ | エンコーダA相 |
| GPIO13 | レベルシフタ | エンコーダB相 |
| GPIO14 | 抵抗 | LED8 (START) |
| GPIO15 | 抵抗 | LED9 (SELECT) |
| GPIO16 | 抵抗 | LED1 (ボタンLED) |
| GPIO17 | 抵抗 | LED2 |
| GPIO18 | 抵抗 | LED3 |
| GPIO19 | 抵抗 | LED4 |
| GPIO20 | 抵抗 | LED5 |
| GPIO21 | 抵抗 | LED6 |
| GPIO22 | 抵抗 | LED7 |
| GPIO27 | レベルシフタ | DIN (WS2812B) |
コーディング
まじで苦手なコーディングです。
拙いコードですが、重要部分のコードだけ載せておきます。
Arduino-Pico環境で、Arduino版TinyUSBを使って作りました。
Arduino IDEでuf2ファイルを生成してPicoに書き込みました。
ボタン
処理はこんな感じです。宣言などは載せていません。
TinyUSBのサンプルコードをベースに作りました。
ボタンの状態変化を検知したあと、一定の時間が経過している場合は即座にレポートを行うeagerデバウンスを行っています。
for (int i = 0; i < 9; i++) {
currentTime[i] = millis();
currentRawState[i] = (digitalRead(keys[i]) == LOW);
if (currentRawState[i] != button_isPressed[i]) {
if (i < 7) {
// デバウンスタイムが経過しているか確認
if (currentTime[i] - button_lastChangeTime[i] > DEBOUNCE_DELAY_MS) {
button_isPressed[i] = currentRawState[i];
if (button_isPressed[i]) {
gp.buttons |= (1U << i);
}
else {
gp.buttons &= ~(1U << i);
}
button_lastChangeTime[i] = currentTime[i];
}1鍵は0, 2鍵は1, 3鍵は2……7鍵は6 のようにレポートしていますが、STARTは7ではなく、一つ間をあけて8なので気をつけてください。(SELECTは9)
スクラッチ
ロータリーエンコーダはある信号の立ち上がりを検知した後、すぐに回転方向の検知を行う必要があります。これは、高分解能なエンコーダほど顕著になります。
そのため、一般的には割り込みが使われていますが、これだとエンコーダによってとんでもない量の割り込みが行われてしまいます。(Picoのクロック周波数なら問題ないとは思いますが)
幸い、PicoにはPIOという、メインのCPUとは別で簡単な命令だけ実行できるブロックが用意されています。メインCPUが別の処理をしていてもPIOがエンコーダの検知をやってくれます。
メインCPUは一連の処理が終わった後に、エンコーダがどのくらい動いたかPIOから教えてもらうことで、スクラッチ処理を行えるというわけです。
公式のサンプルコード集(pico-example)にちょうどエンコーダ処理プログラムがあったので、今回はお借りしました。
上記のリンクのコードは、エンコーダの回転方向に応じて値を増減させるものです。これはPIO側でやってくれます。
メインCPUからこの値を読み取って、スクラッチの処理を行います。
処理についてですが、INFコンではゲームパッドのX軸を増減させるアナログスクラッチ方式が主流になっています。
カウントした値をX軸の座標に変換するだけで、INFINITASやBeatoraja側がいい感じに処理してくれるのでかなり楽です。(INFINITASはUSBのIDを変更する必要あり)
// 変数encoder_countにPIOが計測した値を代入する
int32_t encoder_count = quadrature_encoder_get_count(g_pio_enc_instance, g_pio_enc_sm);
// このままだとスクラッチの感度が3000倍なので、ENCODER_SENSを用意して感度を落とす
int32_t scaled_count = encoder_count / ENCODER_SENS;
// ゲームパッドのX軸は0~255なので、それにあうように値を取り出す
uint8_t axis_value = (uint8_t)(scaled_count & 0xFF); // 下位8bitを符号なし8bitにキャスト
gp.x = axis_value;ENCODER_SENSの値を変更する仕組みを実装すれば、フェニワンみたいに感度を変更できるようになります。
皿LED
皿LEDについてはAdafruit_NeoPixelを使って実装しました。
単色点灯・虹色・消灯の3モードを実装しました。
色は何種類か用意し、変更できるような仕組みにしておきました。
虹色に回転させるサンプルコードは、ノンブロッキングなものに改変して使いました。
初期の構想時の名残で演算を行うコアを分けていますが、ノンブロッキングな処理なので分ける意味はないような気がします。
モードの変更や輝度変更などの指示はコア0から行い、その指示を受けてコア1が光り方を変更させるといった仕組みです。
その他
LEDの光り方や、ボタンのデバウンスタイムなどの設定を、コントローラーのみで変更できるように設定モードを作りました。
設定値はフラッシュメモリに保存し、毎起動時に読み込むようにしています。
完成したコード
完成したコードを載せておきます。プログラマからみるとひどいコードだとは思いますが、参考にどうぞ。
ピンアサイン等はconfig.hから変更できます。
ところどころマジックナンバーを用いたり、無駄な部分があったりしますが、コードの修正は行わない予定です。
使用する際はライセンスに注意してください。私自身、OSSライセンスについて完璧に理解しているわけではないので、ライセンス関連のミスがあったらXで教えてください。
おわり
ということで製作記事でした。ところどころ端折ってしまい申し訳ないです。(特に外装加工)
詳細な材料費は計算していませんが、たぶん1万円ぐらいだと思います。
最後に完成品の画像・動画を載せておきます。
完成品

自作したい方向けの話
githubで公開しているリポジトリに、製作過程で使用したデータを公開しているので良かったら参考にしてみてください。ビルドガイドを作る予定はないので、私のデータで製作するのはおすすめしません。
調べた感じでは、Thomas-Star氏のプロジェクトがおすすめできそうです。見た目もよくて説明もわかりやすい。すごい。
3Dプリンターを持っていない方でも、JLC3DPなどの製造委託サービスを使うといった方法もあるので、良かったらみなさんもDIYにチャレンジしてみてください。
ありがとうございました。





