主页 > imtoken区块链钱包官网 > 如何通过WinUSB函数访问USB设备

如何通过WinUSB函数访问USB设备

imtoken区块链钱包官网 2023-01-18 11:04:25

如何通过WinUSB函数访问USB设备

谢谢。

本文内容

总结

重要的 API

本主题详细介绍了如何使用与使用 Winusb.sys 作为其功能驱动程序的 USB 设备进行通信。

如果使用 Microsoft Visual Studio 2013,请使用 WinUSB 模板创建骨架应用程序。在这种情况下,请跳过步骤 1 到 3 并继续本主题中的步骤 4。模板会打开设备的文件句柄,获取后续操作所需的WinUSB句柄。此句柄存储在应用程序定义的 device.h DEVICE_DATA 结构中。

有关模板的详细信息,请参阅“基于 WinUSB 模板编写 Windows 桌面应用程序”。

请注意,WinUSB 功能需要 Windows XP 或更高版本。这些函数可用于 C/C++ 应用程序以与 USB 设备进行通信。 Microsoft 不为 WinUSB 提供托管 API。

先决条件

以下内容适用于本演练:

usb区块化

第一步:基于 WinUSB 模板创建骨架应用程序

要访问 USB 设备,首先基于 Windows 驱动程序工具包 (WDK)(带有 Windows 调试工具)和 Microsoft Visual Studio 集成环境中包含的 WinUSB 模板创建主干应用程序。您可以使用此模板作为起点。

有关模板代码以及如何创建、构建、部署和调试主干应用程序的信息,请参阅编写基于 WinUSB 模板的 Windows 桌面应用程序。

模板使用SetupAPI例程枚举设备,打开设备的文件句柄,创建后续任务所需的WinUSB接口句柄。有关获取设备句柄并打开设备的示例代码,请参阅模板代码讨论。

第 2 步:向设备查询 USB 描述符

接下来,查询设备以获取特定于 USB 的信息,例如设备速度、接口描述符、关联端点及其管道。此过程类似于 USB 设备驱动程序使用的过程。但是,应用程序通过调用 WinUsb_GetDescriptor 来完成设备查询。

以下列表显示了您可以调用以获取 USB 特定信息的 WinUSB 函数:

应用程序使用 PipeId 值来标识调用 WinUSB 函数的 WinUsb_ReadPipe() 中用于数据传输的管道(如本主题的“发出 I/O 请求”部分所述),因此此示例存储这三个 PipeId 值都是供以后使用的。

以下示例代码获取WinUSB接口句柄指定的设备的速度。

usb区块化

BOOL GetUSBDeviceSpeed(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pDeviceSpeed)
{
    if (!pDeviceSpeed || hDeviceHandle==INVALID_HANDLE_VALUE)
    {
        return FALSE;
    }
    BOOL bResult = TRUE;
    ULONG length = sizeof(UCHAR);
    bResult = WinUsb_QueryDeviceInformation(hDeviceHandle, DEVICE_SPEED, &length, pDeviceSpeed);
    if(!bResult)
    {
        printf("Error getting device speed: %d.\n", GetLastError());
        goto done;
    }
    if(*pDeviceSpeed == LowSpeed)
    {
        printf("Device speed: %d (Low speed).\n", *pDeviceSpeed);
        goto done;
    }
    if(*pDeviceSpeed == FullSpeed)
    {
        printf("Device speed: %d (Full speed).\n", *pDeviceSpeed);
        goto done;
    }
    if(*pDeviceSpeed == HighSpeed)
    {
        printf("Device speed: %d (High speed).\n", *pDeviceSpeed);
        goto done;
    }
done:
    return bResult;
}

以下示例代码查询由 WinUSB 接口句柄指定的 USB 设备的各种描述符。示例函数检索受支持端点的类型及其管道标识符。该示例存储了所有三个 PipeId 值以供以后使用。

struct PIPE_ID
{
    UCHAR  PipeInId;
    UCHAR  PipeOutId;
};
BOOL QueryDeviceEndpoints (WINUSB_INTERFACE_HANDLE hDeviceHandle, PIPE_ID* pipeid)
{
    if (hDeviceHandle==INVALID_HANDLE_VALUE)
    {
        return FALSE;
    }
    BOOL bResult = TRUE;
    USB_INTERFACE_DESCRIPTOR InterfaceDescriptor;
    ZeroMemory(&InterfaceDescriptor, sizeof(USB_INTERFACE_DESCRIPTOR));
    WINUSB_PIPE_INFORMATION  Pipe;
    ZeroMemory(&Pipe, sizeof(WINUSB_PIPE_INFORMATION));
    
    bResult = WinUsb_QueryInterfaceSettings(hDeviceHandle, 0, &InterfaceDescriptor);
    if (bResult)
    {
        for (int index = 0; index < InterfaceDescriptor.bNumEndpoints; index++)
        {
            bResult = WinUsb_QueryPipe(hDeviceHandle, 0, index, &Pipe);
            if (bResult)
            {
                if (Pipe.PipeType == UsbdPipeTypeControl)
                {
                    printf("Endpoint index: %d Pipe type: Control Pipe ID: %d.\n", index, Pipe.PipeType, Pipe.PipeId);
                }
                if (Pipe.PipeType == UsbdPipeTypeIsochronous)
                {
                    printf("Endpoint index: %d Pipe type: Isochronous Pipe ID: %d.\n", index, Pipe.PipeType, Pipe.PipeId);
                }
                if (Pipe.PipeType == UsbdPipeTypeBulk)
                {
                    if (USB_ENDPOINT_DIRECTION_IN(Pipe.PipeId))
                    {
                        printf("Endpoint index: %d Pipe type: Bulk Pipe ID: %c.\n", index, Pipe.PipeType, Pipe.PipeId);
                        pipeid->PipeInId = Pipe.PipeId;
                    }
                    if (USB_ENDPOINT_DIRECTION_OUT(Pipe.PipeId))
                    {
                        printf("Endpoint index: %d Pipe type: Bulk Pipe ID: %c.\n", index, Pipe.PipeType, Pipe.PipeId);
                        pipeid->PipeOutId = Pipe.PipeId;
                    }
                }
                if (Pipe.PipeType == UsbdPipeTypeInterrupt)
                {
                    printf("Endpoint index: %d Pipe type: Interrupt Pipe ID: %d.\n", index, Pipe.PipeType, Pipe.PipeId);
                }
            }
            else
            {
                continue;
            }
        }
    }
done:
    return bResult;
}

第 3 步:将控制传输发送到默认端点

接下来,通过向默认端点发出控制请求来与设备通信。

除了与接口关联的端点外,所有 USB 设备都有一个默认端点。默认端点的主要目的是为主机提供可用于配置设备的信息。但是,设备也可以将默认端点用于特定于设备的目的。例如,OSR USB FX2 设备使用默认端点来控制灯条和 7 段数字显示器。

控制命令由一个 8 字节的设置数据包组成,其中包括一个指定特定请求的请求代码和一个可选的数据缓冲区。请求代码和缓冲区格式由供应商定义。在此示例中,应用程序向设备发送数据以控制灯条。用于设置灯条的代码为0xD8usb区块化,为方便起见定义为SET_BARGRAPH_DISPLAY。对于这个请求,设备需要一个 1 字节的数据缓冲区,通过设置适当的位来指定应该点亮哪些元素。

应用程序可以通过用户界面 (UI) 进行设置,例如,通过提供一组 8 个复选框控件来指定应该点亮灯条的哪些元素。指定的元素对应于缓冲区中的相应位。为避免编写 UI 代码,本节中的示例代码将设置位以打开备用灯。

使用以下步骤发出控制请求。

usb区块化

分配一个 1 字节的数据缓冲区并将数据加载到缓冲区中,通过设置适当的位来指定应点亮的元素。

在调用者分配的结构中构造一个 WINUSB_SETUP_PACKET 数据包。初始化成员代表请求类型和数据如下:

调用 WinUsb_ControlTransfer 通过传递设备的 WinUSB 接口句柄、设置数据包和数据缓冲区来将请求传输到默认端点。该函数在 LengthTransferred 参数中接收传输到设备的字节数。

以下代码示例向指定的 USB 设备发送控制请求,以控制灯条上的灯。

BOOL SendDatatoDefaultEndpoint(WINUSB_INTERFACE_HANDLE hDeviceHandle)
{
    if (hDeviceHandle==INVALID_HANDLE_VALUE)
    {
        return FALSE;
    }
    BOOL bResult = TRUE;
    
    UCHAR bars = 0;
    WINUSB_SETUP_PACKET SetupPacket;
    ZeroMemory(&SetupPacket, sizeof(WINUSB_SETUP_PACKET));
    ULONG cbSent = 0;
    //Set bits to light alternate bars
    for (short i = 0; i < 7; i+= 2)
    {
        bars += 1 << i;
    }
    //Create the setup packet
    SetupPacket.RequestType = 0;
    SetupPacket.Request = 0xD8;
    SetupPacket.Value = 0;
    SetupPacket.Index = 0; 
    SetupPacket.Length = sizeof(UCHAR);
    bResult = WinUsb_ControlTransfer(hDeviceHandle, SetupPacket, &bars, sizeof(UCHAR), &cbSent, 0);
    if(!bResult)
    {
        goto done;
    }
    printf("Data sent: %d \nActual data transferred: %d.\n", sizeof(bars), cbSent);
done:
    return bResult;
}

第 4 步:发出 I/O 请求

接下来,将数据发送到设备的批量输入和批量输出端点,它们可分别用于读取和写入请求。在 OSR USB FX2 设备上,两个端点都配置为环回功能,因此设备将数据从批量输入端点移动到批量输出端点。它不会更改数据的值或添加任何新数据。对于环回配置,读取请求将读取最近写入请求发送的数据。 WinUSB 提供以下函数用于发送写入和读取请求:

发送写请求

分配一个缓冲区并填充要写入设备的数据。如果应用程序没有将缓冲区大小设置为管道的策略 RAW_IO,则缓冲区大小没有限制。如有必要,WinUSB 会将缓冲区划分为适当大小的块。如果是 RAW_IO,缓冲区的大小受 WinUSB 支持的最大传输大小限制。调用 WinUsb_WritePipe 将缓冲区写入设备。传递设备的 WinUSB 接口句柄、管道标识符(如本主题部分所述)和批量传出管道的缓冲区。该函数在 bytesWritten 参数中返回实际写入设备的字节数。 Overlapped 参数设置为 NULL 以请求同步操作。要执行异步写入请求usb区块化,请将 Overlapped 设置为指向 OVERLAPPED 结构的指针。

usb区块化

包含零长度数据的写入请求沿 USB 堆栈向下转发。如果传输长度大于最大传输长度,WinUSB将请求分成最大传输长度的较小请求,依次提交。以下代码示例分配一个字符串并将其发送到设备的批量传出端点。

BOOL WriteToBulkEndpoint(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pID, ULONG* pcbWritten)
{
    if (hDeviceHandle==INVALID_HANDLE_VALUE || !pID || !pcbWritten)
    {
        return FALSE;
    }
    BOOL bResult = TRUE;
    UCHAR szBuffer[] = "Hello World";
    ULONG cbSize = strlen(szBuffer);
    ULONG cbSent = 0;
    bResult = WinUsb_WritePipe(hDeviceHandle, *pID, szBuffer, cbSize, &cbSent, 0);
    if(!bResult)
    {
        goto done;
    }
    printf("Wrote to pipe %d: %s \nActual data transferred: %d.\n", *pID, szBuffer, cbSent);
    *pcbWritten = cbSent;
done:
    return bResult;
}

发送读取请求

长度为零的读取请求立即完成(导致成功)并且不会向下发送到堆栈。如果传输长度大于最大传输长度,WinUSB将请求分成最大传输长度的较小请求,依次提交。如果传输长度不是端点的 MaxPacketSize 的倍数,WinUSB 会将传输的大小增加到 MaxPacketSize 的下一个倍数。如果设备返回的数据多于请求的数据,WinUSB 将保存多余的数据。如果上一个读取请求中的数据仍然存在,WinUSB 会将其复制到下一个读取请求的开头并完成请求(如果需要)。以下代码示例从设备的批量输入端点读取数据。

BOOL ReadFromBulkEndpoint(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pID, ULONG cbSize)
{
    if (hDeviceHandle==INVALID_HANDLE_VALUE)
    {
        return FALSE;
    }
    BOOL bResult = TRUE;
    UCHAR* szBuffer = (UCHAR*)LocalAlloc(LPTR, sizeof(UCHAR)*cbSize);
    
    ULONG cbRead = 0;
    bResult = WinUsb_ReadPipe(hDeviceHandle, *pID, szBuffer, cbSize, &cbRead, 0);
    if(!bResult)
    {
        goto done;
    }
    printf("Read from pipe %d: %s \nActual data read: %d.\n", *pID, szBuffer, cbRead);
done:
    LocalFree(szBuffer);
    return bResult;
}

第 5 步:释放设备句柄

在对设备进行所有必需的调用后,释放设备的文件句柄和 WinUSB 接口句柄。为此,请调用以下函数:

第 6 步:实现 Main

以下代码示例显示了控制台应用程序的主要功能。

usb区块化

获取设备句柄和打开设备的示例代码(GetDeviceHandle 和 GetWinUSBHandle),请参阅。

int _tmain(int argc, _TCHAR* argv[])
{
    GUID guidDeviceInterface = OSR_DEVICE_INTERFACE; //in the INF file
    BOOL bResult = TRUE;
    PIPE_ID PipeID;
    HANDLE hDeviceHandle = INVALID_HANDLE_VALUE;
    WINUSB_INTERFACE_HANDLE hWinUSBHandle = INVALID_HANDLE_VALUE;
    
    UCHAR DeviceSpeed;
    ULONG cbSize = 0;
    bResult = GetDeviceHandle(guidDeviceInterface, &hDeviceHandle);
    if(!bResult)
    {
        goto done;
    }
    bResult = GetWinUSBHandle(hDeviceHandle, &hWinUSBHandle);
    if(!bResult)
    {
        goto done;
    }
    bResult = GetUSBDeviceSpeed(hWinUSBHandle, &DeviceSpeed);
    if(!bResult)
    {
        goto done;
    }
    bResult = QueryDeviceEndpoints(hWinUSBHandle, &PipeID);
    if(!bResult)
    {
        goto done;
    }
    bResult = SendDatatoDefaultEndpoint(hWinUSBHandle);
    if(!bResult)
    {
        goto done;
    }
    bResult = WriteToBulkEndpoint(hWinUSBHandle, &PipeID.PipeOutId, &cbSize);
    if(!bResult)
    {
        goto done;
    }
    bResult = ReadFromBulkEndpoint(hWinUSBHandle, &PipeID.PipeInId, cbSize);
    if(!bResult)
    {
        goto done;
    }
    system("PAUSE");
done:
    CloseHandle(hDeviceHandle);
    WinUsb_Free(hWinUSBHandle);
    return 0;
}

接下来的步骤

如果设备支持永远在线的等效端点,则可以使用发送传输。此功能仅在 Windows 8.1 上受支持。

有关详细信息,请参阅从 WinUSB 桌面应用程序发送 USB 同步传输。

相关主题

WinUSB

WinUSB 架构和模块

WinUSB (Winusb.sys) 安装

用于修改管道策略的 WinUSB 函数

WinUSB 电源管理

基于 WinUSB 模板编写一个 Windows 桌面应用程序