見出し画像

【Unity】FTDIのFTD2XX_NETを使ったシリアルポート通信(C#)

FTDI社から提供されているFTDIドライバのラッパーライブラリ(FTD2XX_NET)を使ってUnityからシリアルポートで送受信を行ってみます。

このFTD2XX_NETはドライバを制御するという比較的低レイヤー層を扱うため、ラッパーライブラリといえどC#のような高級言語に慣れ親したんだ人からするとやや使い方が難解です。

しかしその分、使いこなせるようになると.NET FrameworkのSerialPort クラスよりもきめ細かい設定ができるようになるため、チャレンジしてみる価値はありそうです。前置きが長くなりましたが、早速やっていきましょう!

動作環境

Unity 2020.3.12f1
Windows10

1. 準備

以下のFTDIのサイトからFTD2XX_NET.DLLをダウンロードします。

スクリーンショット 2021-07-25 230828_New

zipでダウンロードされるため、展開してFTD2XX_NET.dllを使用するUnityプロジェクトへドラッグアンドドロップします。これで使用できるようになります。

スクリーンショット 2021-07-25 230657

2. スクリプト

以下のスクリプトを適当なオブジェクトにアタッチします。

using UnityEngine;
using System;
using FTD2XX_NET;
using System.Threading.Tasks;

public class SerialPortController : MonoBehaviour
{
   private const string PORT_NAME = "COM3";
   private FTDI m_Ftdi;
   private bool m_isLoop = true;
   
   async void Start() {
       m_Ftdi = new FTDI();
       uint deviceCount = 0;
       
       var ftStatus = m_Ftdi.GetNumberOfDevices(ref deviceCount);
       if (ftStatus != FTDI.FT_STATUS.FT_OK) {
           throw new InvalidOperationException("Could not get port size.");
       }

       for (uint i = 0; i < deviceCount; i++) {
           
           ftStatus = m_Ftdi.OpenByIndex(i);
           if (ftStatus != FTDI.FT_STATUS.FT_OK) {
               continue;
           }

           ftStatus = m_Ftdi.GetCOMPort(out var comPortName);
           if (ftStatus != FTDI.FT_STATUS.FT_OK) {
               throw new InvalidOperationException("Could not get port name.");
           }

           if (comPortName == PORT_NAME) {
               ftStatus = m_Ftdi.SetBaudRate(9600);
               if (ftStatus != FTDI.FT_STATUS.FT_OK) {
                   throw new InvalidOperationException("Could not set BaudRate.");
               }

               ftStatus = m_Ftdi.SetDataCharacteristics(
                   FTDI.FT_DATA_BITS.FT_BITS_8,
                   FTDI.FT_STOP_BITS.FT_STOP_BITS_1,
                   FTDI.FT_PARITY.FT_PARITY_NONE);

               if (ftStatus != FTDI.FT_STATUS.FT_OK) {
                   throw new InvalidOperationException("Could not set DataCharacteristcs.");
               }
               
               break;
           }
           
           m_Ftdi.Close();
       }

       if (m_Ftdi.IsOpen) { 
           Debug.Log(PORT_NAME + " Opened");
       }
       else {
           throw new InvalidOperationException(PORT_NAME + " not found");
       }
       
       await Read();
   }

   private void Update() {
       if (Input.GetKeyDown(KeyCode.A)) {
           Write();
       }
   }

   private void Write() {
       var data = new byte[] { 0x01, 0x02, 0x03 };
       uint numBytesWritten  = 0;
       m_Ftdi.Write(data, data.Length, ref numBytesWritten );
   }

   private async Task Read() {
       uint readLength = 0;
       uint bytesAvail = 0;
       
       await Task.Run(() => {
           while (m_isLoop) {
               m_Ftdi.GetRxBytesAvailable(ref bytesAvail);
               if (bytesAvail > 0) {
                   var buf = new byte[bytesAvail];
                   m_Ftdi.Read(buf, bytesAvail, ref readLength);
                   Debug.Log(string.Join(", ", buf) + " bytesAvail: " + bytesAvail + ", readLength: " + readLength);
               }
           }

           return "Canceled";
       });
   }
   
   private void OnDestroy() {
       m_isLoop = false;
       m_Ftdi?.Close();
   }
}

上記の例では、シリアルポートの設定は以下のようにしています。これらは使用する環境に合わせて変更してください。

・PORT_NAME:COM3
・ボーレート:9600 bps
・データ:8bit
・ストップビット:1 bit
・パリティ:none

全体の流れとしては以下のようになります。

1. FTDIデバイスの数を取得
2. その数だけfor分を回す
3. ポート名を取得
4. ポート名が一致するかチェック
5. ボーレートを設定
6. 各種ポート設定

コードの特徴としてftStatusが頻繁に出てきますが、これはポートに対して取得や設定が成功したかどうかの判定値(FTDI.FT_STATUS.FT_OK)を代入しています。仮に成功した場合は関数の引数で与えた参照(refやout)から欲しい値が得られるようになります。(デバイス数やポート名など)

このあたりが、よくあるUnityのコーディングとは記法が異なるため難しく感じますが、理屈がわかればあとは繰り返しです。コードをシンプルにするためにチェックを飛ばしてしまうこともできますが、どこでエラーが生じたのかがわかりにくくなるため、やはり都度チェックする方が望ましいといえます。

また、このラッパーライブラリに関するドキュメントは残念ながら用意されていないのですが、FTDI社からDLLを扱うためのAPIを記載したProgrammer's Guideが提供されているので、目を通してみると引数パラメータや戻り値が何となくわかるようになっているため参考にしてみると良いでしょう。

スクリーンショット 2021-07-27 223029_New

3. 実行

スクリプトではキーボードの「A」キーを押すと、バイナリで0x01、0x02、0x03の3Byteを送信します。読み取りはasync/awaitを用いて非同期で読み取りを行っています。

シリアルポートをもつデバイス(機器)とFTDIのUSBシリアル変換ケーブルを介してPCと接続します。場合によっては送信と受信信号がクロスするようにするためのケーブルやアダプタを間に挟みます。

Unityを実行し、「A」キーを押して相手側にデータが送られていることや、Unity側のログに受信データが表示されていることを確認してみてください。

スクリーンショット 2021-07-26 232558

この例はUnityを2つ立ち上げて送信と受信を行った結果です。

4. おわりに

.NET FrameworkのSerialPortクラスで事足りれば問題ないのですが、どうしても痒い所に手が届かない場合もありますので、FTDIドライバのAPIにアクセスできるラッパーを扱えるようになると強力な手段と成り得ます。

FTDIからはC#以外にも様々な言語のラッパーライブラリが提供されているので、1つの言語で使い方を覚えておくと他の言語でも応用ができる可能性があります。シリアルポートをよく扱う方は得意な言語で1度試してみると良いかもしれませんね。それでは良いシリアルポートライフを!🌱

5. 参考


この記事が気に入ったらサポートをしてみませんか?