8

这是我之前的问题的后续,需要为 USB 外围设备编写驱动程序吗?

背景

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

PC 将永远是主机(发起者)——它会发送命令,设备会发出响应,在单个命令或响应中,每个方向都有多达几百字节的数据。我想我会想使用 USB 批量传输模式。

选项 1 - USB CDC

据我了解,一种选择是我可以使用 USB 通信设备类 (CDC)。在设备方面,我可以将 ST 的示例代码用于 USB CDC,例如来自STM32Cube。在 PC 端,设备将显示为虚拟 COM 端口 (VCP)。然后在软件中,我基本上会有一个原始的双向流,在它之上我必须定义我的消息格式、命令等。

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

选项 2 - WinUSB

我很难弄清楚这是什么以及如何使用它。

  1. WinUSB 与 USB 设备类有什么关系?它似乎起到“通用”USB 类的作用,但我找不到任何说明它的文档。

  2. WinUSB 是否提供任何内置的消息分隔符?例如, WinUsb_WritePipe是否将缓冲区的内容作为原子单元向下发送到设备?或者我只是得到像 VCP/UART 这样的原始流?

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

选择

  1. 在为我的应用程序选择选项 1 和 2 时,有哪些相关考虑因素?
4

4 回答 4

9

WinUSB 由两部分组成:

  • WinUsb.sys 是一个内核模式驱动程序,可以作为过滤器或功能驱动程序安装在 USB 设备的内核模式设备堆栈中的协议驱动程序之上。
  • WinUsb.dll 是公开 WinUSB API 的用户模式 ​​DLL。当作为设备的功能驱动程序安装时,应用程序可以使用此 API 与 WinUsb.sys 进行通信。WinUSB API——由 WinUSB.dll 公开。WinUSB 以共同安装程序包 WinUSBCoInstaller.dll 的形式包含在 Windows 驱动程序工具包 (WDK) 中,位于 WinDDK\BuildNumber\Redist\Winusb 中。

在应用程序中使用 WinUSB API:

  • 包括 WinUsb.h
  • 将 WinUsb.lib 添加到链接到您的应用程序的库列表中。
  • Usb100.h 包含一些有用宏的声明。
  • 使用设备接口 GUID 获取设备路径。正确的 GUID 是您在用于安装 WinUsb.sys 的 INF 中指定的 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 获取有关每个端点的信息。
  • 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/;带有 USBPcap 插件的Wireshark https://www.wireshark.org 。

其他示例: 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; }
}
于 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 上的一系列控制传输发送。控制传输具有请求和对请求的响应的内置概念。尽管名称如此,控制传输可用于传输大量数据。它们通常用于 USB 引导加载程序(请参阅 DFU 类)。使用控制传输而不是非零端点的另一个优点是您不必添加额外的端点描述符并在固件中初始化端点。您的 USB 堆栈应该具有​​处理自定义控制传输的机制,并且您应该能够通过编写一些回调函数来进行自定义控制传输。
  4. 要在设备端实现 WinUSB,您需要编写自己的 USB 描述符,然后使用低级 USB 传输命令从端点读取和写入数据,或在端点 0 上处理供应商特定的控制传输。我不熟悉 STM32 USB 库,但您应该能够识别它实现控制传输和 IN 和 OUT 端点的核心组件,而无需执行任何特定于设备类的操作。这是您需要学习如何使用的组件。
  5. 默认情况下,您应该使用 WinUSB 而不是 USB CDC ACM。使用 USB CDC ACM 的唯一原因是您的设备实际上是一个串行端口,或者您想让人们更容易地在各种编程语言和环境中与您的设备交谈。大多数编程语言都支持串行端口,但用户仍然必须在生成特定命令格式的代码之上编写代码,因此它实际上并没有给你那么多。USB CDC ACM 的一个问题是,如果设备在您打开手柄时断开连接,然后重新连接,则各种 USB CDC ACM 驱动程序通常会进入错误状态。尤其是 Windows 10 之前的 usbser.sys 不能很好地处理这一点,您通常必须拔下并重新插入设备才能使 COM 端口再次可用。USB CDC ACM 使用批量端点进行数据传输,因此没有延迟保证。使用 WinUSB,您可以选择使用中断端点,这样您就可以保证始终为每个 USB 帧传输一个数据包。
于 2016-08-12T04:11:50.580 回答
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-c ​​ommunicate-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

于 2018-05-24T13:45:16.527 回答
0

我解决了这个请求,所以我了解您的意图是让您的固件可以在插入时由 Windows 自动枚举为 WINUSB(winusb 通用驱动程序)设备。

我相信如果您有演示和代码会很清楚,所以我为您制作了一个:)

我的KEIL 项目使用STM32F4 探索板,将WINUSB用作USB CDC 设备

您可以从我的 GitHub查看更多信息并获取源代码。

部署 WINUSB 需要手动修改 KEIL 项目中的 USB 设备和中间件层源代码。您可以关注usbd_ctlreq.c、usbd_cdc.c 和 usbd_desc.c来与 CubeMX 生成的来源进行比较。

于 2020-08-10T10:22:19.417 回答