FTDI VCPのデータ欠落対策 D2XXに切り替える方法【C# .NET】

アプリ
スポンサーリンク

こん○○は、よふかしわーくすのよふかしさんです

自作の.NETを使用したPCアプリでマイコンとのシリアル通信を扱うことがまぁまぁあるのですが
今回は3Mbpsというなかなか高速方向の通信が必要となりました

手持ちのデスクトップ、ノートPCでは、送受信共にうまくいくんですが
とあるPCでは、デバイス→PCでの受信でデータ欠落が頻発する
という症状にブチ当たりました

セキュリティソフトなど、何か裏で動いているアプリが影響していそうな気がしてますが
その辺りのお手当はできないPCだったので、アプリ側で対処することにしました

元々、FTDIとの通信をVCPで実施していて、色々デバッグコードを仕込んで確認しましたが
どうにもこうにもうまくいかず途方に暮れていたのですが
VCP→D2XXに変更したところ、全く問題なく安定した通信ができたので
備忘録として残しておきます。。。

スポンサーリンク

この記事で分かること(はじめに)

この記事は、FTDIチップを使った USBシリアル通信を、VCP(仮想COMポート)だけでなく
D2XXでも使えるようにするための解説になります

  • 「とりあえず D2XX で通信してみたい」 … 第1〜3章まで読めば、準備からサンプル実行まで一通りできます
  • 「既存の VCP アプリに D2XX も選べるようにしたい」 … 第4章で、設計と実装の進め方が分かります

用語が初めての方は、まず第1章でVCPとD2XXの違いを押さえてから
第2章の準備に進んでください


VCPとD2XXとは?

FTDIのUSBシリアル変換チップをPCから使う方法は、主に2 通り

  • VCP(仮想 COM ポート)  

Windows が「COM3」のようなCOMポートとして認識する方式
多くのシリアル通信ソフトがそのまま使えます
一方で、OSのドライバ層を経由するため、高ボーレートで遅延や不安定さが出ることがある

  • D2XX  

FTDIのネイティブドライバをアプリから直接使う方式
COMポートを経由しないため、同じデバイスでも高ボーレート、大量転送で安定しやすい傾向がある

VCPで開発するのが一般的な気がしてますが
今回の様にどうにもこうにもVCPでは遅延に起因するようなデータ欠落が発生するシーンであれば
D2XXも選べるように準備しておくとよさそうです

違いの整理

観点 VCP D2XX
ドライバ Windows 標準の COM ドライバ(FTDI VCP ドライバ) FTDI D2XX ドライバ
アプリからの見え方 COM3 などのポート名で開く デバイス一覧から「インデックス」や「シリアル番号」で開く
データの通知 DataReceived などのイベントが多い ポーリング(バイト数確認 → Read)が基本
フロー制御 アプリで None / XON-XOFF / RTS-CTS を選択可能 API で RTS/CTS などをコード側で設定することが多い

VCPとD2XXは別物なので必要な準備を次に解説します


D2XX を使うための準備

D2XXで通信するには、Windows用のドライバと2つのDLLを用意します

必要なものの全体像

  1. D2XX ドライバ  

 FTDI の公式サイトから「D2XX ドライバ」をダウンロード・インストールします

D2XX Drivers - FTDI


「VCP ドライバ」とは別パッケージなので、D2XX用を選んでください
ドライバが当たっていない場合、下記の様に
USB <-> Serial Converter
と表示されます

ドライバのインストール後、D2XXが正常認識されると、
ユニバーサルシリアルバスコントローラーに、「USB Serial Converter」が表示されます

FTD2XX_NET.dll と ftd2xx.dll の関係

D2XXで通信するには、2つのDLLの役割を押さえておくとよいです

FTD2XX_NET.dll とは?

FTDI が提供するUSBシリアル変換チップを
.NET から扱うための公式ラッパーがFTD2XX_NETです

ファイル 役割
FTD2XX_NET.dll .NET 用のラッパ。C# から FTDI クラスなどを呼ぶ
NuGet でプロジェクトの参照に追加する
ftd2xx.dll 実際のネイティブドライバ(C API)
実行時に exe と同じフォルダに置く(必須)

.NETアプリの場合はFTD2XX_NET.dllというラッパーを参照して
内部的にはftd2xx.dllを呼び出す、という感じで、簡単に使える様になってます
その分、若干オーバーヘッドを喰うとは思うので、
もしバキバキに最適化するなら、.NETの使用自体を諦めるしかない感じです

使い方(基本の流れ)

  1. FTD2XX_NET.dll をプロジェクトに追加する  

 NuGetでFTD2XX_NETまたはFTDI.FTD2XX_NETをインストールし、参照に含める

  1. ftd2xx.dll を実行ファイルと同じフォルダに置く  

bin\Debugやbin\Releaseに配置するか、
projに含めて「出力にコピー」を設定して自動コピーするとよいです

基本クラス:FTDI

FTD2XX_NETの中心となるクラスはFTDIです
基本的な使い方の準備は下記です

using FTD2XX_NET;

FTDI ftdi = new FTDI();

以降、ftdi.OpenByIndex(0) や ftdi.Write(…) のように、
このオブジェクト経由でデバイスを開いたり送受信したりします

よく使うメソッド一覧(参照用)

FTDIクラスの主なメソッドを用途別にまとめてみました

デバイス管理

メソッド 説明
GetNumberOfDevices 接続されている FTDI デバイス数を取得
GetDeviceList デバイス情報一覧を取得
OpenByIndex インデックスでデバイスを開く
OpenBySerialNumber シリアル番号で開く
Close デバイスを閉じる

通信設定

メソッド 説明
SetBaudRate ボーレート設定
SetDataCharacteristics データビット・ストップビット・パリティ
SetFlowControl フロー制御
SetTimeouts 読み書きタイムアウト設定

データ送受信

メソッド 説明
Write バイト列を書き込み
Read バイト列を読み込み
GetRxBytesAvailable 受信バッファの残量確認
Purge RX/TX バッファクリア

その他便利機能

メソッド 説明
ResetDevice デバイスリセット
SetBitMode ビットバングモード設定
GetLatencyTimer / SetLatencyTimer レイテンシタイマ設定
EEPROM_Read / EEPROM_Write EEPROM 読み書き


D2XXで動かしてみる

ここからは「開く → 設定 → 送る → 受け取る → 閉じる」の流れで実際にコードを動かしてみます

最小構成のサンプルコード(動作確認用)

以下をコンソールアプリとしてビルドし、exeと同じフォルダにftd2xx.dllを置いて実行します

using System;
using FTD2XX_NET;

class Program
{
    static void Main()
    {
        FTDI ftdi = new FTDI();
        FTDI.FT_STATUS status;

        status = ftdi.OpenByIndex(0);
        if (status != FTDI.FT_STATUS.FT_OK)
        {
            Console.WriteLine("Failed to open device");
            return;
        }

        ftdi.SetBaudRate(9600);
        ftdi.SetDataCharacteristics(
            FTDI.FT_DATA_BITS.FT_BITS_8,
            FTDI.FT_STOP_BITS.FT_STOP_BITS_1,
            FTDI.FT_PARITY.FT_PARITY_NONE);

        byte[] send = { 0x55 };
        uint written = 0;
        ftdi.Write(send, send.Length, ref written);

        byte[] recv = new byte[64];
        uint read = 0;
        ftdi.Read(recv, recv.Length, ref read);
        Console.WriteLine($"Read {read} bytes");

        ftdi.Close();
    }
}

基本の流れ

D2XXでの操作は、次の7ステップで実装できます

  1. デバイスの列挙 … GetNumberOfDevices で台数を取得
  2. デバイスを開く … OpenBySerialNumber(“FT123456”) または OpenByIndex(0)
  3. 通信設定 … SetBaudRate / SetDataCharacteristics / SetFlowControl
    (必要なら SetTimeouts)
  4. 書き込み … Write(byte[], length, ref written)
  5. 読み込み … Read(byte[], length, ref bytesRead)
    事前に GetRxBytesAvailable で受信可能バイト数を確認するとよい
  6. バッファクリア … Purge(FT_PURGE_RX | FT_PURGE_TX)
  7. 閉じる … Close()

サンプルコードは下記です

// 1. 列挙
uint deviceCount = 0;
ftdi.GetNumberOfDevices(ref deviceCount);

// 2. 開く(シリアル番号 or インデックス)
status = ftdi.OpenBySerialNumber("FT123456");   // または ftdi.OpenByIndex(0);

// 3. 設定
ftdi.SetBaudRate(115200);
ftdi.SetDataCharacteristics(
    FTDI.FT_DATA_BITS.FT_BITS_8,
    FTDI.FT_STOP_BITS.FT_STOP_BITS_1,
    FTDI.FT_PARITY.FT_PARITY_NONE);
ftdi.SetFlowControl(FTDI.FT_FLOW_CONTROL.FT_FLOW_NONE, 0x11, 0x13);  // または FT_FLOW_RTS_CTS

// 4. 書き込み
byte[] data = { 0x01, 0x02, 0x03 };
uint written = 0;
ftdi.Write(data, data.Length, ref written);

// 5. 読み込み
byte[] buffer = new byte[256];
uint bytesRead = 0;
ftdi.Read(buffer, buffer.Length, ref bytesRead);

// 6. バッファクリア
ftdi.Purge(FTDI.FT_PURGE.FT_PURGE_RX | FTDI.FT_PURGE.FT_PURGE_TX);

// 7. 閉じる
ftdi.Close();



実装時の注意点

  • フロー制御  

D2XXではフロー制御なし、RTS/CTS、は共に使えます
但し、XON/XOFFは使えないので要注意です

  • 同一デバイスの排他  

同じ FTDI デバイスをD2XXで開いている間は、COMポートは使えないです
切り替えたい場合は、デバイスを読み込み直すなどのお手当てが必要です

  • D2XX が一覧に出ない場合  

D2XXドライバ未導入、別アプリがデバイスを占有、USBの不調などの可能性があります
Windows10以降は標準でD2XXドライバが当たる様ですが、必要に応じて手動でのインストールが必要です
今のところ、クリーンインストールのWindows11の場合は
デバイス接続後にドライバが当たることを確認済みです


まとめ

今回は、ちょっと高ボーレートで数kB単位でのシリアル通信(PCで受信)をする必要があり
どうしてもC#の.NETでは解決できなかったデータ欠落対策として
VCP→D2XXへ移行して解決したので、解説してみました

Windows10以降を使用している環境であれば、ほぼほぼ問題ないかと思いますが
それ以前のOSをオフラインで使用している場合などは
D2XXドライバを手動で当てる必要があるかもしれません(手持ちないので未トライ)

PC単体で動作するアプリと違って、外部デバイスとの通信をするアプリとなると
こういったPC依存での相性的な問題が出るのでなかなか対応が難しいですね
自分のPCでは再現できないわけですし…

特定のPCでのトラブル対応に向けてデバッグ用のログを残す仕掛けなど
ロバスト性を上げる手段も予め用意しておく必要があるんだなぁとしみじみした次第です。。。

コメント

タイトルとURLをコピーしました