ArduinoのUSBホストシールド。
https://www.arduino.cc/en/Main/ArduinoUSBHostShield
ずいぶん前に買ってから、結構放置しっぱなしだった
んだけど、改めて弄り回しているところ。
(Spark Funのやつは、パターンカットして改造を
しないと最新ライブラリで動かなかったり、オイラ
謹製のArduino互換基板だと電源線(多分3.3V線)が
機能しないせいか、うまく動かせなかったり)
で、以前からもやもやと考えていた、現代版のUSB
ジョイスティックやUSBジョイパッドを、MSXやPC-6001、
PC-8801mkIISR以降、X68000などに繋ぐための変換基板
作れないかなぁ?という妄想を、少し前進させてみる。
使うのは、Arduino-UNOと、aitendoで買ってきたUSB
ホストシールド。
http://www.aitendo.com/product/10293
これだと、パターンカットせずにそのまま使えて便利。
これと、USBホストシールドライブラリ2.0を組み合わ
せて使う。
https://github.com/felis/USB_Host_Shield_2.0
https://github.com/felis/USB_Host_Shield_2.0/archive/master.zip
これをインストールしておく。
各種USB機器用に、サンプルプログラムが公開されて
いるので、その中から、USB HID Joystickのサンプル
を利用しちゃう。
https://github.com/felis/USB_Host_Shield_2.0/tree/master/examples/HID/USBHIDJoystick
ここに3つのファイル(inoファイル、cppファイル、
hファイル)があるので、これら3つを1つのディレクトリ
に入れておいて、Arduino-IDEでコンパイル、書き込みを
すれば動いちゃう。
(USBジョイパッドをUSBホストシールドに挿して、この
スケッチ一式を書き込んで実行すると、ジョイパッド
の操作にあわせて、シリアルモニタにその情報を表示
するようになってる)
で、このプログラムを元に少し改造してみる。
サンプルプログラムは、ジョイパッドの状態に変化が
生じたときだけシリアルモニタに表示されるみたい
なんだけど、それだとちょっと困るので、常時監視を
していて、見たいときに最新の入力状態を知れるように。
んで、その結果をLEDに表示してみる。
実験中の様子。
LEDは、上下左右+ABボタンの計6個としてみた。MSX互換
のジョイスティック端子は、D-SUB9ピンでつなげられる
ボタンが2個に限定されちゃうので、ABの2個だけに。
https://www.clarestudio.org/elec/misc/joystick.html
この「MSX互換ジョイスティックポート」の情報がすごい
纏まってるなぁ…。
USBジョイパッドから取り込んだ入力情報を、8ビットの
データ変数に格納していくんだけど、とりあえずこの
ページの情報とかを元に、上下左右データの4ビット+
1ビットの隙間(5V端子)+ABの2個のボタン+1ビットの
空き(COMMON端子分)という並びにしてみた。
別に5V端子分のところを開けておく必要は無いんだけど、
ひとまずそうしてみただけ。
すっかり忘れてたんだけど、88SRのジョイスティック
入力のI/Oポートって、このサイトの「I/Oマップ」の
最下段みたいに、
http://www.maroon.dti.ne.jp/youkan/pc88/index.html
「方向」と「ボタン」でポートが2つに分かれてたん
だなぁ。全然忘れてた。
でも、4ビットパラレルモード(マウス信号の制御とか
につかう)用に、出力が1ビットあったと思うだんけど
なぁ。あれ、このどっちかのポートに入ってなかった
かなぁ?まぁ、本題から外れちゃうからとりあえず
いいや。
ちなみにプログラム一式。
先ほどの3本のファイルをチョコチョコ弄ってみたもの。
まず、メインプログラム。
#include <usbhid.h>
#include <hiduniversal.h>
#include <usbhub.h>
// Satisfy IDE, which only needs to see the include statment in the ino.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#include <span style="color: #CC6600;">SPI
.h>
#endif
#include
"hidjoystickrptparser.h"
USB Usb;
USBHub Hub(&Usb);
HIDUniversal Hid(&Usb);
JoystickEvents JoyEvents;
JoystickReportParser Joy(&JoyEvents);
int dout[8] = {2, 3, 4, 5, 14, 15, 16, 17};
void setup() {
Serial.
begin(115200);
#if !defined(__MIPSEL__)
while (!
Serial);
// Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
Serial.
println(
"Start");
if (Usb.Init() == -1)
Serial.
println(
"OSC did not start.");
delay(200);
if (!Hid.
SetReportParser(0, &Joy))
ErrorMessage<uint8_t > (PSTR(
"SetReportParser"), 1);
for (
int i=0; i<8; i++) {
pinMode(dout[i],
OUTPUT);
}
}
void loop() {
Usb.
Task();
for (
int i = 0; i<8; i++) {
int d = (data >> i) & 0b00000001;
digitalWrite(dout[i], d);
Serial.
println(d);
}
for (
int i = 0; i<7; i++) {
if ((data >> (7-i)) == 0) {
Serial.
print(
"0");
}
}
Serial.
print(data,
BIN);
Serial.
println(
"");
}
cppファイル(ジョイスティック用の共通処理)。
#include "hidjoystickrptparser.h"
unsigned char data;
JoystickReportParser::JoystickReportParser(JoystickEvents *evt) :
joyEvents(evt),
oldHat(0xDE),
oldButtons(0) {
for (uint8_t i = 0; i void JoystickReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) {
bool match = true;
// Checking if there are changes in report since the method was last called
for (uint8_t i = 0; i if (buf[i] != oldPad[i]) {
match = false;
break;
}
// Calling Game Pad event handler
if (!match && joyEvents) {
joyEvents->OnGamePadChanged((const GamePadEventData*)buf);
for (uint8_t i = 0; i // Calling Hat Switch event handler
if (hat != oldHat && joyEvents) {
joyEvents->OnHatSwitch(hat);
oldHat = hat;
}
uint16_t buttons = (0x0000 | buf[6]);
buttons <<= 4;
buttons |= (buf[5] >> 4);
uint16_t changes = (buttons ^ oldButtons);
// Calling Button Event Handler for every button changed
if (changes) {
for (uint8_t i = 0; i < 0x0C; i++) {
uint16_t mask = (0x0001 << i);
if (((mask & changes) > 0) && joyEvents) {
if ((buttons & mask) > 0)
joyEvents->OnButtonDn(i + 1);
else
joyEvents->OnButtonUp(i + 1);
}
}
oldButtons = buttons;
}
}
void JoystickEvents::OnGamePadChanged(const GamePadEventData *evt) {
data = 0;
int dx = (evt->X);
int dy = (evt->Y);
int z1 = (evt->Z1);
int z2 = (evt->Z2);
int rz = (evt->Rz);
/* x axis */
if (dy > 0xC0) {
data += (1 << 0);
} else if (dy < 0x40) {
data += (1 << 1);
}
/* y axis */
if (dx > 0xC0) {
data += (1 << 3);
} else if (dx < 0x40) {
data += (1 << 2);
}
/* buttons */
int buttons = ( z1 & 0b00000011 ) <void JoystickEvents::OnHatSwitch(uint8_t hat) {
Serial.print("Hat Switch: ");
PrintHex<uint8_t > (hat, 0x80);
Serial.println("");
}
void JoystickEvents::OnButtonUp(uint8_t but_id) {
Serial.print("Up: ");
Serial.println(but_id, DEC);
}
void JoystickEvents::OnButtonDn(uint8_t but_id) {
Serial.print("Dn: ");
Serial.println(but_id, DEC);
}
ヘッダファイル。
#if !defined(__HIDJOYSTICKRPTPARSER_H__)
#define __HIDJOYSTICKRPTPARSER_H__
#include <usbhid.h>
extern unsigned char data;
struct GamePadEventData {
uint8_t X, Y, Z1, Z2, Rz;
};
class JoystickEvents {
public:
virtual void OnGamePadChanged(const GamePadEventData *evt);
virtual void OnHatSwitch(uint8_t hat);
virtual void OnButtonUp(uint8_t but_id);
virtual void OnButtonDn(uint8_t but_id);
};
#define RPT_GEMEPAD_LEN 5
class JoystickReportParser : public HIDReportParser {
JoystickEvents *joyEvents;
uint8_t oldPad[RPT_GEMEPAD_LEN];
uint8_t oldHat;
uint16_t oldButtons;
public:
JoystickReportParser(JoystickEvents *evt);
virtual void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
};
#endif // __HIDJOYSTICKRPTPARSER_H__
(例によって、このブログの制約で、不等号記号が一部
全角に置き換わってます)
この3つを1個のディレクトリに入れて、IDEで開くと、
コンパイルできる。
元のサンプルプログラムと比べて、変更したのは
以下のとおり。
・cppファイル中で、シリアル出力していたロジック
周りを修正して、変数「data」の中に、ビット
イメージで格納していく処理に変更
・ヘッダファイルで、変数「data」をextern宣言して
おいて、メイン処理からも参照できるように
・メイン処理では、変数「data」から1ビットずつ
取り出して、デジタルポートに出力
ちなみに、USBホストシールドライブラリ2.0がD7から
D13を占有しているので、残りのポートに割り振り。
上下左右はD2~D5に、ABボタンはD15~D16(アナログ
入力端子)に割り振り。
とりあえずいごいた。何の問題も無くいごいた。
ArduinoをUSBホスト機器に出来るって、何気に便利
だよな、やっぱ。
MIDI機器にしても、キーボードやマウスにしても、
ジョイスティックにしても、この手の入力デバイス
がArduinoから使えると、色々便利だし、出力機器も
同様。
ホントは、USBホストシールドじゃなく、USBホスト
機能が内蔵されているARMチップを簡単に使えると
いいんだけどな。なにかいいの無いかな?
mbed互換で安くてこの辺りの機能がさくっと使える
ようだとうれしいんだけどな。
さて、USBジョイパッド~Arduinoまでのところは
これで繋がって、1ビットずつの出力にすることは
出来た。
で、これをMSXとかにつなげるとしたら、その辺の
回路構成も考えないといけない。
実は、同じD-SUB9ピン端子でも、ATARIのジョイ
スティックポート(もともとのオリジナル)から、
MSX、P6、88SR、X68とかで使っているジョイスティック
端子では、端子の割り振りとか回路構成が少し違う
ので、単純に使いまわし出来ないところに注意が
必要。
ATARIはそもそも手元に実機も無いし、多分今後も
入手はしないだろうから、主にPC88SR用を念頭に
考える。一応、これを基準に考えておけば、
MSXもP6もX68も動くはず。
そうは言っても、ある程度ちゃんと考えておく
必要はある。
http://www.ktjdragon.com/ktj/ktjs-blog/3044308f3086308b300catari30e730a430b930c630a330c330af30fc30c8300d306e59099077306b306430443066307e306830813066307f308b
この「いわゆる「ATARIジョイスティックポート」
の変遷についてまとめてみる」のページによると、
ATARIオリジナルの場合、8番ピンがGNDになってて、
9番ピンは「パドル」入力に割り当ててある。
P6(やMSX、88SR、X68なども)は、8番ピンは出力
(COMMON)端子で、GNDは9番。
なので、P6とかにATARI互換のジョイパッドを繋ぐ
とすると、8番端子にLOWを出力しておかないと、
値が読み込めないってことになる。
逆にATARIにMSX用ジョイパッドを繋いじゃうと、
Bボタンが自爆ボタン(VccとGND、もしくはCOMMONが
ショート)になっちゃう。
まぁ、ATARIのことは考えないで進める。
ジョイスティック・ジョイパッドの内部では、
上下左右も、ABボタンも、本来は電源(Vcc)
からの電源供給は不要で、オープンコレクタ
(オープンドレイン)になってる。
Arduinoで制御するには、Arduino+USBホスト
シールドのための電源は必要になるとしても、
上下左右+BAの信号自体は、オープンコレクタ
出力にしておくと、一番トラブルが少ないはず。
まぁ、そのままデジタル出力しても、(オン/オフ
のロジックを間違えさえしなければ)普通に動く
はずではあるけど。
実際、当時の市販のジョイパッドで、4069インバータ
とかで発振回路を使って連射機能を搭載していた製品
では、その発振回路のICの出力端子を、そのままAB
ボタン出力に割り当てしていたっていうものが普通に
あった。
で、ここでは、とりあえずボタンなり上下左右なりが
押されたところは「1」出力、それ以外は「0」のまま、
という具合にソフト側の処理を行っておいて、その
出力端子にオープンコレクタのインバータ「74HC06」
を使ってD-SUB9ピンに出力すればよいはず。
まぁ、オープンコレクタは、出力ピン同士をショート
させても壊れないけど、普通のトーテムポールバッファ
出力だとショートさせたら壊れる恐れがあるので、
その辺りを気にする必要がある機器では注意要。
(ちなみに、オープンコレクタじゃなく、Arduinoの
出力ピンを直接出力しちゃうなら、押されたところ
は「0」に、それ以外は「1」に出力する必要がある
ので、注意が必要)
さて、問題は、74HC06使ってオープンコレクタにする
のはいいとして、その場合のGND線は、9ピンのGNDを
前提にすればいいのか、それとも8番ピンにしておいて、
PC側から8番ピンにLOWを出力することで制御すれば
いいのか良くわかってない。
多分、9番のGNDで大丈夫と思うんだけどなぁ。
8番って、MSX用のパラレルマウスの座標を読むのに、
4ビットパラレルモードを使うとき使った気がする。
逆に言うと、8番ピンはそれ以外では使ってないと
思ったな。むしろ、8番ピンをちゃんとLOW出力
にしておかないと、ジョイスティックの内容を
読み込めなくなる(ノー操作と判断される)ので、
GNDでいいと思うけど…。
まずは、ジョイパッドだけでいいかな、と思うので、
やっぱ9番のGNDでいいんじゃないかな、って思う。
(この機器をATARIに繋ぐと危険だぜ!!)
せっかくのUSBホストシールドなので、もしマウスも
対応したいって考えるなら、その辺はしっかり設計
しておく必要はありそうな気もする。
でも、その場合はむしろ9番ピンGNDだろうなぁ。
少し前から、オイラの88SRは、ディスプレー出力機能
周りのコンデンサが死に始めているようで、画面表示が
ちゃんと出ていないみたい。
なので、せっかくここまで出来たといっても、実機で
テストできる環境が整ってない。
とりあえず、P6かMSXの安い中古をゲットしちゃうのも
手なんだけど、どうしたものやら…。
ABボタンはもちろん、XYボタン、LRボタン、START/
SELECTボタンもちゃんと読み込めたので、その辺り
のキーにアサインしなおす機能も付けるとしたら、
その辺はソフト周りでゴニョゴニョできるように
したほうがいいのかなぁ?それとも、ハード上で
ジャンパー繋ぎ換えたりして設定可能にしたほうが
いいのかなぁ?
なにか、色々もやもやと考えているところ。