ImpactSystems(インパクトシステム)

プロポ改造でUSBジョイスティック

Joypro2

かつてJOYPRO2として、ラジコンの送信機をジョイスティックとして使うための変換器を制作していました。2002年頃です。その頃はX347送信機を主に使っていました。Windows98にCFS、CFS2を入れていました。写真の背景のディスプレイで大体年代は想像つくと思います。
最近思い立ってCFS(Microsoft Combat Flight Simulator)をWindows10へ入れてみました。ところがJOYPROは処分してありません。1個位は残していたはずなんですが、行方不明です。そこでJOYPROの再制作を始めました。

Leonardoとシールド

背景はともかく、現在はArduino_LEONARDOを使えば簡単にジョイスティックを自作できるようです。LEONARDOにはATMEGA32U4が使われており、このCPUを実装したPRO_MICROやコンパチ品でも良いようです。写真はとりあえず2軸の簡単なジョイスティックで動作確認した組み合わせです。ジョイスティックはユニバーサル基板のシールドに手配線しました。VIDやUIDを考えずにUSBを使えて、しかも小一時間で完成してしまいます。便利ですね。ArudinoのIDEインストールやLeonardoドライバー(API?)組込み方法などは他のホームページを参照してもらって、ここからは写真の試作状態から実用な物になるまでを紹介していきます。

その前にJOYPROと言う名前ですが、ネット検索してみれば、関東方面にそういう名前のDIYショップがあるみたいです。紛らわしいので今後は使わないようにします。ただしプロジェクト名やファイル名にちょこちょこそういう記述が現れると思います。かつて私が使っていた商品名です。JOYはJOYSTICK、PROは送信機のプロポ(比例=proportionality)から名付けていました。

送信機-完成形

JR pcm10S正面

写真はJR(旧日本遠隔制御株式会社)のpcm10S送信機です。古いですが、私はこの時代にX347でラジコンヘリを始めており、その頃のTopのマシンで思い入れがあります。一部樹脂の壊れとかトリムの陥没とかありますが、修理して使用に問題ありません。しかしさすがに72MHzのRFでは今後フィールドで使うことは無いでしょうから、これをバサッと改造してジョイスティック専用機にしてしまいました。表面から見た目は変わっていないように見えます。見にくいですが両肩にタクトスイッチを取り付けて機銃発射と機関砲に振り分けています。奥の長いレバースイッチ以外はモーメンタリースイッチに交換しています。AC100V用なので経年変化が心配ではありますが、手持ち品を使用しました。ジョイスティックとしてはモーメンタリースイッチ(跳ね返り型)が良さそうです。ネジ径がオリジナルと異なっていて、飾りナットではなく普通の六角ナットになっているスイッチがあります。これがちょっと残念。

JR pcm10s背面

背面はこうなっていて、USBケーブルの取り出しがこんな感じになってしまいました。PRO MICROを使用しています。コンパチ品の方が安くて部品感覚で使用できますが、ここはArduinoオリジナルを使用しています。Analog入力がLEONARDOと同じく6ポートあります。(コンパチ品は4ポートしか端子に出ていない)。

裏蓋を開けた

裏蓋を開けると、オリジナルの基板が無いことが分かります。Arduino PRO MICROのベースになっている基板は私の自作品です。液晶は接続先が無くてフレキケーブルが浮いています。スイッチ要素が多くてハーネスが目立ちます。基板上に並んでいる3つのICはアナログスイッチです。Analog入力が6ポートでは足らないので切り替えで24ポートに拡大しています。

基板

組込み前の基板です。JSTのEHコネクタが並んでいます。真ん中のランド列にArduino Pro Microを取り付けます。シルクはMICRO PROと逆に書いていますが、不問にして下さい。このコネクタ形状では左右どちらでも取り付け出来るのですが、USBコネクタがシルクに書いてある向きになるように取り付けて下さい。

この基板は余分を作りましたので、写真の状態で頒布します。BASEショップに置いています。限定3枚です。Arduino Pro Microのシールドとして使えます。タカチのケースにポテンショメータ(ボリューム)やスイッチを並べて、独自のジョイスティックを制作してみて下さい。pcm10S等市販品を改造される時は、もちろん自己責任です。

回路図

回路図

シールドの回路図です。Arduino Pro Microのアナログポートだけではアナログ入力が不足するので、アナログマルチプレクサ(TC4052)を使用して入力数を確保しています。スロットル、エルロン、エレベータ、ラダーの4軸は必須として、その各軸にトリムがあるので、アナログで最低8入力であり、元々の6入力でも入力不足です。そこで2回路4入力のマルチプレクサを3個使って、合計24入力に拡張しています。一方、デジタル入力も不足しますから一部スイッチ入力を、余裕ができたアナログポートに割り振っています。

回路図の説明を続けます。 CN1,CN2,CN3,CN4,CN9は送信機向かって右側の入力要素用です。CN5,CN6,CN7,CN8,CN10が左側です。エルロンのポテンショメータはCN1:3pin=STICK0_Rに接続しています。同様にスロットルはSTICK1_Rです。それぞれのトリムはTRIM0_R,TRIM1_Rとしています。左側のCN5:3pin=STICK0_Lはラダーです。STICK1_Lはエレベータです。


	CN1.3 STICK0_R エルロン
	CN1.4 STICK1_R スロットル
	CN1.7 TRIM0_R  エルロントリム
	CN1.8 TRIM1_R  スロットルトリム
	CN2.3 POTM0_R  HOV.T(正面右上のポテンショメータ)
	CN2.4 POTM1_R  AUX4(上面右のポテンショメータ)
	CN3.3 SW0_R    AUX2(正面右上のトグルスイッチ)
	CN3.4 SW1_R    AILE_D/R(正面右上のトグルスイッチ)
	CN4.3 POTM2_R  ハイピッチ(右側面のレバー式ポテンショメータ)
	CN4.5 SW2_R    FLIGHT_MODE(右肩の両切りスイッチ前側)
	CN4.6 SW3_R    FLIGHT_MODE(右肩の両切りスイッチ向う側)
	CN4.7 SW4_R    GEAR/INVERT(右肩のレバースイッチ)
	CN4.8 SW5_R    追加SW(右肩の機銃用に改造追加したpushSW)
	

	CN5.3 STICK0_L ラダー
	CN5.4 STICK1_L エレベータ
	CN5.7 TRIM0_L  ラダートリム
	CN5.8 TRIM1_L  エレベータトリム
	CN6.3 POTM0_L  HOV.P(正面左上のポテンショメータ)
	CN6.4 POTM1_L  AUX5(上面左のポテンショメータ)
	CN7.3 SW0_L    MIX(正面左上のトグルスイッチ)
	CN7.4 SW1_L    ELEV_D/R(正面左上のトグルスイッチ)
	CN8.3 POTM2_L  ローピッチ(左側面のレバー式ポテンショメータ)
	CN8.5 SW2_L    THRO.HOLD(左肩の両切りスイッチ前側)
	CN8.6 SW3_L    THRO.HOLD(左肩の両切りスイッチ向う側)
	CN8.7 SW4_L    RUDD_D/R(左肩のレバースイッチ)
	CN8.8 SW5_L    追加SW(左肩の機関砲用に改造追加したpushSW)
	
SW2-SW3の両トグルスイッチ(ONモーメンタリ-OFF-ONモーメンタリ)はアナログとして読み取ります。
SW4はプルアップをして、アナログとして読み取ります。
アナログ入力はU1,U2,U3のアナログマルチプレクサ(TC4052)を使用して4:1入力としてアナログポートに入れています。
CN9,CN10はおまけ(予備)の入力で、外部のポテンショメータ(可変抵抗器)を読み取ります。

これも予備ですが、レベルシフター(TXS0102)で3.3V系のI2CをCN14に出力しています。

pcm10Sには圧電ブザーとLED、レベルメータが取り付けられています。このコネクタが残っていたので、一応これの対応コネクタをCN15に設けています。レベルメータは固定抵抗で電源電圧を見ていますが、何も計算せずに単に振らしているだけです。圧電ブザーはエミッタフォロワで駆動し、Arduinoのtone関数が使えるようにしています。

機能割当て

スイッチ割当て

送信機のポテンショメータ(ジョイスティック軸)やスイッチに新たに名前を振って、図面にしてみました。図にしないとややこしくてプログラムできませんでした。この図はJR pcm10Sの配置を現しています。しかもヘリ用です。各要素(大体は丸印)の下の最初の名前はプロポの表面に印字してある機能名です。赤文字は最終的にcfsやcfs2に割当てた機能です。片側3つ、両方で6つのスイッチがHAT機能になりました。
機銃および機関砲は、基板用のプッシュスイッチ(タクトスイッチ)を追加して取り付けています。つまり改造して穴を開けています。ここまでするのは私だけと思います。一般的にはGEAR/INVERTEDスイッチを機銃に割当てることになると思います。ただし、この記事では特殊な私の例で説明を続けていきたいと思います。スケッチの基本要素は簡単なのですが、複雑なことをして長いソースリストになっていますので、それを解析する参考にして下さい。

ゲームの動作にボタンを割当てる前に、Windowsのプロパティで、Joystickの動作を確認しておいた方が良いと思います。Windows10では"スタート"を左クリック\"設定"を左クリック\"デバイス"を左クリック\"関連設定のデバイスとプリンター"を左クリックすると、"Arduino Leonardo"のジョイパッドのアイコンが見えます。これを右クリック\"ゲームコントローラの設定"を左クリック\"ゲームコントローラ"窓の"プロパティ"を左クリックするとプロパティ窓が現れます。

プロパティ窓

これは現在のプログラム設定による画面です(スケッチの冒頭のJoystick( )によるメンバー宣言)。エルロンはX軸、エレベータはY軸、スロットルはスロットル、ラダーはラダーの各項目に対応します。右サイドのトリムはX回転、左サイドのトリムはY回転に対応します。pcm10Sには他にもHOV.pitchなど4入力アナログがありますが、プログラムで設定しても軸は増えませんでした。むしろAruuino Leonardoのゲームコントローラが認識されなくなりました。Joystick宣言でむやみに"true"設定してもゲームコントローラが対応していないようです。もっとも各ゲームで割り当てるアナログ入力はそれほど無くて、ボタン設定によるプッシュ入力が多いようでした。cfsにはこれで十分です(X回転,Y回転は割当てていません)。

ラジコン送信機はスティックの各軸に1:1でトリムがあります。送信機と同じイメージで、トリムはセンターを中心としてスティック軸に加減算しています。よってトリムはゲームプロパティに現れていません。またスティックがセンターにあっても機械誤差により読取り値がセンターにあるとは限りません。これはデバッグ時にUARTを接続して値を直読し、プログラム埋め込みで補正しました。本来の送信機は液晶画面を見ながら機体調整しつつ補正するのですが、この改造では実現できていません。またエクスポネンシャルなどのカーブ設定も同様に実現できていません。今後の課題と思います。

注:Arduinoでは、プログラムのことをスケッチと言っていますね。私はソフトとも言っています。今後なるべくスケッチと記述しますが、癖でプログラムと書くかも知れません。

プロポ入力のゲームに於ける機能割当ては、ゲーム(シミュレート)をしている間に何度も再スケッチを繰り返して、当初より変化して来ました。冒頭の割当て図は最新のものに更新しています。
私はトグルスイッチを跳ね返り型(モーメンタリタイプ)に交換して、HATスイッチの代用としています。例えば右肩のFLIGHT_MODEスイッチを手前に倒すと首を右回しで、後ろを見るような動作です。スイッチを押し続けている間は後ろを見続けます。スイッチを離すとまたグルリと右側を見渡して正面を見ます。8方向のスイッチであれば簡単ですが、これを単独のスイッチで同一方向の半回転としてソフト的に実現しています。例えばこのような動作です。


	----------- ON ----------|-------- OFF -------------------|
	右前 -> 右 -> 右後ろ -> 後ろ -> 右後ろ -> 右 -> 右前 -> 前方
	
書けば簡単ですが、cfs(cfs1)とcfs2ではスイッチの解釈が異なっていて、スケッチは少し複雑になっています。多分歴史的な流れでHATスイッチのスイッチ動作が変わったのかも知れません。或いはゲーム毎に設定があるのかも知れません。詳しくはスケッチを読んで下さい。HATの機能は割当て図の(e)表を参照して下さい。

HATをこの方法で実現すると、ひとつのスイッチがいくつものボタンに割り当てることになります。便利な半面、ゲームの設定画面においてボタンを機能に割り当てることが困難になります。そのためHOV.P或いはHOV.Tのポテンショメータを左にいっぱい回しきった時には、(e)HAT機能の上のテーブルのように、トグル毎に各ボタンがローテーションするようにしました。[次にスイッチをONした時はボタン番号はX番]と言うのを頭に入れておいて、機能割当てをクリックして下さい。プレイする時はHOV.P/HOV.Tポテンショメータをある程度(9時の位置程度)右に回して下さい。回す大きさにより右回り(HOV.T)、左回り(HOV.P)の速度が変わります。
設定中にローテーション位置がどこか解らなくなった時には、このポテンショメータを少し回しておくと、前方視界に戻ります。また、ボタン割当ては右回りでも左回りでも、どちらでも同じなので片方だけで設定できます。
もちろん、ローテーション機能で飛行しても構いません。ただ対戦中は忙しくて何回もカチカチしている暇はありませんでした。

割当て図において、B1、B2とかはプロパティ窓のボタン番号を示しています。(B3),(B4)はその番号を直接出力せずに、HATスイッチのスイッチ割当ての増減に使っています。ただし、現在のスケッチではB1、B2も(B1),(B2)としてHAT機能を割当てており、直接ボタン指定はできなくなりました。

cfsの設定画面

ゲームのスイッチの割当ては、写真のような"コントローラの設定"画面から行います。cfs(cfs1)の例です。cfsのコントローラの設定画面で割当てをし、フリーフライトをして確認をしたり、シミュレータでできなければ、プログラムを修正したりしました。コンバットフライトシミュレーターでは、HATスイッチが活躍するようです。何回もスイッチの割当てを変更しています。何回もHATスイッチ(小さ目のアナログジョイスティック)を外付けしようか、ケースから作り直そうかと思いました。しかし主に見たいのは、旋回で上方に消えた敵機か、後ろの敵機のようです。それらの一応の結果が割り当て図の赤文字になっています。
元々ストレス解消でcfsを再び始めたのですが、かつ1週間程度で終わる予定が1ヶ月過ぎて余計ストレスが貯まったようです。今後はfsxで気楽にストレス発散もいいかな。新バージョンのニュースも流れていて来年が楽しみです。

スケッチ

このプロジェクトのキモは、ATmega324U4を使えば、ArduinoのIDEで簡単にUSB ジョイスティックが作れるという事です。当初は手探りだったので、Aruduino LEONARDOで試作しました。その後基板化してPro Microを使用したのですが、ちょっと高いですね。コンパチ品がAmazonで安く出回っているようです。Pro Microに差し替えできないかと考えて見ましたが、ピンコンパチではなくて、ジャンパー処理が必要になります。それであれば、シールドを止めて素直にATmega324U4の基板を作ればいいので、次バージョンではそうすると思います。

私は外部エディタでスケッチを書いています。Cのプログラムと同じ環境でコーディングできます。ただし、Arduinoのエディタのタブに合わせてタブ数は2文字にしています。ArduinoのIDEで開く分には問題ありませんが、IDEのエディタはタブコード2文字のスペースに置き換えてしまいます。IDEエディタで保存するとタブコードがスペースに置き換わったまま保存されます。外部エディタを使用する方は気をつけて下さい。

スケッチのスタートはいくつかのホームページを参照しました。特にArduino LeonarudoのHID ジョイスティックの項は記述法も含めて参考になりました。

以降、ソースリストを抜粋で説明します。ジョイスティック制作の参考にしてみて下さい。なおいつものお約束ですが、この記事を参考に何らかの実験、制作、改造や何らかトラブルに見舞われても全て自己責任で対処してください。質問は問い合わせのページよりお願いします。

USB ジョイスティックの使用

まずコードの最初にJoystick宣言をします。これはcppのメンバー定義と言うのかも知れません。良くは解らないのですが、こう記述するとWindows10のプロパティ画面がこの記事のようになるようです。


	Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_JOYSTICK, 32, 0,
	true, true, false, true,  true, false, true, true, false, false, false);
	// XA   YA    Za     Rx     Ry    Rz    Rudd Throt Accel  Brake  Steering
		
trueに対応する注釈の機能がプロパティに表示されています。ボタンの数は"32"の数字が対応しています。

次にsetup()でJoystickの使用を開始します。


void setup() {
	Joystick.begin();			// 開始
	Joystick.setXAxis(0x200);	// 初期化が必要であれば
	Joystick.setYAxis(0x200);	// Analog入力は0x000~0x3FFまで
	Joystick.setRxAxis(0x200);
	Joystick.setRyAxis(0x200);
	Joystick.setRudder(0x200);
	Joystick.setThrottle(0x200);
}
		

loop()でAnalog値を読み取ったり、デジタル入力を読み取ってUSBに出力します。


void loop() {
	analogvalue = analogRead(A0);	// 何らかのanalog値を
	Joystick.setXAxis(analogvalue);	// X軸に設定

	Joystick.setButton( 5, 1);	// ボタン5をON
	Joystick.setButton( 6, 0);	// ボタン6をOFF
}
		

スケッチの説明

Arduino IDEのスケッチ(プログラム)をzip圧縮でここに置いておきます。解凍してできるのはjoypro3.inoと言うスケッチファイルのみです。私と同じハードウェアの人は少ないでしょうから改変は必須になると思います。以下にファイルの行番号に沿って説明していきます。

変数定義

冒頭はコメント行から始まります。コメントは主にスラッシュ2つのコメント記号を使用しています。10行目の


10 #include <Joystick.h>
		
は、ドライバーの読み込みとして必須です。

12~13行目のJoystick()は前章で説明しました。


12 Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_JOYSTICK, 32, 0,
13  true, true, false, true,  true, false, true, true, false, false, false);
		

17行目の


17 int	HATmode = 0;	// 0=cfs1,1=cfs2: トグルスイッチをHATに見立てた時の振舞い
		
は、単なる変数宣言です。これはコメントのとおり、スイッチの動作のエンコードをcfs1風かcfs2風に行うかのフラグです。スロットルスティックの値が0x100未満の時に0としています。最後に作った変数ですが一番最初に置いています。setup()内の初期化の時にのみセットしています。

19,20行はLEDの点滅に関する変数です。

22~43行はアナログ入力に関する変数です。アナログ入力はATmega32u4に6チャネルあります。それを4バンク切り替えとして24ポートに拡張しています。読み込みは一旦配列に格納し、前回の値と移動平均しています。

配列に保存したアナログ値は、トリム補正やスイッチに分解します。結果を45~69行の固有名の変数に格納します。
JOYstick出力はこの値を出力しますが、変化のあった時のみ出力するので、前回の値を71~72行の配列に保存します。

77~87行はデジタル入力に関する配列定義です。ハード寄りの機能をスイッチと言い、ソフト(Windowsのプロパティ等)寄りの機能をButton(ボタン)と記述しています。明確に定義して使用いるのでは無く、曖昧です。Button=Switch=SWと思って下さい。

89~116行はスイッチ入力を疑似HATスイッチとする為の変数です。

118~124行はスイッチのチャタリングを取る為の変数です。スイッチの変化から実際の検出まで2チック(インターバル)遅れがあります。プログラムは16mSのインターバルで動作するようにしています。当初のデバッグ中にオシロ(オシロスコープ)で周期を見ていてそれ位で妥当と決めた値です。かつてラジコン受信機のPWM波形を観測した時、その周期は20mSでした。今回スイッチ入力の検出に、2~3 周期の遅れが発生しますので、少し早めの16mSにしています。126行はそのチック(tick)に関する変数です。

void setup()

Arduino LEONARDOの回路図にあるATmega32u4のシンボルには,ポート0のシリアルが見当たりません。代わりにポート1を使うようです。Pro Microもコンパチ品も同様です。

131  Serial1.begin(38400);
		
今回のジョイスティックではシリアル(UART)を使わないですが、デバッグの為にSerial1を使用しました。デバッグではTX(送信)のみ使用しました。必要になれば都度はんだ付けで線を引き出して、TeraTermに表示してデバッグしました。

D0入力ポートはRX1(先のSerial1の受信)と兼用ポートです。シリアルでは使用しませんので、入力ポートを宣言しています。


133  pinMode(0, INPUT_PULLUP);
134  pinMode(13, OUTPUT);
		
USB経由のプログラム時に、Joystick出力をしないようにするための判断スイッチを接続する為です。あるホームページにこの記述がありました(アドレス紛失につきリンクを張ってありません)。ここで入力を宣言し、loop()の冒頭でスイッチをReadしています。ただし、今回のシールド基板にはスイッチを実装していません。Joypro4ではコンパチ基板を使用していますが、そちらではこの機能を何回か使用しました。JoystickがUSBポートを占有すると、プログラム書き込みにUSBポートを使えなくなります。それを回避します。デバッグが進んでJoystick出力が一定周期になれば必要ないようです。
D13はLED出力ポートです。Pro Microの基板上で完結しているポートで、コネクタピンには接続されていません。動作確認用のLEDとして点滅させています。

77行~87行で配列に格納したD4~D9を、実際に入力ポートとして割当てます。


136  for (int index = 0; index < NUM_OF_BUTTONS; index++) {
137    pinMode(ButtonPin[index], INPUT_PULLUP);
138  }
		

D10,D11はTC4052(アナログマルチプレクサ)の入力切り替えです。


140  pinMode(10, OUTPUT);	// TC4052 SELA
141  pinMode(11, OUTPUT);	// TC4052 SELB
		

149~159行に於いて、potm0の値を読み出します。初期化の時にHATmodeの0/1を確定します。


149  digitalWrite(11, 0);	// B SELB:SELA = 2'b01
150  digitalWrite(10, 1);	// A
151  HATmode = ( analogRead(ADCpin[0]) < 0x100 )? 0: 1;
     ...省略
159  delay(300);
		
HATmode==0の時はcfs1と判断し、ブザーを短く鳴らします。HATmode==1の時はブザーを2回鳴らします。

161行でJoystickの使用宣言をしています。162~167行でディフォルト値を各軸に設定しています。


161  Joystick.begin();
		

void loop()

178,179行で16mSの周期動作を行うように設定しています。Renesas RL78/G13ではインターバルタイマの割込みを使用していましたが、Arduinoでは、ユーザが割込みを使用しなくても済んでいます。


178  if (millis() >= NextTickTime) {
179    NextTickTime = millis() + 16;	// 16mSのインターバルで以降の処理をする。
       .....mainの色々な処理 省略
658  }
		

182~199行はsetup()の133行で説明した具体例です。D0入力がONであればこれ以降の処理はせずにloop()の最初に戻ります。


183  if (digitalRead(0) == 0) {	// pushSWが押されていたら
184    digitalWrite(13, 0);		// LED消灯
185    return;					// USBに出力しない
186   } else {
        LEDの処理
199   }
		

202~241行はアナログ入力を配列に入れています。移動平均も求めています。


215    digitalWrite(11, 0);	// B SELB:SELA = 2'b00
216    digitalWrite(10, 0);	// A
217    for (index = 0; index < NUM_OF_ADC; index++) {
218      oldAnalog00[index] = lastAnalog00[index];
219      lastAnalog00[index] = (analogRead(ADCpin[index]) + lastAnalog00[index]) >> 1;
220    }
		

245~319行は配列に読取ったスティックのアナログ値を、そのペアーのトリムにより増減しています。また、スティックの中央値に機械誤差があった為、補正を加えています。Serial1を使って観測しました。


253    temp = lastAnalog10[0] >> 3;	// trim0R 1/8
254    if ( temp >= 64 ) {
255      stick0R = lastAnalog00[0] + (temp & 0x003F);
256      if ( stick0R > 1023) stick0R = 1023;
257    } else {
258      stick0R = lastAnalog00[0] - (temp ^ 0x003F);
259      if ( stick0R < 0 ) stick0R = 0;
260    }
261    stick0R -= 13;				// マシン固有の補正
262    if ( stick0R < 0 ) stick0R = 0;
		

245~319行はアナログ値の配列内容を固有名の変数に代入しています。また、アナログとして読取ったスイッチ入力を0/1のデジタルに変換しています。スイッチがOFFの時は'0',ONなら'1'です。


323    potm0R = lastAnalog00[1];
324    potm1R = lastAnalog01[1];
325    potm2R = lastAnalog10[1];
		

392~416行はスイッチのチャタリングを取る作業です。特に、sw2R/sw3R,sw2L/sw3Lに於いて、接点のデバウンスよりも、抵抗値によるエッジ遷移に問題があった為に作成しました。392~406行でスイッチ入力をビット有意の16bitに並べ替えています。'1'がON方向になるように一部インバートしています。
408~416行において、過去の値とANDをしてチャタリングを取り、排他ORでエッジを求めています。SWedgeとSWlevelを後程使用します。


392    SWnow = 0x0000;
393    if ( !digitalRead(ButtonPin[0]))  SWnow |= 0x0001;  // sw0R 直接入力port
394    if ( !digitalRead(ButtonPin[1]))  SWnow |= 0x0002;  // sw1R 直接入力port
395    if ( sw2R )                       SWnow |= 0x0004;  // sw2R anlog入力より
396    if ( sw3R )                       SWnow |= 0x0008;  // sw3R anlog入力より
397    if ( sw4R )                       SWnow |= 0x0010;  // sw4R anlog入力より
398    if ( !digitalRead(ButtonPin[2]))  SWnow |= 0x0020;  // sw5R 直接入力port
399    if ( !digitalRead(ButtonPin[3]))  SWnow |= 0x0040;  // sw0L 直接入力port
400    if ( !digitalRead(ButtonPin[4]))  SWnow |= 0x0080;  // sw1L 直接入力port
401    if ( sw2L )                       SWnow |= 0x0100;  // sw2L anlog入力よ
402    if ( sw3L )                       SWnow |= 0x0200;  // sw3L anlog入力より
403    if ( sw4L )                       SWnow |= 0x0400;  // sw4L anlog入力より
404    if ( !digitalRead(ButtonPin[5]))  SWnow |= 0x0800;  // sw5L 直接入力port
405    if ( buz2R )                      SWnow |= 0x1000;  // potm2R中間値
406    if ( buz2L )                      SWnow |= 0x2000;  // potm2L中間値
407
408    SWnew  = SWnow & SWold1 & SWold0;    // 今回のレベル
409    SWold0 = SWold1;                     // 入力を記憶(shift)
410    SWold1 = SWnow;                      // 入力を記憶(shift)
411
412    SWedge  = SWkeep ^ SWnew;            // 前回レベルと今回レベルでエッジ
413    SWkeep  = SWnew;                     // 今回レベルを保存
414    SWlevel = SWkeep;                    // 今回レベル
		

419~539行は実際のJoystick出力です。前回のアナログ入力と今回のインターバルでの入力が異なる時のみ、今回の値を出力します。


424    case 0:
425      if ( stick0R != potentiometer[index]) {	// stick0R
426        Joystick.setXAxis( stick0R );
427        potentiometer[index] = stick0R;
428      }
429    break;
		

560~656行は、スイッチ(Button)の出力です。HATmodeの値によってcallする関数が違います。sw0R,sw1R,sw0L,sw1Lは、任意のボタンに変更しています。cfs2の時が一番解りやすいと思います。例えば569行は、スイッチ入力のエッジを検出した時にButton17をONかOFFにします。エッジ検出の時に'1'であればONとして'1'を出力します。'0'であればOFFとして'0'を出力します。SWedge,SWlevelはcase文の評価終了毎に右に1bitシフトします。


565  case 0:							// sw0R HATうしろ
566    if( HATmode == 0 ) {				// cfs1の時
567      SingleEncode( 16, 12, &sw0RTime, 10);
568    } else {							// cfs2の時
569      if ( SWedge & 0x0001 ) Joystick.setButton( 16, (SWlevel & 0x0001) );	// Button17
570    }
571  break;
		
sw2R,sw3R,sw2L,sw3Lは少々複雑です。potm0Rが0x20以下ならばsw2R,sw3RはスイッチONの度に右回り、右上回りに後ろを振り向きます。0x20以下は実際にはツマミを左に回しきった状態です。sw2L,sw3Lはpotm0Lに影響を受けます。0x20より大きい時は、ONにしている限り最大真後ろまで勝手に振り向きます。その大きさにより振り向く速度を変えています。RotaryEncode函数の5番目の引数により時間を変えています。

581  case 2:						// sw2R HAT右回り チェック
582    if ( potm0R < 0x20 ) {		// gameにボタン番号割当て時
583      CWturn( SideTurn, &RightIndex, 8);
584    } else {						// 通常
585      RotaryEncode( RightTable, &RightIndex, &RightIndexold, &RightTime, (potm0R >> 5), 5);
586    }
587  break;
		
sw5Rの機銃スイッチはcase文の評価がありませんので、default文に引っかかります。スイッチの番号と同じボタンの番号をON/OFFします。ボタン番号=スイッチ番号 +1です。

646  default:
647    if ( SWedge & 0x0001 ) {								// edgeで
648      Joystick.setButton( index, (SWlevel & 0x0001) );	// 上記以外素直にSW出力
649    }
650  break;
		

函数 SingleEncode

スイッチに任意のボタン番号を設定します。ONの時と、OFFの時で違う番号でも構いません。また、スイッチがダブって同じボタン番号を割当てることも可能です。いちいちプログラムの埋め込み定数を変えなければなりませんが、USBコネクタを差したままコンパイルして書き込み出来るので、ひどく不便という事は無いでしょう。


672  void SingleEncode( int ondir, int offdir, int *time, int tmax )
		

函数 RotaryEncode

モーメンタリースイッチがONの間、首を右回り、あるいは左回りで真後ろまで首を振ります。OFFにすれば反対に正面を向きます。cfs1とcfs2でスイッチの解釈が違っていました。cfs1では古いButtonをOFFにしてから新たなButtonをONにします。cfs2では古いButtonはONのまま新たなButtonをONにします。


702  RotaryEncode( int *table, int *index, int *indexold, int *time, int tmax, int num )
		

函数 CWturn/CCWturn

ボタン番号をゲームの設定で割り振るために追加した函数です。Sirial1を宣言していますので、必要無くなりましたがシリアル出力をここに残しています。


771  void CWturn( int *table, int *index, int num )
		

右回りがあれば左は必要はないと思います。


v789 void CCWturn( int *table, int *index, int num )
		

他はシリアル出力に関するものなので説明は省略します。Cのライブラリにあるような函数ですが、自作しています。