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

ラジコン送信機でスロットカー

本記事は、カレラ社のデジタル式スロットカーのコントロールを、ラジコン送信機が使えるようにする信号変換基板の制作説明です。また本基板にはXBOXコントローラの互換機能もありますので、PCでのラジコン・シミュレーションゲームも楽しめます。

作成基板の利用方法

T4PMとR314SB-E

Carrera® DIGITAL132(以降Carrera®Digitalと省略)のコントローラは、有線式の親指コントローラです。これをカー用プロポを利用する変換基板を作成しました。当然ワイヤレスなので立ち位置がケーブルで制約を受けることはありません。操作は人差し指のトリガー式になります。写真のプロポはフタバ社(双葉電子工業株式会社、以降Futabaと記述)のT4PM PLUS(以降単にT4PMと記述)です。受信機はR314SB-Eを使用してS.BUS信号をデコードしています。CPUはArduino LeonarudoコンパチのPro Micro を利用しました。

試作手組み基板

これにより配線量は少なくなり、手配線でも作成できる規模です。写真のユニバーサル基板に作成したものは試作品です。
スピードコントロールしている実態は電子ボリュームのAD8400です。SOP変換基板を使用して10kΩの内蔵抵抗をモジュラープラグに出力しています。なおプリント基板化してAD8400は、Pro Microの基板下に隠れました。

RadioLinkプロポ

写真はRadioLinkのRC6GSv3+R7FGです。安くていいのですが、R7FGのアンテナ同軸ケーブルが長くて取り回しが不便でした。

カー用プロポでは、S.BUSのch2(チャネル2)がスロットルなので、これをそのままAD8400に割り当てています。
また、スロットカーなのでch1のステアリングは不要です。ただCarrera® Digitalのコントローラにはプッシュスイッチがありますので、これをch1に割当てました。ライトのON/OFFやレーンチェンジ、担当コントローラの記憶などのスイッチ操作を、ステアリングに割り当てました。

Analog改造

改造すればこの基板はアナログにも使えます。(チップ抵抗の付け替えが必要ですが)。
Carrera® EVOLUTIONのコントローラは、10kΩのカーボン抵抗を繋いでいるだけでした。そこでアナログコントローラからコネクタをハーネスごと拝借し、基板に仮付けして動作を確認しました。ただし、コントロールユニットから電源をもらえないので、写真のように別電源が必要になります。

従来からのアナログ式スロットカーは、コントローラに高電力の抵抗があって、レーンの電圧を上下していると思われます。しかし、Carrera®EVOLUTIONのレーンをオシロスコープで見たら、電圧一定でパルス幅変調してありました。ラジコンカーのESC(スピードコントロール)がレーンにあるようなものですね。よってAD8400のチップ内蔵抵抗でも、スピードコントロールできています。

ユーチューブ動画

純正コントローラとの比較

ワイヤレステスト

T4PMで実際にモータを回している様子です。TOWER1に青色のウラカンを設定しています。後輪が回っている様子が見て取れます。(リアウィングやサイドミラーは外しています)。

受信機やCPUの電源はコントローラユニットからもらっていますので、スッキリした基板になっています。ただこの構成では、コントローラユニットは何アンペア供給できるのか心配になります。実測したところ、この基板の消費電流は36.4mAでした。
一方入力電圧は4.1Vであり、保護ダイオード経由のPro Microへの供給電圧は3.8Vでした。この電圧はCPUの動作範囲なので問題はありません。しかし、メーカPDFでは発振周波数16MHzの保証は4.5V以上となっており、S.BUSを受信できるのか心配になります。今のところ問題なく信号をデコードできているので、オーバスペックで16MHz動作してくれているのかも知れません。あるいは発振周波数に従ってボーレイトの分周比を変えているのかも知れません。いずれにせよ今後調べてみようと思います。

純正コントローラテスト

2番に差した純正のアナログコントローラ(TOWER2ソケット)で動作させてみました。奥の黄色コルベットのリアホイールが回っています。このコルベットはCarrera® EVOLUTIONのものですが、デジタルコンバージョン基板でデジタル化しています。
話はそれますが、アナログ車体はディジタルよりうるさいですね。ギアなのかモータ自体なのか軸受なのか、今のところ不明です。

履歴

試作基板

ユニバーサル基板上に手組みした試作基板の写真です。基板側電話線はモジュラージャックがピッチに乗らないので、シングル4ピンで配線しています。基板化したときに、これはモジュラージャックに変更しました。

使用方法

頒布基板

余分に基板を作成しましたので、この形で頒布可能です(数量限定)。
ある程度電気的知識を持っている方のみ購入してください。本記事が理解できる方を対象とします。
購入はBASEのSHOPでお願いします。

基板サイズは82x37mm,t1.6のガラエポ基板です。タカチTWN5-3-9Wの基板サイズで作成しました。中央の蛇の目部分は、ユニバーサル領域です。受信機を両面テープで固定する領域として利用予定です。

表面部品実装図

受信機はS.BUS信号を出力できるものを使用してください。また送信機と受信機のペアリングはあらかじめしておいたほうが良いでしょう。
接続ですが、CN1の3pinコネクタと受信機のS.BUS出力を、向きを注意して接続してください。CN1の四角いランド、四角いシルクの囲み、あるいは"G"の文字位置はGND(グラウンド)を示します。大抵のケーブルは黒い配線色になっていると思います。

接続例

外部電源を接続する場合は、CN3を使用してください。電池の"-"側を四角いランド側に接続してください。ただし、電池(CN3)とUSBは同時に接続しないでください。PCを壊すかも知れません。
付属のモジュラーケーブルのプラグをCN2に挿入してください。基板側の準備ができたら、モジュラーケーブルの反対側をCarera®DIGITAL132のコントロールユニットに差し込みます。
これら接続は左の写真を参考に、間違えないようにしてください。電池はほどんどの場合不要ですが、写真では接続方向の説明のために、電池を接続しています。また、基板を写真のように、床に直置きしないでください。

POM材をスペーサ

実際の使用では、基板を何らかの方法で絶縁して使用してください。左の写真ではPOM材をスペーサとして使用してみました。接着性のゴム足を使ってもいいでしょう。安全のために基板上も絶縁した方が良いです。

シミュレーションゲーム

joypro7全景

実のところ、ラジコンのプロポをPCのゲームコントローラに使う、と言うのが開発のスタートです。ラジコン送信機でPCのフライトシミュレータをするものです。当初はトレーナケーブルの信号をデコードしていましたが、最近はS.BUSを利用しています。受信機が必要になるのですが、ワイヤレスの魅力には勝てません。S.BUSの解析記事はいずれ別掲したいと思います。

写真のコントローラは、多チャンネルの空用のプロポを使う物で、キーボードやマウスコントローラ機能もあります。Futaba T12Kを使っているのですが、これだけでは視点変更とかしにくいので、DIOピンにスイッチを追加しています。

PCゲームのプラットホームであるSteamにはRF(RealFlight)やVirtual SlotCars(スロットカーのシミュレーションゲーム、以降VSCと略)、VRC PRO(ラジコンカーのシミュレーションゲーム,以降VRCと略)があり、結構楽しめます。

VSCゲーム中

RFは以前から変換器(JOYPRO:自社プロジェクト)で遊んでいましたが、VSCやVRCはXBOXコントローラでないとコントローラと認識してくれなくて、これらでプロポは使用できませんでした。どうやらXBOXコントローラが従来のゲームコントローラのUSB_IDと異なっているみたいです。そこで、今回制作のPro Micro基板では、XInputライブラリーに変更してVSCやVRCをゲームできるようにしました。
写真はVSCでのゲームイメージです。

つまり、本Carrera® Digital対応ワイヤレスコントローラ・アダプタ基板は出力先を変えて、USBコネクタをPCに接続すれば、XBOXコントローラとしてラジコンプロポが使えます。次の節ではXBOXコントローラとしての所作を説明します。

XBOXコントローラとして

最初に、XBOXコントローラがWindows11のプロパティでは、どのように見えるのか確認しました。それに合わせてプロポの各ch(チャネル)と機能をプログラムして、XBOX互換にしました。

コントローラを接続して電源が入った状態にしてから、"設定" → "Bluetoothとデバイス" → "デバイス"とクリックしていき、表示されたリストの一番下にある"その他のデバイスとプリンターの設定"をクリックします。そうすると"デバイスとプリンター"窓が開いて、"XINPUT互換HIDデバイス"アイコンが表示されます。(なぜXBOXコントローラを接続してこの表示なのか不明です)

XBOXのプロパティ

この"XINPUT互換HIDデバイス"アイコンを右クリックしてプルダウンメニューの"ゲームコントローラの設定"を左クリックします。開いた"ゲームコントローラ"窓に表示された"Controller(Xbox One For Windows)"がフォーカスされた状態で"プロパティ"タブを左クリックします。するとフォーカス名のプロパティ窓が表示されます。この状態でXBOXのスティックやスイッチを操作して各機能の変化を確認します。これを基準として、プロポのスイッチ操作が所望のXBOX動作と同じになるようにプログラムしました。

XBOXのアサイン表

XBOXコントローラのプロパティをメモし、それにコントローラのスティック、スイッチの名前を控えて、比較基準を作成しました。

T12Kのアサイン表

T12K PLUSをXBOXコントローラに模して機能割当てをしました。T4PMのch2をトリガーとしてZ軸に割り当てる必要性から、T12KではY軸とZ軸が2重定義なりました。ゲーム側の設定では気を付けなければなりません。本基板においてはT12Kはおまけなのでこれで良しとします。

注:XBOXのUSBドングル(あるいはUSBケーブル)を抜いて、本基板をUSBに接続してWindowsのプロパティを表示する過程で、"デバイスとプリンター"窓には、"Xbox360 Controller for Windows" と表示されました。しかもアイコンが立体的にそれらしいものになりました。本物を差したときと逆な感じがしますが、原因不明です。

T12Kの設定表

これはT12K PLUSのチャネルと機能名およびスイッチ番号の対応表です。S.BUSのデコードでは順番にチャネル番号を割振りますが、それは実際にはスティックなのか、スイッチなのかはユーザの設定によります。スティックの4舵はこのとおりの順番でほぼ決まっているはずですが、その他はこの順番で割振りました。
また、NOR/REVはすべてNOR(normal)にしています。

RC6GSのアサイン表 RC6GSの設定表

ステアリング型のプロポ、RC6GSv3は7chの送信機なのでボタンスイッチにも機能を割り振ることができます。

RC6GSv3で2つ問題が発生しました。1つはch3のフラつきです。ボリュームチャネルなのですが、値が安定せずに微振動しています。そうすると操作しなくても常にUSB通信が発生することになります。実質問題無いとは言え気になります。そこでch3の分解能を下げて安定化させました。
2つ目の問題として、RC6GSv3は7chまでとなっているのですが、S.BUSで未使用のch8以降がそれ以前のチャネル操作により変動します。あるいは勝手にフラツイて変動します。T12Kの12ch(実質14ch)までS.BUSをデコードして出力設定しているので、未使用のチャネルは安定していてほしいと思います。そこでS.BUSのエンドコードでRC6GSv3(あるいはRadioLinkのコード)を判断して出力chに制限をかけるようにしました。

T4PMのアサイン表

T4PMは4chの送信機で、ボタンに割り振る機能はありません。送信機のSW1をch3に設定し、Y回転に割当てました。またSW2はch4に設定し、X回転に割当てました。

URL、用語説明など

Carrera®DIGITAL124あるいはCarrera®DIGITAL132およびCarrera®EVOLUTIONはCarrera-toys社の商品です。本記事ではデジタル仕様のものをCarrera® Digitalと表現しています。またアナログ版をCarrera®EVOLUTIONと表現しています。本記事はCarrera®DIGITAL132のコントロールユニットおよびCarrera®EVOLUTIONの利用を深めるために書いております。理解不足により言葉足らずの記述があればご容赦お願いします。
日本ではKYOSHO(京商株式会社)が正規販売代理店です。

S.BUS(エスバス)はFutaba(双葉電子工業株式会社、以降Futabaと記述)がラジコンサーボ制御のために、シリアル通信のフォーマット(UART:調歩同期式通信、または非同期通信とも言う)を利用して、11bits/chのデータを工夫して送っている通信方式です。解析してみると特にスクランブルとかしていなくて、単純に8+3,5+6,2+8+1,,のように11bitのデータを8bit区切りにデータを分散して送信していました。UART機能は大抵のCPUに備わっているので各サーボは単純化でき、また受信機から各サーボに個別配線する必要がなくなる便利な通信方式です。Futabaホームページ及び取説によればS.BUSが正式呼称のようです。本記事中あるいはソースコードでは、Sbusと省略して記述することがあります。そのときはS.BUSと読替えてください。

T4PM PLUSおよびT12KはFutaba社のラジコン送信機です。本記事中でT4PM PLUSはT4PMと省略して書いています。
T4PMは4ch(4チャネル)のカー用送信機で、人差し指で操作するトリガータイプのスロットルと、タイヤ型のステアリングを有しています。対応する受信機はR314SB-Eを使用しました。これはプロポセットになっていたものです。
T12Kは12chの空用送信機です。拡張チャネルとしてデジタルが2ch使えて合計14ch利用できます。対応受信機は多いのですが、私は本記事のようなシミュレータにはR3001SBを使用しています。これはS.BUSとS.BUS2の2ポート出力になっており、安価でシミュレータには最適と思っています。

RC6GS_V3はRadioLink社のラジコン送信機です。V3以降が7ch送信機で、V3が付かないと6chの送信機らしいです。本記事ではRC6GSv3と表現しました。受信機はR7FG_V1.4を使用しました。これは単にR7FGと省略して記述しています。

Windows11はマイクロソフトのオペレーティングシステムです。

回路説明

ir940回路図

受信機からのSbus信号はCN1から入り、Q1でレベルシフトし、ProMicroのシリアル入力に入れます。Sbusの信号ラインはRS232CのMAX232レシーバと同様にインバートして、スタートbit=LowとしてCPU(ATMEGA32U4)に取り込みます。プロトコルは100kbps,8bits,even,1stop,LSBfirstです。

ProMicroコンパチ基板はATMEGA32U4を使用しています。このCPUはUSBインターフェイス持っており、USBからプログラムできます。CPU電源は基板上のジャンパーをショートして5Vとしています。これでシステムクロックが16MHzで動作できます。

Carrera®DigitalとはRJ12モジュラージャックのCN2でインターフェイスします。このときUSBは差していないでしょうから、電圧はCarrera Digital側から供給されることになります。ただしこの端子から取り出せる電流値は不明です。純正コントローラは負荷としてポテンショメータ(スライド型可変ボリューム)が接続してあるだけでした。電圧をテスターで測ったところ4.1Vでした。
本回路では逆流防止ダイオードD1をとおって電源供給をしています。その電圧(ネット名:VCC)は3.8Vでした。この電源はProMicro,電子ボリュームのAD8400,プロポ受信機に供給します。電流、電圧とも容量に不安(不満?)があるものの、とりあえずはCN2からのこの電源で動くことを確認しました。

先に書いた受信機電源はSbus経由で供給します。FutabaのT4PM送信機は受信機電圧をテレメトリしており、送信機のパネルで電圧値がわかります。送信機によっては受信機の動作下限電圧を制限しているものがあると思います。このときはその電圧を変更して実際の動作可能電圧を探ってみてください。RadioLinkのRC6GSv3+R7FGではこれに引っかかりました。

また、受信機にサーボは接続しないでください。サーボはまともに動かないでしょうし、明らかに電流の引き過ぎになります。モジュラーケーブルの線材も細くてサーボ駆動電流は流せません。最悪の場合発熱発火の恐れがあります。Carrera Digitalのコントローラユニットを壊すかも知れません。

もしも不安があるのなら、USBから電圧供給しても構いません。ただしモバイルバッテリーを使用したとき、消費電流が少なくて保護回路が動作し、USB電源がシャットダウンするかも知れません。
あるいは受信機に5V電源を接続して、SbusからProMicroに電源を供給する方法があります。しかしこの方法は正確に5.5V以下の電圧に限定してください。乾電池4個直列は定格で6.0V超えでATmega32U4の絶対最大定格を超えてしまいます。NiCd4個であれば満充電であってもギリギリセーフと思います。
結局、別電源を使うのなら、CN3を使ってください。5Vの安定化電源(NJM2872F05)を用意しましたので、こちらだと乾電池電源でも使えます。定格は+2.5~+14Vになっていますが、安全のためには乾電池4個直列が最適かと思います。

肝心のコントローラをシミュレートしているのは回路図右半分です。
純正コントローラを調べたところ、スライドボリュームが使用されており、抵抗値は10.3kΩでした。カーボンの幅が一定に見えるのでB型でしょう。これに10kΩ固定抵抗器が直列に接続してありました。結線はコントローラから親指を離した状態でGND電位、押していって電圧が増えていく方向です。

この機能を実現するためにAD8400を採用しました。1チャンネルのデジタル・ポテンショメータです。256ポジションなのでプロポのビット数からすれば荒いのですが、スロットカーのコントロールとしては十分でしょう。アナログのバナナプラグで接続するコントローラは16接点みたいです。所有していないのでネットの写真で判別しており定かではありません。

オリジナルは赤色線から固定10kΩ経由でスライドボリュームに接続し黒色線に帰っています。黄色線が検出ラインです。本回路では赤色線を電源ラインと判断し、パスコン(C3)と逆流防止ダイオード(D1)を付加しています。

CN2の回路記号の中にあるBRGYはそれぞれ黒赤緑黄の配線色をあらわしています。純正コントローラの接続に合わせています。このモジュラープラグは6pinタイプですが中央の4pinのみ使用されています。

そのモジュラープラグですが、抜け止めの爪の位置が中央ではなく、端に偏っています。これはCarrera®Digitalのコントロールユニットのモジュラージャックと同じ形状を採用したからです。結線位置も同じです。

R6は0Ωで特に意味はありません。テストピンの代わりです。
R7も同じ0Ωです。Carrera®Digitalにおいては意味はありませんが、Carrera®EvolutionなどのAnalogにおいては、取り外してB端を浮かす為に必要です。Evolutionのコントローラを調べたら、スライドボリュームのA端子とワイプ端子が接続してあるだけでした。そのためR5を0Ωに変更し、D1削除,C3削除,R7削除して、R色線とY色線をEvolutionコントロールユニットに接続すればAnalogでもワイヤレスコントローラが実現できます。ただし、コントロールユニット側のコネクタは特殊なので、既存のコントローラからコネクタを拝借して仮はんだ付けで動作を少し確かめただけです。
注意すべきは、アナログと言ってもバナナプラグで接続するコントローラの代わりに、この回路を接続しないでください。そういうことをすると確実にU2(AD8400)が壊れます。そのアナログには、京商からバナナプラグ対応のワイヤレスコントローラが販売されているようですから、そちらを使用してください。

Q2は、コントローラにある接点スイッチを実現するものです。コントローラの接続番号を覚えるために必要なスイッチ機構です。あるいはヘッドライトの点灯、消灯をコントロールします。

SW1はRESETピンに直結していますが、ATMEGA32U4をArduinoのIDEから再プログラミングするときに必要なスイッチです。Arduino IDEのコンパイル中に押しておいて、書き込みになったときに離します。AD8400の制御だけでプログラムを組めば、そういう状態は発生しないと思いますが、このProMicroに、Xbox互換のドライバーを組み込んでいるので、USBの機能切り替えの為に必要になっています。

ソフトウェア

本装置はArduino IDEを使用してプログラムしています。以下その使用を前提とした説明です。

カー用プロポを使ってCarrera® Digitalのコントローラを実現するために、入力は受信機からのS.BUS信号をUARTシリアル受信して、各ch(チャネル、チャンネル)にデコードしています。次にそのデータをデジタル・ポテンショメータのAD8400に出力します。

スロットカー(Slot Cars)ではそれだけですが、先に述べたように本装置はXBOXコントローラとして互換使用できます。S.BUSの入力はスロットカーと共通で、出力先が異なるだけです。出力はXInputライブラリーに吐き出すだけです。

S.BUSのデコード

UART受信用にバッファを定義しておきます。S.BUSは25バイトですが、バッファは余裕をみて32バイト確保しています。
STKbufferは各ch(チャネル)毎のデータを保存するバッファです。デコード後はこのバッファのデータを利用します。STKoldやSTKcountはXInputを使ってXBOXコントローラになるときの小細工です。


	// シリアル受信バッファ
	#define RdEND  0x1F			// RdBuffer バッファの長さ -1、Sbusの文字長25byte+α
	byte    RdBuffer[RdEND +1];	// Serial1 受信buffer
	unsigned int RdIndex = 0;

	// 受信ワーク
	const unsigned int STKmax = 16;
	unsigned int STKbuffer[STKmax] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
	unsigned int STKindex = 0;
	unsigned int STKold[STKmax];	// pushSWとして1回のみ出力するための記憶用
	unsigned int STKcount[STKmax];	// オルタネイトSWをモーメンタリと見做すためのタイマー		

UARTを使用するためにポートの初期化が必要です。S.BUS受信だけですのでRXIピンしか使っていません。TXOピンも有効になっていますので、チェック用のモニター出力として使えます。ただしボーレイトは受信と同じ100kbpsになってしまいます。


void setup() {
	Serial1.begin(100000,SERIAL_8E1);		// 100kbps,8bit,even,1stop
}
		

初期設定は出来たのでloop文に処理を書いていきます。
最初にS.BUSのデータ入力待ちをします。ReadBuffer関数はバッファに貯まっている文字数をチェックして帰ってきます。データを25バイト受信済みであれば25を返してきます。そうでないときは0を返します。ReadBufferは無限ループで繰り返し呼び出す関数です。結果があるときのみif文内でS.BUSデータをデコードをします。結果はSTKbuffer[]にch順に10bitsで格納します。
最後のデータを1/2にしているところは、ループ内の5bitシフトを6bitシフトすれば不要です。


	if( nums = ReadBuffer( RdBuffer, &RdIndex ) ) {	// Sbusストリームが完結すれば,=25
		// CHデータに分解格納
		n=1;								// start byte'0F'無視
		j = 0;								// bit counter
		maskbit = 0x01;						// LSbから評価
		for(m=0; m<STKmax; m++) {			// CH counter(T12Kは12CHだが、16CHがバイト区切り)
			for(i=0; i<11; i++) {			// 各CHは {符号(正1,負0),b9~b0}の11bit有効
				STKbuffer[m] >>= 1;			// あらかじめMSb=0を入れる
				if( RdBuffer[n] & maskbit ) {
					STKbuffer[m] |= 0x8000;	// MSbにセット
				}
				maskbit <<= 1;
				j++;						// bit counter
				if( j > 7 ) {
					j = 0;
					n++;
					maskbit = 0x01;			// 再設定
				}
			}
			STKbuffer[m] >>= 5;				// b10~b0有効(wordで右詰め)
		}

		// 受信値を10bit範囲にする
		for( i=0; i<STKmax; i++) {
				nums = STKbuffer[i] >> 1;		// 1/2
				STKbuffer[i] = nums;			// 保存
		}
		

ReadBuffer関数の中身です。Serial1のバッファにデータがあればあるだけをRdBufferに移動します。
S.BUSは先頭の0x0Fはスタートコードとして固定のようです。一方エンドコードは決まっていないみたいです。Futabaでは決めがあるようですが(最終byteが0x?4とか)、RadioLinkのRC6GSv3では常に0x00でした。よって受信文字数で終了判断しています。
タイマー割り込みを使用して判断する方法もありますが、今のところ文字数だけでも動作しています。


uint16_t ReadBuffer( uint8_t *buff, uint16_t *index )
{
	uint8_t  data;
	uint16_t temp,nums;

	nums = 0;
	while ( Serial1.available() ) { 				// dataがあれば
		data = Serial1.read();						// 取得した文字
		if( *index == 0 ) {
			if( data == 0x0F ) {					// ストリームの最初の文字
				temp = *index;
				buff[temp++] = data; 				// set data
				if (temp <= RdEND ) *index = temp;	// =1
			}
		} else if( *index < 24 ) {					// バイト数で終了判断
			temp = *index;
			buff[temp++] = data; 					// set data
			if (temp <= RdEND ) *index = temp;		// ターンオーバー無し
		} else {
			temp = *index;
			buff[temp++] = data; 					// set data
			if (temp <= RdEND ) *index = temp;		// ターンオーバー無し
			nums = *index;							// =25
			*index = 0;								// 次の為に初期化
		}
	}
	return nums;
}
		

スロットカーの出力

デジタル・ポテンショメータIC(AD:AD8400)にはSPI(3線シリアル)ポートを使って書き込みします。それにはライブラリ(ドライバ)を追加します。


	#include <SPI.h>
		

次にそのAD8400のCSピン制御のためにデジタルポート番号を定義します(D10)。同時にQ2のBASEを制御するポート(D9)も定義しておきます。


	// Arduino Pro Micro コンパチ品
	// SCLK: D15 pin9   AD8400:pin5(CLK)
	// MISO: D14 pin11  AD8400:未接続 AD8400の出力なし、PUP
	// MOSI: D16 pin10  AD8400:pin4(SDI)
	const int AD8400_CS = 10;	// D10, AD8400:pin3(nCS) 
	const int SlotCont_SW = 9;	// D9,  Controllerのpush SW相当のPNP TRのBaseピン(DTC123) 
		

初期設定に追加します。


void setup() {
	SPI.begin();
	SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));

	pinMode(AD8400_CS, OUTPUT);			// Digital POTmeterのnCS
	pinMode(SlotCont_SW, OUTPUT);		// Digital Slot Car Lane Change SW(H出力でPNP Tr ON)
}
		

STKbuffer[1]にはS.BUSの2番目(ch2)のデータが入っています。これはトリガーのデータです。データは10bitに丸めていますので0~1023の範囲です。トリガーはセンターを境にスロットル側、ブレーキ側になっています。そうすると512がセンターになります。これに不感帯を設けるために、スロットル側の0~511を8センター側へズラします。更にセンターが0値になるように反転します。ブレーキ側は、バックはできないことと、AD8400にブレーキの大小は書けないことにより、STKbuffer[1]が512より多きときは0にしています。
最初に、定数としてT12K-R3008SBで測定した値を、グローバル変数としてコードに追加しておきます。


	// 受信データ範囲(Futaba R3008SB 8/SB端子,実測値)
	// UART:100kbps,8bit,even,1stop,11bitsストリーム,break=Hlevel(3.3V),25bytes
	// Futaba T12K T-FHSS/S.BUS R3008SB 設定チャンネル:モードB
	//  -125%  -100%   -75%   -50%    0%    +50%   +75%  +100%  +125%
	//   0x0B8  0x160  0x208  0x2B0  0x400  0x550  0x5F8  0x6A0  0x748   11bits
	//     184    352    520    688   1024   1360   1548   1696   1864   11bits
	//      92    176    260    344    512    680    774    848    932   10bits
	//      23     44     65     86    128    170    193    212    233    8bits
	// DG1,DG2: OFF=0x070,ON=0x790
	const unsigned int p75per  = 774;	// 10bits,以下同
	const unsigned int p50per  = 680;
	const unsigned int p25per  = 596;
	const unsigned int zeroper = 512;
	const unsigned int n25per  = 428;
	const unsigned int n50per  = 344;
	const unsigned int n75per  = 260;

	const unsigned int TRGdeadwidth = 8;		// 不感帯幅±8
		
実際のloop()内のコードは以下のようになります。

	uint16_t stick1;						// work
	uint8_t  spivalue;

	if( STKbuffer[1] < (zeroper - TRGdeadwidth) ) {			// 512 - 8 = 504
		stick1 = (zeroper - TRGdeadwidth) - STKbuffer[1];	// 1~419,(504-85)
	} else {
		stick1 = 0;
	}
		

T4PMのEP(End Point)を120%に調整して、トリガー最大値が211でした。AD8400の最大値255とは開きがあるので、掛け算で補正します。255/211=1.213になりますので1.25倍にします。更にそれを8bitsにし、SPI用の8bits変数に格納します。


	stick1 += stick1 >> 2;		// 1.25=(1+0.25)
	stick1 = stick1 >> 1;		// 8bit order
	( stick1 > 255 )? (spivalue = 255): (spivalue = stick1);
		

AD8400はアドレス2bit,データ8bitの合計10bitsのデータが必要です。一方ATmega32U4のSPI出力は8bits単位のため2回書き込みで16bits書き込んでしまいます。しかしながらbit差は、AD8400がCSの立上り時に右詰めで解釈してくれるようです。そのため単純に2回書き込みすれば良くなります。AD8400は1チャネルのためアドレスは00になり、最初は0x00を書き込みます。


	digitalWrite(AD8400_CS, LOW);		// SPI CS low
	SPI.transfer(0x00);					// AD8400は1chのみ
	SPI.transfer(spivalue);				// 実データの書き込み
	digitalWrite(AD8400_CS, HIGH);		// SPI CS high
		

Carrera® Digitalのコントローラにはスイッチ入力があります。人差し指の位置にある赤いプッシュスイッチです。 これをT4PMのステアリングに割当てました。ステアリングが±75%以上でスイッチONにしています。またそのON/OFFは75%の境界を跨いだ1回のみにしています。T4PMは4chなのでch3,ch4がありますが、それらに割り当てるスイッチが押しにくいので、ステアリングに割り当てています。


	if( STKbuffer[0] > p75per ) {				// SW ON操作
		if( STKold[0] <= p75per ) {				// 以前異なる
			digitalWrite(SlotCont_SW, HIGH);	// TR base=high
			STKold[0] = STKbuffer[0];			// 記憶
		}
	} else if( STKbuffer[0] < n75per ) {		// SW ON操作
		if( STKold[0] >= n75per ) {				// 以前異なる
			digitalWrite(SlotCont_SW, HIGH);	// TR base=high
			STKold[0] = STKbuffer[0];			// 記憶
		}
	} else {									// SW OFF位置
		if( STKold[0] > p75per ) {				// 以前ON位置だった
			digitalWrite(SlotCont_SW, LOW);		// TR base=low
			STKold[0] = STKbuffer[0];			// 記憶
		} else if( STKold[0] < n75per ) {		// 以前ON位置だった
			digitalWrite(SlotCont_SW, LOW);		// TR base=low
			STKold[0] = STKbuffer[0];			// 記憶
		}
	}
		

このような1回だけ送信するというような操作は、バスのトラフィックを無駄に増やさないと言う配慮です。しかしXInputライブラリは内部でそういう動作をしているようです。ただ二重に動作しても何ら問題ないのでそのままにしています。
USB送信が発生したときは、Pro Micro基板上の赤LEDが点灯するのでその状態を確認できます。

以上の実装でカー用プロポ(Futaba:T4PM)を、Carrera®DIGITAL132(142)のワイヤレス・コントローラとして利用できました。スライドボリューム構造をディジタルICに変えているだけですので、特に難しいところはありません。またハードウェアの説明項でも述べたとおり、RJ12モジュラージャックの出力部分を少し変更すれば、アナログ(Carrera® EVOLUTION)にも使えます。

ここまでは、S.BUS信号をデコードしてチャネル信号を取り出す方法、およびSPIバスでデジタル・ポテンショメータへの書き込み方法をAruino IDEで例示しました。それによりスロットカーのコントロールをラジコンプロポで実行可能になりました。以降は同じラジコンプロポをPCゲームのコントローラとするコーディングを例示します。

XInputのインストール

XBOXコントローラを実現するために、XInputライブラリを利用しました。そのためボードマネージャよりXInput AVR Boardsを探してインストールします。David Madison氏作で、バージョンは1.0.5でした。
インストールできたら同じくボードマネージャの並びのプルダウンメニューの中からXInput AVR Boards をフォーカスし、新たなプルダウンメニューの中からArduino Leonardo w/XInputを選択します(Pro Microの場合)。これでXInputを使用する準備ができたので、コーディングを進めます。

XBOXコントローラの実装

プログラムの先頭でライブラリ使用を宣言します。


	#include <XInput.h>
		

定数定義を追加しました。ターゲットプロポのT4PMでの値です。若干T12Kとは異なりますが、ほぼ同じような値でした。ただ、XInputは動作の最大最小は補正しなくてもパラメータ設定ができるので、今回カー用プロポのT4PMに合わせました。なお、これらはEP(エンドポイント)を変えながらスティックを振り切って求めた値です。当然トリムやミキシングによって値は代わりますので、それらは中立あるいは0として測定しています。


	// Futaba T4PM T-FHSS/S.BUS R314SB-E, ch1(trigger入力 (EndPoint変更で計測)
	//   -120%  -100%   -75%   -50%   -25%    0%    +25%   +50%   +75%  +100%  +120% 
	//   0x052  0x099  0x0F3  0x14C  0x1A6  0x1FF  0x258  0x2B1  0x30B  0x362  0x3AA   10bits
	//      82    153    243    332    422    511    600    689    779    866    938   10bits
	//      85    156    245    334    423    512    601    690    779    868    939   10bits
	const unsigned int T4PMmin = 85;	// EndPoint 120% THR FWD(Throttle Forward)
	const unsigned int T4PMmax = 939;	// EndPoint 120% THR BRK(Throttle Break)
		

XInputは最大最小の振れ幅を定義する関数がありますので、それを定義しておきます。またスティック中央に不感帯を設けておきます。バージョンのこなれているゲームはこれを調整するパラメータがあるようですが、無いゲームもありますので念の為その計算もしています。


	// T4PM range
	const unsigned int JOYdeadwidth = 16;						// 不感帯幅±16
	const unsigned int JOYrangemax = T4PMmax - JOYdeadwidth;	// T4PM 939-16=923
	const unsigned int JOYrangemin = T4PMmin + JOYdeadwidth;	// T4PM 85+16=101
	const unsigned int TRGdeadwidth = 8;						// 不感帯幅±8
	const unsigned int TRGrangemax = zeroper - TRGdeadwidth - T4PMmin;	// 512-8-85=419
	const unsigned int TRGrangemin = 0;							// ±値として設定の為
		

初期設定にジョイスティック、およびトリガーの動作範囲を追加し、XInputの使用を開始します。


void setup() {
	XInput.setJoystickRange(JOYrangemin, JOYrangemax);	// ジョイスティック
	XInput.setTriggerRange(TRGrangemin,TRGrangemax);	// スロットル、ブレーキ
	XInput.begin();
}
		

初期設定はできたのでloop文に処理を書いていきます。
プロポのch2値が0~511のときはXBOXコントローラのRTにスロットル値として書き、512~1023のときはXBOXコントローラのLTにブレーキ値として書き込みます。


	// 0~503  504~519(不感帯 16幅) 521~1023
	if( STKbuffer[1] < (zeroper - TRGdeadwidth) ) {			// 512 - 8 = 504
		stick1 = (zeroper - TRGdeadwidth) - STKbuffer[1]; 	// 504 - STKbuffer[1](min85)
		if( stick1 > TRGrangemax ) stick1 = TRGrangemax;
		XInput.setTrigger(TRIGGER_RIGHT, stick1);			// センターから減る=スロットル
		XInput.setTrigger(TRIGGER_LEFT,  TRGrangemin);
	} else if( STKbuffer[1] > (zeroper + TRGdeadwidth) ) {	// 512 + 8 = 520
		stick1 = STKbuffer[1] - (zeroper +TRGdeadwidth);	// STKbuffer[1](max939) - 520
		if( stick1 > TRGrangemax ) stick1 = TRGrangemax;
		XInput.setTrigger(TRIGGER_RIGHT, TRGrangemin);
		XInput.setTrigger(TRIGGER_LEFT,  stick1);			// センターから増える=ブレーキ
	} else {
		XInput.setTrigger(TRIGGER_RIGHT, TRGrangemin);
		XInput.setTrigger(TRIGGER_LEFT,  TRGrangemin);
	}
		

STKbuffer[0]をステアリング(X軸)に設定のため、stick0に保存します。また遊び(不感帯)を設けます。stick0,stick1は単なるワーク変数です。


	if( STKbuffer[0] < (zeroper - JOYdeadwidth) ) {			// 512-16=496
		stick0 = STKbuffer[0] + JOYdeadwidth;				// 495+16=511
		if( stick0 < JOYrangemin ) stick0 = JOYrangemin;
	} else if( STKbuffer[0] > (zeroper + JOYdeadwidth) ) {	// 512+16=528
		stick0 = STKbuffer[0] - JOYdeadwidth;				// 529-16=513
		if( stick0 > JOYrangemax ) stick0 = JOYrangemax;
	} else {
		stick0 = zeroper;
	}
		

T4PMではSTKbuffer[1]をスロットル/ブレーキに割り当て済みですが、XBOXコントローラとしてみると、Y軸が空いているので、T12Kのエレベータを想定して、これをY軸に設定するためstick1に保存します。
T12Kのエレベータがノーマルだと動きが逆だったので、ここでリバースしています。もちろんプロポ側でそうしても構いませんが、他の機器との兼ね合いでこのソフトでリバースしています。


	if( ( ~STKbuffer[1] & 0x03FF) < (zeroper - JOYdeadwidth) ) {		// 512-16=496
		stick1 = ( ~STKbuffer[1] & 0x03FF) + JOYdeadwidth;				// 495+16=511
		if( stick1 < JOYrangemin ) stick1 = JOYrangemin;
	} else if( ( ~STKbuffer[1] & 0x03FF) > (zeroper + JOYdeadwidth) ) {	// 512+16=528
		stick1 = ( ~STKbuffer[1] & 0x03FF) - JOYdeadwidth;				// 529-16=513
		if( stick1 > JOYrangemax ) stick1 = JOYrangemax;
	} else {
		stick1 = zeroper;
	}
		

T4PMではステアリング/スロットル(ブレーキ)ですが、T12Kで言えばエルロン/エレベータがそれぞれstick0,stick1のワークに入りました。これをXnputを使ってXBOXコントローラの左ジョイスティックに割当てます。


	XInput.setJoystick(JOY_LEFT, stick0, stick1 );		// stick0をX軸,stick1をY軸
		

Xboxコントローラの右側ジョイスティックへ、T12Kのスロットル/ラダー(mode2)、もしくはT4PMのch3,ch4を割当てます。ワークを再利用しているので少し紛らわしくなっていますが、不感帯なんかの考え方は前項までと同じです。


	if( STKbuffer[2] < (zeroper - JOYdeadwidth) ) {			// 512-16=496
		stick0 = STKbuffer[2] + JOYdeadwidth;				// 495+16=511
		if( stick0 < JOYrangemin ) stick0 = JOYrangemin;
	} else if( STKbuffer[2] > (zeroper + JOYdeadwidth) ) {	// 512+16=528
		stick0 = STKbuffer[2] - JOYdeadwidth;				// 529-16=513
		if( stick0 > JOYrangemax ) stick0 = JOYrangemax;
	} else {
		stick0 = zeroper;
	}

	if( STKbuffer[3] < (zeroper - JOYdeadwidth) ) {			// 512-16=496
		stick1 = STKbuffer[3] + JOYdeadwidth;				// 495+16=511
		if( stick1 < JOYrangemin ) stick1 = JOYrangemin;
	} else if( STKbuffer[3] > (zeroper + JOYdeadwidth) ) {	// 512+16=528
		stick1 = STKbuffer[3] - JOYdeadwidth;				// 529-16=513
		if( stick1 > JOYrangemax ) stick1 = JOYrangemax;
	} else {
		stick1 = zeroper;
	}

	XInput.setJoystick(JOY_RIGHT, stick1,stick0);			// X回転、Y回転へ
		

XBOXコントローラのHATスイッチ(Dpad,方向スイッチ)も設定できますが、T4PMではすでに割当てるチャネルがありません。ここではT12KのSAを上下、SBを左右に割当てた例を示します。



	//XInput.setDpad( upPad, downPad, leftPad, rightPad);
	XInput.setDpad( STKbuffer[7] < n25per, STKbuffer[7] > p25per,
					STKbuffer[8] < n25per, STKbuffer[8] > p25per );
		

XBOXコントローラにはプッシュスイッチもいくつかあり、これをプロポにどう割り振るかは悩むところです。送信機にはレバースイッチがほとんであり、プッシュスイッチはありません。以下はそのレバースイッチをプッシュスイッチ相当にする方法です。例としてT12Kに設定しているch5=GEAR=SCの場合を示します。SCのレバーを上でbutton1,中央でOFF,下でbutton2に割り当てています。button1,button2はWindowsのゲームコントローラのプロパティに現れるスイッチ番号です。XBOXではA,Bボタンになります。


	StickSwitch( &STKbuffer[4],  &STKold[4],  1,  2 );

/* ------------------------------------------------------------------
  送信機レバースイッチをbutton出力する
------------------------------------------------------------------ */
void StickSwitch( uint16_t *stick, uint16_t *old, uint8_t btnA, uint8_t btnB )
{
	if( *stick < n25per ) {					// 3pos.オルタネートスイッチ上側
		if( *old >= n25per ) {				// 以前と異なる
			XInput.setButton(btnA, 1);		// buttonA=ON
			*old = *stick;					// 記憶
		}
	} else if( *stick > p75per ) {			// 3pos.オルタネートスイッチ下側
		if( *old <= p75per ) {				// 以前と異なる
			XInput.setButton(btnB, 1);		// buttonB=ON
			*old = *stick;					// 記憶
		}
	} else {								// 3pos.オルタネートスイッチ中側
		if( *old < n25per ) {				// 以前上側だった
			XInput.setButton(btnA, 0);		// buttonA=OFF
			*old = *stick;					// 記憶
		} else if( *old < p75per ) {		// 以前下側だった
			XInput.setButton(btnB, 0);		// buttonB=OFF
			*old = *stick;					// 記憶
		}
	}
}
		

免責

本記事の使用、不使用に関わらず、本記事の情報に基づく事件、事故等の利用者の不利益に関し筆者は一切関知しません。全ては個人責任においてご利用ください。