8

これは、以前の質問「USB 周辺機器用のドライバーを作成する必要がありますか?」の続きです。

バックグラウンド

STM32 マイクロコントローラー (ベアメタル / OS なし) を使用して USB 周辺機器の設計に取り組んでいます。デバイスは Windows PC に接続されることがあり、各方向に数 KB のデータを転送します。独自のプロトコル (つまり、USB ペイロード用) を使用して、データ転送を制御するカスタム PC アプリケーションがあります。

PC は常にマスター (イニシエーター) になります。コマンドを送信し、デバイスが応答を発行します。1 つのコマンドまたは応答で最大数百バイトのデータが双方向に送信されます。USB Bulk Transfer モードを使用したいと思います。

オプション 1 - USB CDC

私が理解していることから、1 つのオプションは、USB 通信デバイス クラス (CDC) を使用することです。デバイス側では、 STM32Cubeなどの USB CDC に ST のサンプル コードを使用できます。PC 側では、デバイスは仮想 COM ポート (VCP) として表示されます。次に、ソフトウェアでは、基本的に生の双方向ストリームを用意し、その上でメッセージ形式やコマンドなどを定義する必要があります.

  1. 私はこれを正しく説明しましたか?

オプション 2 - WinUSB

これが何であるか、そしてそれをどのように扱うかを正確に理解するのに苦労しています。

  1. WinUSB と USB デバイス クラスの関係はどのようなものですか? 「一般的な」USB クラスとして機能しているようですが、そのように説明しているドキュメントは見つかりません。

  2. WinUSB には組み込みのメッセージ区切り文字がありますか? たとえば、 WinUsb_WritePipeはバッファの内容をアトミック ユニットとしてデバイスに送信しますか? それとも、VCP/UART のような raw ストリームを取得するだけですか?

  3. デバイスに WinUSB を実装するにはどうすればよいですか? サンプルコードはありますか? (できれば STM32 用です。)

選ぶ

  1. アプリケーションのオプション 1 と 2 を選択する際に考慮すべき事項は何ですか?
4

4 に答える 4

9

WinUSB は 2 つの部分で構成されています。

  • WinUsb.sys は、USB デバイスのカーネル モード デバイス スタック内のプロトコル ドライバーの上に、フィルター ドライバーまたは関数ドライバーとしてインストールできるカーネル モード ドライバーです。
  • WinUsb.dll は、WinUSB API を公開するユーザー モード DLL です。アプリケーションは、デバイスの関数ドライバーとしてインストールされている場合、この API を使用して WinUsb.sys と通信できます。WinUSB API — WinUSB.dll によって公開されます。WinUSB は、WinDDK\BuildNumber\Redist\Winusb にある共同インストーラー パッケージ WinUSBCoInstaller.dll の形式で Windows Driver Kit (WDK) に含まれています。

アプリケーションで WinUSB API を使用するには:

  • WinUsb.h を含める
  • アプリケーションにリンクされているライブラリのリストに WinUsb.lib を追加します。
  • Usb100.h には、いくつかの便利なマクロの宣言が含まれています。
  • デバイス インターフェイス GUID を使用して、デバイス パスを取得します。正しい GUID は、WinUsb.sys のインストールに使用された INF で指定したものです。
  • INF で定義したデバイス インターフェイス GUID を SetupDiGetClassDevs に渡すことによって、デバイス情報セットへのハンドルを取得します。この関数は、HDEVINFO ハンドルを返します。
  • SetupDiEnumDeviceInterfaces を呼び出して、システムのデバイス インターフェイスを列挙し、デバイス インターフェイスに関する情報を取得します。
  • SetupDiGetDeviceInterfaceDetail を呼び出して、デバイス インターフェイスの詳細データを取得します。
  • GetDevicePath 関数を呼び出して、デバイス パスを取得します。
  • デバイス パスを CreateFile に渡して、デバイスのファイル ハンドルを取得します。ReadFile と WriteFile を使用して device と通信します。
  • ファイル ハンドルを WinUsb_Initialize に渡して、WinUSB を初期化し、WinUSB ハンドルを取得します。WinUSB API 関数を呼び出すときは、デバイスのファイル ハンドルではなく、デバイスの WinUSB ハンドルを使用してデバイスを識別します。

より高度なソリューションについては、関数を使用します。

  • デバイスの速度を取得するための WinUsb_QueryDeviceInformation。
  • WinUsb_QueryInterfaceSettings を使用して、対応するインターフェイス記述子を取得します。WinUSB ハンドルは、最初のインターフェイスに対応します。
  • WinUsb_QueryPipe は、各エンドポイントに関する情報を取得します。
  • WinUsb_WritePipe はバッファをデバイスに書き込みます - デフォルトの動作: 長さゼロの書き込みはスタックに転送されます。転送長が最大転送長より長い場合、WinUSB は要求を最大転送長のより小さい要求に分割し、それらを連続して送信します。
  • その他の機能と情報: http://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/winusb_howto.docx

デバッグ目的で必要になる可能性があるのは、次のとおり です。Wireshark https://www.wireshark.orgと USBPcap プラグイン。

その他の例: http://searchingforbit.blogspot.com/2012/04/winusb-communication-with-stm32-part-1.html . サンプル テンプレートは Visual Studio に付属しています。

また、.inf ファイルの作成に関する知識も必要です。

USB と通信するもう 1 つの簡単な方法 - libusb-win32 https://sourceforge.net/projects/libusb-win32/

私の単純なコンソール アプリケーションの例では、小さな (キープアライブ用の) データのチャンクをデバイスに送信します。

#include "stdafx.h"
#include <SetupAPI.h>
#include <Hidsdi.h> 
#include <devguid.h> 
#include <winusb.h>
#include <usb.h>
#pragma comment(lib, "hid.lib")
#pragma comment(lib, "setupapi.lib")
#pragma comment(lib, "winusb.lib")
#include <iUString.h> 


iString<char> DevicePath;
bool                    WinusbHandle_Open=false;
bool                    DeviceHandle_Open = false;
WINUSB_INTERFACE_HANDLE WinusbHandle;
HANDLE                  DeviceHandle;
UCHAR usb_out_buffer[64];
DEFINE_GUID(GUID_DEVCLASS_WINUSB, 0x88bae032L, 0x5a81, 0x49f0, 0xbc, 0x3d, 0xa4, 0xff, 0x13, 0x82, 0x16, 0xd6);
DEFINE_GUID(GUID_DEVCLASS_STL, 0xf177724dL, 0x74d3, 0x430e, 0x86, 0xb5, 0xf0, 0x36, 0x89, 0x10, 0xeb, 0x23);
GUID winusb_guid;
GUID stl_guid;

bool connectusb();
void  disconnectusb();




int main()
{
    DWORD n;
    DWORD   numEvents;
    HANDLE rHnd;    

WinusbHandle_Open = false;
DeviceHandle_Open = false;
winusb_guid = GUID_DEVCLASS_WINUSB;
stl_guid = GUID_DEVCLASS_STL;
usb_out_buffer[0] = 0;
usb_out_buffer[1] = 1;
usb_out_buffer[2] = 2;
usb_out_buffer[3] = 3;

ULONG bytesWritten;
ULONG timeout;
timeout = 100;
rHnd = GetStdHandle(STD_INPUT_HANDLE);

WinUsb_SetPipePolicy(WinusbHandle, 0x01, PIPE_TRANSFER_TIMEOUT, sizeof(ULONG), &timeout);

timeout = TRUE;
WinUsb_SetPipePolicy(WinusbHandle, 0x01, AUTO_CLEAR_STALL, sizeof(ULONG), &timeout);


timeout = TRUE;
WinUsb_SetPipePolicy(WinusbHandle, 0x01, RAW_IO, sizeof(ULONG), &timeout);//Bypasses queuing and error handling to boost performance for multiple read requests.


while (true)
{
if ((!WinusbHandle_Open) || (!WinusbHandle_Open)) { if (!connectusb())Sleep(2000); }
if ((!WinusbHandle_Open) || (!WinusbHandle_Open))continue;

bytesWritten = 0;
if (!WinUsb_WritePipe(WinusbHandle, 0x01, &usb_out_buffer[0], 2, &bytesWritten, NULL))
{
    n = GetLastError();
disconnectusb();
}
Sleep(2000);
}
disconnectusb();
return 0;
}




bool connectusb()
{
    BOOL                             bResult = FALSE;
    HDEVINFO                         deviceInfo;
    SP_DEVICE_INTERFACE_DATA         interfaceData;
    PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = NULL;
    DWORD n;
    SP_DEVINFO_DATA devinfo;
    BYTE devdetailbuffer[4096];
    bool found;

    deviceInfo = SetupDiGetClassDevs(&stl_guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    if (deviceInfo == INVALID_HANDLE_VALUE) { return false; }

    found = false;
    for (n = 0;; n++)
    {

        interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

        if (!SetupDiEnumDeviceInterfaces(deviceInfo, NULL, &stl_guid, n, &interfaceData))
        {
            n = GetLastError();
            break;
        }




        detailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)devdetailbuffer;
        detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
        devinfo.cbSize = sizeof(devinfo);
        if (!SetupDiGetDeviceInterfaceDetail(deviceInfo, &interfaceData, detailData, sizeof(devdetailbuffer), NULL, &devinfo)) { printf("SetupDiGetDeviceInterfaceDetail: %u\n", GetLastError()); break; }
        if (IsEqualGUID(devinfo.ClassGuid, winusb_guid))
        {
            if ((-1 != iStrPos(detailData->DevicePath, "VID_0483")) || (-1 != iStrPos(detailData->DevicePath, "vid_0483")))
            {
                if ((-1 != iStrPos(detailData->DevicePath, "PID_576B")) || (-1 != iStrPos(detailData->DevicePath, "pid_576b")))
                {

                    DevicePath = detailData->DevicePath;
                    found = true;
                    break;
                }
            }
        }
    }



SetupDiDestroyDeviceInfoList(deviceInfo);
if (!found)return false;


DeviceHandle = CreateFile(DevicePath.Buffer() ,
    GENERIC_WRITE | GENERIC_READ,
    FILE_SHARE_WRITE | FILE_SHARE_READ,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
    NULL);

if (INVALID_HANDLE_VALUE == DeviceHandle) {
    n = GetLastError();
}

if (INVALID_HANDLE_VALUE == DeviceHandle) return false;
DeviceHandle_Open = true;



if (!WinUsb_Initialize(DeviceHandle, &WinusbHandle))
 {
     n = GetLastError();
     CloseHandle(DeviceHandle); DeviceHandle_Open = false;
     return false;
 }



WinusbHandle_Open = true;
return true;
}

void  disconnectusb()
{
    if (WinusbHandle_Open) { WinUsb_Free(WinusbHandle); WinusbHandle_Open = false; }
    if (DeviceHandle_Open) { CloseHandle(DeviceHandle); DeviceHandle_Open = false; }
}
于 2018-02-18T00:45:07.797 に答える
8
  1. はい、USC CDC ACM を正しく説明しました。
  2. WinUSB は、特定のデバイス クラスを持たないデバイスをサポートするために存在します。デバイスがヒューマン インターフェイス デバイス、マス ストレージ デバイス、通信デバイス (CDC) などのデバイス クラスを実装している場合は、オペレーティング システムに付属のドライバーを使用するだけで、そのデバイスと通信できます。これらのクラスのいずれにも準拠しない、よりカスタマイズ可能で柔軟な USB インターフェイスが必要な場合は、WinUSB を使用してデバイスと通信できます。必要に応じて独自のドライバーを作成することもできますが、それは大変な作業になるため、お勧めしません。WinUSB の代替となるドライバーを作成した人もいます。たとえば、libusbK、libusb0.sys、および UsbDK を参照できます。WinUSB には Windows に付属しているという利点があるため、本当に必要な特定の機能がない限り、他のドライバーは使用しません。
  3. WinUSB_WritePipe単一の USB転送としてデータを送信すると思います。転送_USB 仕様に特定の定義があります。転送の最後に短いパケットを取得するため、転送がいつ終了するかがわかります。ショート パケットは、エンドポイントの最大パケット サイズよりも小さいパケットであり、長さがゼロになる可能性があります。ただし、それが実際に正しいかどうかを再確認する必要があります。最大パケット サイズの倍数である転送を送信してみて、Windows がその最後に長さ 0 のパケットを送信することを確認してください。ちなみに、エンドポイント 0 でのコントロール転送または一連のコントロール転送としてデータを送信することを検討する必要があります。コントロール転送には、要求と要求に対する応答の概念が組み込まれています。その名前にもかかわらず、コントロール転送は大量のデータを転送するために使用できます。これらは USB ブートローダで一般的に使用されます (DFU クラスを参照してください)。ゼロ以外のエンドポイントの代わりにコントロール転送を使用するもう 1 つの利点は、追加のエンドポイント記述子を追加して、ファームウェアでエンドポイントを初期化する必要がないことです。USB スタックにはカスタム コントロール転送を処理するための機構が必要であり、いくつかのコールバック関数を記述するだけでカスタム コントロール転送を実行できる必要があります。
  4. デバイス側で WinUSB を実装するには、独自の USB 記述子を記述してから、低レベルの USB 転送コマンドを使用してエンドポイントからデータを読み書きするか、エンドポイント ゼロでベンダー固有の制御転送を処理する必要があります。私は STM32 USB ライブラリに精通していませんが、デバイス クラスに固有のことをしなくても、制御転送と IN および OUT エンドポイントを実装するコア コンポーネントを特定できるはずです。これは、使用方法を学ぶ必要があるコンポーネントです。
  5. デフォルトでは、USB CDC ACM の代わりに WinUSB を使用する必要があります。USB CDC ACM を使用する唯一の理由は、デバイスが実際にシリアル ポートである場合、またはさまざまなプログラミング言語や環境で人々がデバイスと簡単に通信できるようにする場合です。ほとんどのプログラミング言語はシリアル ポートをサポートしていますが、ユーザーは特定のコマンド フォーマットを生成するコードをその上に記述する必要があるため、実際にはそれほど多くは得られません。USB CDC ACM の問題の 1 つは、デバイスのハンドルが開いているときにデバイスが切断され、その後再接続されると、さまざまな USB CDC ACM ドライバーがしばしば悪い状態になる可能性があることです。特に、Windows 10 より前の usbser.sys はこれをうまく処理できず、通常、デバイスを取り外してから再度接続して、COM ポートを再び使用できるようにする必要があります。USB CDC ACM はデータ転送にバルク エンドポイントを使用するため、遅延の保証はありません。WinUSB では、割り込みエンドポイントを使用するオプションがあるため、USB フレームごとに常に 1 つのパケットが転送されることが保証されます。
于 2016-08-12T04:11:50.580 に答える