首页 文章

如何使用WinUSB?

提问于
浏览
4

这是我上一个问题的后续行动,Need to write driver for USB peripheral device?

背景

我正在使用STM32微控制器(裸机/无操作系统)设计USB外设 . 该设备偶尔会连接到Windows PC,并在每个方向传输几KB数据 . 将有一个定制的PC应用程序,使用专有协议(即USB有效载荷)控制数据传输 .

PC将永远是主设备(发起者) - 它将发送命令,设备将发出响应,在单个命令或响应中,任意方向上最多可传输几百个字节的数据 . 我想我会想要使用USB批量传输模式 .

选项1 - USB CDC

据我所知,一个选项是我可以使用USB通信设备类(CDC) . 在设备方面,我可以使用ST的示例代码用于USB CDC,例如,来自STM32Cube . 在PC端,设备将显示为虚拟COM端口(VCP) . 然后在软件中我必须定义我的消息格式,命令等 .

  • 我是否正确解释了这一点?

选项2 - WinUSB

我无法理解这是什么,以及如何使用它 .

  • WinUSB与USB设备类的关系是什么?它似乎起到了“通用”USB类的作用,但我找不到任何解释它的文档 .

  • WinUSB是否提供任何内置的消息分隔符?例如WinUsb_WritePipe将缓冲区的内容作为原子单位发送到设备吗?或者我只是像VCP / UART那样获得原始流?

  • 如何在设备上实现WinUSB?有可用的示例代码吗? (最好是STM32 . )

选择

  • 为我的申请选择选项1和2有哪些相关注意事项?

3 回答

  • 4
    • 是的,您正确描述了USC CDC ACM .

    • WinUSB用于支持没有特定设备类的设备 . 如果您的设备实现了人机接口设备,大容量存储设备或通信设备(CDC)等设备类,则只需使用操作系统附带的驱动程序与该设备通信即可 . 如果你想要一个更加可定制和灵活的USB接口,不推荐它 . 有些人编写的驱动程序是WinUSB的替代品:您可以查找libusbK,libusb0.sys和UsbDK作为示例 . WinUSB的优势在于它带有Windows,所以我不会使用其他驱动程序,除非它具有您真正需要的特定功能 .

    • 我相信 WinUSB_WritePipe 将数据作为单个USB传输发送 . 传输在USB规范中有特定的定义 . 您可以判断传输何时结束,因为您将在传输结束时收到一个短数据包 . 短数据包是一个小于 endpoints 最大数据包大小的数据包,它可能是零长度 . 你应该仔细检查这是否真的是真的;尝试发送一个最大数据包大小的倍数的传输,并确保Windows在结束时发送一个零长度数据包 . 顺便说一句,您应该考虑将数据作为控制传输或 endpoints 0上的一系列控制传输发送 . 控制传输具有内置的请求概念和对请求的响应 . 尽管名称,控制转移可用于传输大量数据;它们通常用于USB引导加载程序(参见DFU类) . 使用控制传输而不是非零 endpoints 的另一个优点是,您不必添加额外的 endpoints 描述符并初始化固件中的 endpoints . 您的USB堆栈应该具有处理自定义控制传输的机制,您应该只需编写一些回调函数就可以进行自定义控制传输 .

    • 要在设备端实现WinUSB,您需要编写自己的USB描述符,然后使用低级USB传输命令从 endpoints 读取和写入数据,或者在 endpoints 0上处理特定于供应商的控制传输 . 我不熟悉STM32 USB库,但您应该能够识别实现控制传输和IN和OUT endpoints 的核心组件,而无需对设备类进行任何特定操作 . 这是您学习如何使用的组件 .

    • 默认情况下,您应该使用WinUSB而不是USB CDC ACM . 使用USB CDC ACM的唯一原因是,如果您的设备实际上是串行端口,或者您希望人们更容易在各种编程语言和环境中与您的设备通信 . 大多数编程语言都支持串行端口,但用户仍然必须在生成特定命令格式的代码之上编写代码,因此它实际上并没有给你那么多 . USB CDC ACM的一个问题是,如果设备在打开手柄时断开连接,然后重新连接,各种USB CDC ACM驱动程序可以经常陷入糟糕的状态 . 特别是,Windows 10之前的usbser.sys不能很好地处理这个问题,您通常必须拔出并重新插入设备才能使COM端口再次可用 . USB CDC ACM使用批量 endpoints 进行数据传输,因此没有延迟保证 . 使用WinUSB,您可以选择使用中断 endpoints ,以确保始终为每个USB帧传输一个数据包 .

  • 1

    WinUSB由两部分组成:

    • WinUsb.sys是一个内核模式驱动程序,可以作为过滤器或功能驱动程序安装在USB设备的内核模式设备堆栈中的协议驱动程序之上 .

    • WinUsb.dll是一个公开WinUSB API的用户模式DLL . 当应用程序作为设备的功能驱动程序安装时,应用程序可以使用此API与WinUsb.sys进行通信 . WinUSB.dll暴露的WinUSB API . WinUSB以联合安装程序包WinUSBCoInstaller.dll的形式包含在Windows驱动程序工具包(WDK)中,位于WinDDK \ BuildNumber \ Redist \ Winusb中 .

    要在应用程序中使用WinUSB API:

    • 包括WinUsb.h

    • 将WinUsb.lib添加到链接到您的应用程序的库列表 .

    • Usb100.h包含一些有用宏的声明 .

    • 使用设备接口GUID获取设备路径 . 正确的GUID是您在INF中用于安装WinUsb.sys的GUID .

    • 通过将在INF中定义的设备接口GUID传递给SetupDiGetClassDevs来获取设备信息集的句柄 . 该函数返回一个HDEVINFO句柄 .

    • 调用SetupDiEnumDeviceInterfaces枚举系统的设备接口并获取设备接口的信息 .

    • 调用SetupDiGetDeviceInterfaceDetail以获取设备接口的详细数据 .

    • 调用GetDevicePath函数以获取设备路径 .

    • 将设备路径传递给CreateFile以获取设备的文件句柄 . 使用ReadFile和WriteFile与设备通信 .

    • 将文件句柄传递给WinUsb_Initialize以初始化WinUSB并获取WinUSB句柄 . 当您调用WinUSB API函数时,使用设备的WinUSB句柄来识别设备,而不是设备的文件句柄 .

    对于更高级的解决方案 - 使用功能:

    • WinUsb_QueryDeviceInformation获取设备的速度 .

    • WinUsb_QueryInterfaceSettings获取相应的接口描述符 . WinUSB句柄对应于第一个接口 .

    • WinUsb_QueryPipe获取有关每个 endpoints 的信息 .

    • WinUsb_WritePipe将缓冲区写入设备 - 默认行为:零长度写入在堆栈中向下转发 . 如果传输长度大于最大传输长度,WinUSB会将请求分成较小的最大传输长度请求并按顺序提交 .

    • 更多功能和信息:http://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/winusb_howto.docx

    出于调试目的,您可能需要:winusbtrace_tool https://blogs.msdn.microsoft.com/usbcoreblog/2010/02/05/how-to-generate-and-view-a-winusb-debug-trace-log/; Wireshark https://www.wireshark.org带USBPcap插件 .

    其他示例:http://searchingforbit.blogspot.com/2012/04/winusb-communication-with-stm32-part-1.html . 示例模板随Visual Studio一起提供 .

    您还需要具备编写.inf文件的知识 .

    另一种与USB通信的简便方法 - 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; }
    }
    
  • 2

    我还必须做同样的要求:PC <==> STM
    Microsoft在WinUSB上有很多文档 . 以下是我看到的回答你问题的内容......

    自定义USB设备示例
    https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/CustomUsbDeviceAccess

    为USB设备开发Windows应用程序 - C#和VB
    https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/developing-windows-applications-that-communicate-with-a-usb-device

    用于USB设备的Windows桌面应用程序
    https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/windows-desktop-app-for-a-usb-device

    如何使用WinUSB函数访问USB设备
    https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/using-winusb-api-to-communicate-with-a-usb-device

    开发USB主控制器的Windows驱动程序
    https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/developing-windows-drivers-for-usb-host-controllers

    Windows.Devices.Usb Windows.Devices.Usb命名空间
    https://docs.microsoft.com/en-us/uwp/api/windows.devices.usb

相关问题