具有虚拟 COM 端口的 USB 串行设备 - 如果将 CreateFile() 与 USB 路径一起使用,则 ReadFile() 读取零字节

USB Serial Device with Virtual COM port - ReadFile() reads zero bytes if use CreateFile() with USB path

我有一个销售点应用程序,它使用串行通信端口 (RS-232) 与称重产品的秤进行通信。我现在正致力于能够直接支持 USB 设备,而不是使用虚拟串行通信端口,因为它们有移动的烦人倾向。

我们发现,虽然 Windows 7 似乎会自动创建虚拟串行通信端口,但 Windows 的其他版本(例如 POS Ready 7)可能不会。我们怀疑这是由于 POS Ready 7 中缺少带有 Windows 7 的特定 .inf 文件。有人可以确认吗?

我有一个间歇性工作的 USB 示例应用程序。我在使用 ReadFile() Windows API 函数进行 USB 级通信时遇到问题。我正在使用 CreateFile() 指定 USB 设备路径来获取 I/O 句柄,然后使用 WriteFile()ReadFile() 与秤进行通信。 ReadFile() 在某些情况下未提供数据。

背景信息

我正在使用的特定秤,Brecknell 67xx 台秤,直接与销售点应用程序一起使用开箱即用的虚拟串行通信端口。我使用 USB 数据线将体重秤连接到我的 Windows 7 PC,然后 Windows 自动安装了驱动程序以创建一个虚拟串行端口,在我的例子中是 COM4。然后,我将应用程序配置为通过 COM4 与秤对话,一切正常。

使用天平的协议是发送一个两字节的命令,"W\r"(大写字母 W 后跟一个回车 return 字符)到天平,然后读取一个 16 字节的响应其中包含当前重量以及有关体重秤机制的状态信息,例如 In Motion。

我正在学习的示例 USB 应用程序将成功提供权重。然后它将停止正常工作 ReadFile() returning 零字节读取的行为。一旦它停止工作,它将继续无法从 ReadFile() 提供数据,即使我拔下并重新插入 USB 数据线或重新启动我的电脑也是如此。

学习应用程序的先前版本挂在 ReadFile() 上,当使用 Visual Studio 完成“全部中断”时,将显示暂停,然后显示一条指示死锁的消息。但是,自从我开始在 ReadTotalTimeoutConstant 中使用具有 5000 毫秒超时值的 SetCommTimeouts() 后,我看到在读取零字节的 ReadFile() return 之前有一个一致的 5 秒暂停。

奇怪的是,如果我随后使用打开虚拟串行通信端口 COM4 的应用程序,该应用程序工作正常并且秤会报告物品的重量。

然后我可以 return 使用直接 USB 而不是虚拟串行通信端口的示例应用程序,它可以很好地报告权重。

但是,如果我随后拔掉连接体重秤和 PC 的 USB 电缆,这也会关闭体重秤的电源,然后重新插入 USB 电缆,示例应用程序将不再正常运行,我再次看到超时暂停.

然后我尝试使用依赖于使用虚拟串行端口 COM4 的串行通信端口的原始销售点应用程序,并且该应用程序可以很好地称重物品。

然后当我重试示例应用程序时,它还会报告项目重量。

我的问题。

如果 USB 设备在插入时创建虚拟串行通信端口,那么是否需要通过在 CreateFile() 调用中指定通信端口(在我的例子中为 COM4)来仅使用虚拟串行端口?

如果设备导致 Windows 生成虚拟通信端口,如何通过使用 CreateFile() 和 USB 设备路径进行直接 USB 串行通信?

是否有某种方法可以指定 Windows 的任何版本在插入设备时自动为设备创建虚拟串行通信端口?

示例 USB 应用程序的源代码

我使用 Visual Studio 2005 的示例 USB Windows 控制台应用程序的源代码如下,主要位于底部,其中大部分是 class 用于查找特定的 USB 设备,然后允许 ReadFile()WriteFile():

// usb_test_cons.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <windows.h>
#include <setupapi.h>
#include <initguid.h>

#include <stdio.h>

// This is the GUID for the USB device class.
// It is defined in the include file Usbiodef.h of the Microsoft Windows Driver Kit.
// See also https://msdn.microsoft.com/en-us/library/windows/hardware/ff545972(v=vs.85).aspx which
// provides basic documentation on this GUID.
DEFINE_GUID(GUID_DEVINTERFACE_USB_DEVICE, 0xA5DCBF10L, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED);
// (A5DCBF10-6530-11D2-901F-00C04FB951ED)

// Following are standard defines to be used with all of the
// devices that are use through the UIE interface.
#define UIE_DEVICE_ERROR             (-11)        /* error when accessing the device */
#define UIE_DEVICE_NOT_PROVIDE       (-12)        /* device is not provided */
#define UIE_DEVICE_ERROR_RANGE       (-13)        /* range error         */
#define UIE_DEVICE_ERROR_COM         (-14)        /* communication error */
#define UIE_DEVICE_TIMEOUT           (-15)        /* communication error */
#define UIE_DEVICE_SPECIFIC          (-20)        /* device specific errors start here */


#define UIE_SCALE_ETX           0x03        /* ETX character         */
#define UIE_SCALE_IN_MOTION     0x01        /* scale in motion       */
#define UIE_SCALE_ZERO          0x02        /* zero weight           */
#define UIE_SCALE_UNDER         0x01        /* under capacity        */
#define UIE_SCALE_OVER          0x02        /* over capacity         */

#define UIE_SCALE_ERROR             UIE_DEVICE_ERROR          /* error          */
#define UIE_SCALE_NOT_PROVIDE       UIE_DEVICE_NOT_PROVIDE    /* not provide    */
#define UIE_SCALE_TIMEOUT           UIE_DEVICE_TIMEOUT        /* time out when reading from scale  */
#define UIE_SCALE_MOTION            (UIE_DEVICE_SPECIFIC-1)   /* motion         */
#define UIE_SCALE_UNDER_CAPACITY    (UIE_DEVICE_SPECIFIC-2)   /* under capacity */
#define UIE_SCALE_OVER_CAPACITY     (UIE_DEVICE_SPECIFIC-3)   /* over capacity  */
#define UIE_SCALE_DATAFORMAT        (UIE_DEVICE_SPECIFIC-4)   /* Data read from scale incorrect format in UieScaleAnalysis()  */
#define UIE_SCALE_DATAUNITS         (UIE_DEVICE_SPECIFIC-5)   /* Units read from scale incorrect in UieScaleAnalysis()  */


static SHORT UieScaleStatus(char *puchBuffer, DWORD sLength)
{
    UCHAR   uchByte;

    switch (sLength) {
        case 16:
            // The scale message is a weight message with a status section.
            // Move the buffer pointer to where the status section should begin.
            // A status only message has the same format as the status section of a weight message.
            puchBuffer += 10;
        case 6:
            // The scale message may be a status only message if there is a problem with the scale.
            // A status only message is 6 characters with the letter S as the second character.
            if (*(puchBuffer + 0) != '\n' ||
                *(puchBuffer + 1) != 'S'  ||
                *(puchBuffer + 4) != '\r' ||
                *(puchBuffer + 5) != UIE_SCALE_ETX) {
                return (UIE_SCALE_DATAFORMAT);               /* exit ... */
            }
            break;
        default:
            return (UIE_SCALE_DATAFORMAT);               /* exit ... */
            break;
    }

    /* --- check status of low byte --- */
    uchByte = *(puchBuffer + 3) - (UCHAR)0x30;

    if (uchByte & UIE_SCALE_UNDER) {
        return (UIE_SCALE_UNDER_CAPACITY);
    } else if (uchByte & UIE_SCALE_OVER) {
        return (UIE_SCALE_OVER_CAPACITY);
    }

    /* --- check status of high byte --- */
    uchByte = *(puchBuffer + 2) - (UCHAR)0x30;

    if (uchByte & UIE_SCALE_IN_MOTION) {
        return (UIE_SCALE_MOTION);
    } else if (uchByte & UIE_SCALE_ZERO) {
        return (0);
    } else {
        return (TRUE);
    }
}

class UsbSerialDevice
{
public:
    UsbSerialDevice();
    ~UsbSerialDevice();
    int CreateEndPoint (wchar_t *wszVendorId);
    int CloseEndPoint ();
    int ReadStream (void *bString, size_t nBytes);
    int WriteStream (void *bString, size_t nBytes);

    DWORD   m_dwError;          // GetLastError() for last action
    DWORD   m_dwErrorWrite;     // GetLastError() for last write
    DWORD   m_dwErrorRead;      // GetLastError() for last read
    DWORD   m_dwBytesWritten;
    DWORD   m_dwBytesRead;

private:
    HANDLE        m_hFile;
    DWORD         m_dwStatError;
    COMMTIMEOUTS  m_timeOut;
    COMSTAT       m_statOut;
};

UsbSerialDevice::UsbSerialDevice() :
    m_dwError(0),
    m_dwErrorWrite(0),
    m_dwErrorRead(0),
    m_dwBytesWritten(0),
    m_dwBytesRead(0),
    m_hFile(NULL)
{
}

UsbSerialDevice::~UsbSerialDevice()
{
    CloseHandle (m_hFile);
}

int UsbSerialDevice::WriteStream(void *bString, size_t nBytes)
{
    BOOL  bWrite = FALSE;

    if (m_hFile) {
        m_dwError = m_dwErrorWrite = 0;
        m_dwBytesWritten = 0;
        ClearCommError (m_hFile, &m_dwStatError, &m_statOut);
        bWrite = WriteFile (m_hFile, bString, nBytes, &m_dwBytesWritten, NULL);
        m_dwError = m_dwErrorWrite = GetLastError();
        return 0;
    }

    return -1;
}

int UsbSerialDevice::ReadStream(void *bString, size_t nBytes)
{
    BOOL  bRead = FALSE;

    if (m_hFile) {
        m_dwError = m_dwErrorRead = 0;
        m_dwBytesRead = 0;
        ClearCommError (m_hFile, &m_dwStatError, &m_statOut);
        bRead = ReadFile (m_hFile, bString, nBytes, &m_dwBytesRead, NULL);
        m_dwError = m_dwErrorRead = GetLastError();
        return 0;
    }

    return -1;
}

int UsbSerialDevice::CreateEndPoint (wchar_t *wszVendorId)
{
    HDEVINFO    hDevInfo;


    m_dwError = ERROR_INVALID_HANDLE;

    // We will try to get device information set for all USB devices that have a
    // device interface and are currently present on the system (plugged in).
    hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
    if (hDevInfo != INVALID_HANDLE_VALUE)
    {
        DWORD    dwMemberIdx;
        BOOL     bContinue = TRUE;
        SP_DEVICE_INTERFACE_DATA         DevIntfData;

        // Prepare to enumerate all device interfaces for the device information
        // set that we retrieved with SetupDiGetClassDevs(..)
        DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
        dwMemberIdx = 0;

        // Next, we will keep calling this SetupDiEnumDeviceInterfaces(..) until this
        // function causes GetLastError() to return  ERROR_NO_MORE_ITEMS. With each
        // call the dwMemberIdx value needs to be incremented to retrieve the next
        // device interface information.
        for (BOOL bContinue = TRUE; bContinue; ) {
            PSP_DEVICE_INTERFACE_DETAIL_DATA  DevIntfDetailData;
            SP_DEVINFO_DATA    DevData;
            DWORD  dwSize;

            dwMemberIdx++;
            SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_USB_DEVICE, dwMemberIdx, &DevIntfData);

            if (GetLastError() == ERROR_NO_MORE_ITEMS) break;

            // As a last step we will need to get some more details for each
            // of device interface information we are able to retrieve. This
            // device interface detail gives us the information we need to identify
            // the device (VID/PID), and decide if it's useful to us. It will also
            // provide a DEVINFO_DATA structure which we can use to know the serial
            // port name for a virtual com port.

            DevData.cbSize = sizeof(DevData);

            // Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail with
            // a NULL DevIntfDetailData pointer, a DevIntfDetailDataSize
            // of zero, and a valid RequiredSize variable. In response to such a call,
            // this function returns the required buffer size at dwSize.

            SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL);

            // Allocate memory for the DeviceInterfaceDetail struct. Don't forget to
            // deallocate it later!
            DevIntfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
            DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

            if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, DevIntfDetailData, dwSize, &dwSize, &DevData))
            {
                // Finally we can start checking if we've found a useable device,
                // by inspecting the DevIntfDetailData->DevicePath variable.
                //
                // The DevicePath looks something like this for a Brecknell 67xx Series Serial Scale
                // \?\usb#vid_1a86&pid_7523#6&28eaabda&0&2#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
                //
                // The VID for a particular vendor will be the same for a particular vendor's equipment.
                // The PID is variable for each device of the vendor.
                //
                // As you can see it contains the VID/PID for the device, so we can check
                // for the right VID/PID with string handling routines.

                // See https://github.com/Microsoft/Windows-driver-samples/blob/master/usb/usbview/vndrlist.h

                if (wcsstr (DevIntfDetailData->DevicePath, wszVendorId)) {
                    m_dwError = 0;
                    m_hFile = CreateFile (DevIntfDetailData->DevicePath, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
                    if (m_hFile == INVALID_HANDLE_VALUE) {
                        m_dwError = GetLastError();
                    } else {
                        GetCommTimeouts (m_hFile, &m_timeOut);
                        m_timeOut.ReadIntervalTimeout = 0;
                        m_timeOut.ReadTotalTimeoutMultiplier = 0;
                        m_timeOut.ReadTotalTimeoutConstant = 5000;
                        SetCommTimeouts (m_hFile, &m_timeOut);
                        m_dwError = GetLastError();
                    }
                    bContinue = FALSE;    // found the vendor so stop processing after freeing the heap.
                }
            }

            HeapFree(GetProcessHeap(), 0, DevIntfDetailData);
        }

        SetupDiDestroyDeviceInfoList(hDevInfo);
    }

    return 0;
}


int _tmain(int argc, _TCHAR* argv[])
{
    UsbSerialDevice  myDev;

    myDev.CreateEndPoint (L"vid_1a86&pid_7523");
    switch (myDev.m_dwError) {
        case 0:
            // no error so just ignore.
            break;
        case ERROR_ACCESS_DENIED:
            wprintf (_T("   CreateFile() failed. GetLastError() = %d\n      ERROR_ACCESS_DENIED: Access is denied.\n      Is it already in use?\n"), myDev.m_dwError);
            break;
        case ERROR_GEN_FAILURE:
            wprintf (_T("   CreateFile() failed. GetLastError() = %d\n      ERROR_GEN_FAILURE: A device attached to the system is not functioning.\n      Is it an HID?\n"), myDev.m_dwError);
            break;
        case ERROR_INVALID_HANDLE:
            wprintf (_T("   CreateFile() failed. GetLastError() = %d\n      ERROR_INVALID_HANDLE: The handle is invalid.\n      CreateFile() failed?\n"), myDev.m_dwError);
            break;
        default:
            wprintf (_T("   CreateFile() failed. GetLastError() = %d\n"), myDev.m_dwError);
            break;
    }

    if (myDev.m_dwError == 0) {
        char   reqWeight[] = "W\r";
        char   resWeight[256] = {0};

        myDev.WriteStream (reqWeight, strlen (reqWeight));
        wprintf (_T("    Sent request now get response.\n"));
        Sleep (50);
        myDev.ReadStream (resWeight, 16);
        wprintf (_T("    Got response.\n"));
        if (resWeight[0] != '\n' || resWeight[9] != '\r') {
            wprintf (_T("    Unexpected format of response.\n"));
        }

        short sRet = UieScaleStatus (resWeight, myDev.m_dwBytesRead);

        resWeight[9] = 0;      // terminate the weight string so that we can write it out.
        wprintf (_T("   ScaleStatus = %d, Response from device - \"%S\"\n"), sRet, resWeight + 1);
    }

    return 0;
}

开发的附加信息

来自 Microsoft MSDN 的 INF 文件概述 https://msdn.microsoft.com/en-us/windows/hardware/drivers/install/overview-of-inf-files

Whosebug Do I need to write my own host side USB driver for a CDC device

Whosebug how to get vendor id and product id of a plugged usb device on windows

Is it possible to “transplant” drivers between machines? has a link to a document Debugging USB Device Installation on Windows and this posting Remove Windows Device Class in Registry 有更多信息。

USB serial driver (Usbser.sys) 来自微软。

USB device class drivers included in Windows 来自微软。

运行windows的PC(USB主机)与体重秤(USB设备)的通讯遵循USB协议。如果您为 windows 安装 libusb,您可以在使用 lsusb -v 时获得与 PC 从 USB 设备获取的信息类似的信息。一个 USB 设备可以实现多个 USB class.

如果 USB 设备创建虚拟 COM 端口,它肯定会实现 CDC ACM class(通信设备 Class 抽象控制模型)除此之外它还可以实现其他 USB class 像大容量存储 class,...

与 USB 设备的直接 通信还取决于它实现的设备 classes 及其接口和端点。如果 USB 设备实现 CDC ACM(虚拟 COM),您可以使用特定的 RS-232 命令(即 https://www.commfront.com/pages/3-easy-steps-to-understand-and-control-your-rs232-devices 或发送十六进制 'D' 到万用表以接收测量值)如果它实现质量存储class你平时使用批量传输

要更改您使用的 USB 设备的模式控制传输(简而言之请参阅 USB)

这里link是Win如何在确定设备的USBclass后决定加载哪个驱动https://msdn.microsoft.com/en-us/library/windows/hardware/ff538820%28v=vs.85%29.aspx (https://msdn.microsoft.com/en-us/library/windows/hardware/jj649944%28v=vs.85%29.aspx)

我不知道 Brecknell 如何实现作为虚拟 COM 的 CDC ACM 设备 class 但是通常任何支持 USB 的 Win 版本都应该能够加载 CDC ACM 设备的驱动程序 class(虚拟 COM)所以你是对的,这似乎是 .inf 驱动程序文件的问题 或驱动程序加载机制(可能是 Brecknell CDC ACM 实现的问题,但我不知道这么认为)

然后,如果 Win 加载工作驱动程序,则正常方式就是您所做的:将 CreateFile() 与分配给 USB 设备的 COM 一起使用。

奇怪的是,如果我随后使用打开虚拟串行通信端口 COM4 的应用程序,该应用程序工作正常并且秤会报告物品的重量。 < - 这并不奇怪,奇怪的是某些 Win 版本无法识别 CDC USB 设备。 CDC 设备的标准驱动程序似乎是 USBser.sys (https://msdn.microsoft.com/de-de/library/windows/hardware/dn707976%28v=vs.85%29.aspx) 如果您搜索 'windows does not recognize CDC device',您会得到结果

如果 USB 设备在插入时创建虚拟串行通信端口,那么是否需要通过在 CreateFile 中指定通信端口(在我的例子中为 COM4)来仅使用虚拟串行端口() call? 是的,如果 USB 设备实现了虚拟 COM,这是使用此 COM 与此设备通信的最简单方法

另见 http://www.beyondlogic.org/usbnutshell/usb1.shtml USB 简述

标准 USB:设备描述符 (class) -> 接口 -> (配置) -> 端点

你混淆了两个问题,我们可能无法区分它们。

我这么说是因为你linkReadFile设备名有问题。但是,ReadFile 适用于 HANDLE。接受名称并将其转换为 HANDLE 的函数称为 CreateFile。这意味着 ReadFile 甚至 不知道 它使用的是什么名称。

这种误解也解释了其他一些行为。例如,当您拔下设备时,HANDLE 变为无效,并且一直无效。重新插入设备可能会恢复名称,但不会恢复 HANDLE

使用修改后的 USB 串行示例应用程序进行的测试表明,当拔下创建虚拟串行通信端口的 USB 设备时,创建的虚拟串行端口被拆除并从控制面板的设备管理器应用程序的端口列表中消失。

当设备(在本例中为 USB 秤)插入并打开时,虚拟串行通信端口将重新出现在设备管理器中。但是,当创建虚拟串行通信端口时,它是使用默认串行端口设置(波特率、奇偶校验等)创建的,这些设置可能与您的实际设备不同。

总而言之,虚拟串行通信端口设置似乎适用,无论该端口是否作为 COM 端口打开,或者 USB 设备路径名是否与 CreateFile().

一起使用

我仍在调查使用 POS Ready 7 时未自动创建的虚拟串行端口,一旦我了解更多信息,我将更新此答案。然而,Windows 7 和 POS Ready 7 之间的初步比较表明,我的 Windows 7 PC 上指定 usbser.sys、mdmcpq.inf 的文件不在 POS Ready 上7 终端在文件夹 C:\Windows\inf.

有关 .inf 文件结构和各个部分的说明,请参阅 The INF File。它有点旧,但它似乎以可读的方式涵盖了基础知识。

我将问题中的函数 CreateEndPoint() 修改为以下内容,同时对 class 和构造函数进行了更改,以便为我的秤创建一组默认通信端口设置。

class 和构造函数现在包含一组默认的通信端口设置(9600 波特、7 个数据位、一个停止位、比例的偶校验),看起来像:

class UsbSerialDevice
{
public:
    UsbSerialDevice();
    UsbSerialDevice(DWORD BaudRate, BYTE ByteSize = 8, BYTE Parity = NOPARITY, BYTE StopBits = ONESTOPBIT);
    ~UsbSerialDevice();
    int CreateEndPoint (wchar_t *wszVendorId);
    int SetCommPortSettings (DWORD BaudRate, BYTE ByteSize = 8, BYTE Parity = NOPARITY, BYTE StopBits = ONESTOPBIT);
    int CloseEndPoint ();
    int ReadStream (void *bString, size_t nBytes);
    int WriteStream (void *bString, size_t nBytes);
    int UpdateSettingsProxy (void);

    DWORD   m_dwError;          // GetLastError() for last action
    DWORD   m_dwErrorWrite;     // GetLastError() for last write
    DWORD   m_dwErrorRead;      // GetLastError() for last read
    DWORD   m_dwErrorCommState;
    DWORD   m_dwErrorCommTimeouts;
    DWORD   m_dwBytesWritten;
    DWORD   m_dwBytesRead;

    COMMTIMEOUTS  m_timeOut;           // last result from GetCommTimeouts(), updated by UpdateSettingsProxy()
    COMSTAT       m_statOut;           // last result from ClearCommError()
    DCB           m_commSet;           // last result from GetCommState(), updated by UpdateSettingsProxy()

private:
    HANDLE        m_hFile;
    DWORD         m_dwStatError;
    DCB           m_commSetDefault;    // the defaults used as standard
    wchar_t       m_portName[24];      // contains portname if defined for device in form \.\COMnn
};

UsbSerialDevice::UsbSerialDevice() :
    m_dwError(0),
    m_dwErrorWrite(0),
    m_dwErrorRead(0),
    m_dwBytesWritten(0),
    m_dwBytesRead(0),
    m_hFile(NULL)
{
    // initialize our COM port settings and allow people to change with 
    memset (&m_commSetDefault, 0, sizeof(m_commSetDefault));
    m_commSetDefault.DCBlength = sizeof(m_commSetDefault);
    m_commSetDefault.BaudRate = CBR_9600;
    m_commSetDefault.ByteSize = 7;
    m_commSetDefault.Parity = EVENPARITY;
    m_commSetDefault.StopBits = ONESTOPBIT;
    m_commSet.fDtrControl = DTR_CONTROL_DISABLE;
    m_portName[0] = 0;
}

修改函数CreateEndPoint(),在CreateFile()使用USB设备的路径名打开USB设备后,它现在还会设置通信端口参数。

该方法的另一个实验性更改是检查是否还创建了通信端口名称,如果是,则生成正确的 COM 端口规范以与 CreateFile() 一起使用。我计划将 CreateEndPoint() 方法分成两种方法,一种用于查找 USB 设备,另一种用于在我继续调查时实际打开。

对于大于 COM9 的 COM 端口,CreateFile() 的 COM 端口说明符格式似乎需要 \.\ 作为前缀。请参阅来自 Microsoft 支持的 HOWTO: Specify Serial Ports Larger than COM9

新版本的CreateEndPoint()长这样:

int UsbSerialDevice::CreateEndPoint (wchar_t *wszVendorId)
{
    HDEVINFO    hDevInfo;


    m_dwError = ERROR_INVALID_HANDLE;

    // We will try to get device information set for all USB devices that have a
    // device interface and are currently present on the system (plugged in).
    hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
    if (hDevInfo != INVALID_HANDLE_VALUE)
    {
        DWORD    dwMemberIdx;
        BOOL     bContinue = TRUE;
        SP_DEVICE_INTERFACE_DATA         DevIntfData;

        // Prepare to enumerate all device interfaces for the device information
        // set that we retrieved with SetupDiGetClassDevs(..)
        DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
        dwMemberIdx = 0;

        // Next, we will keep calling this SetupDiEnumDeviceInterfaces(..) until this
        // function causes GetLastError() to return  ERROR_NO_MORE_ITEMS. With each
        // call the dwMemberIdx value needs to be incremented to retrieve the next
        // device interface information.
        for (BOOL bContinue = TRUE; bContinue; ) {
            PSP_DEVICE_INTERFACE_DETAIL_DATA  DevIntfDetailData;
            SP_DEVINFO_DATA    DevData;
            DWORD  dwSize;

            dwMemberIdx++;
            SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_USB_DEVICE, dwMemberIdx, &DevIntfData);

            if (GetLastError() == ERROR_NO_MORE_ITEMS) break;

            // As a last step we will need to get some more details for each
            // of device interface information we are able to retrieve. This
            // device interface detail gives us the information we need to identify
            // the device (VID/PID), and decide if it's useful to us. It will also
            // provide a DEVINFO_DATA structure which we can use to know the serial
            // port name for a virtual com port.

            DevData.cbSize = sizeof(DevData);

            // Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail with
            // a NULL DevIntfDetailData pointer, a DevIntfDetailDataSize
            // of zero, and a valid RequiredSize variable. In response to such a call,
            // this function returns the required buffer size at dwSize.

            SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL);

            // Allocate memory for the DeviceInterfaceDetail struct. Don't forget to
            // deallocate it later!
            DevIntfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
            DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

            if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, DevIntfDetailData, dwSize, &dwSize, &DevData))
            {
                // Finally we can start checking if we've found a useable device,
                // by inspecting the DevIntfDetailData->DevicePath variable.
                //
                // The DevicePath looks something like this for a Brecknell 67xx Series Serial Scale
                // \?\usb#vid_1a86&pid_7523#6&28eaabda&0&2#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
                //
                // The VID for a particular vendor will be the same for a particular vendor's equipment.
                // The PID is variable for each device of the vendor.
                //
                // As you can see it contains the VID/PID for the device, so we can check
                // for the right VID/PID with string handling routines.

                // See https://github.com/Microsoft/Windows-driver-samples/blob/master/usb/usbview/vndrlist.h

                if (wcsstr (DevIntfDetailData->DevicePath, wszVendorId)) {
                    HKEY   hKey;

                    m_dwError = 0;
                    // To find out the serial port for our scale device,
                    // we'll need to check the registry:
                    hKey = SetupDiOpenDevRegKey(hDevInfo, &DevData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);

                    if (hKey != INVALID_HANDLE_VALUE) {
                        DWORD    dwSize = 0, dwType = 0;
                        wchar_t  lpData[16] = {0};

                        dwType = REG_SZ;
                        dwSize = sizeof(lpData);
                        LONG queryStat = RegQueryValueEx(hKey, _T("PortName"), NULL, &dwType, (LPBYTE)&lpData[0], &dwSize);
                        RegCloseKey(hKey);
                        if (queryStat == ERROR_SUCCESS) {
                            wcscpy (m_portName, L"\\.\");
                            wcsncat (m_portName, lpData, dwSize / sizeof(wchar_t));
                        }
                    } else {
                        m_dwError = GetLastError();
                    }

                    m_hFile = CreateFile (DevIntfDetailData->DevicePath, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
                    if (m_hFile == INVALID_HANDLE_VALUE) {
                        m_dwError = GetLastError();
                    } else {
                        m_dwError = 0;
                        GetCommState (m_hFile, &m_commSet);
                        m_commSet = m_commSetDefault;
                        SetCommState (m_hFile, &m_commSet);
                        m_dwErrorCommState = GetLastError();
                        GetCommState (m_hFile, &m_commSet);

                        GetCommTimeouts (m_hFile, &m_timeOut);
                        m_timeOut.ReadIntervalTimeout = 0;
                        m_timeOut.ReadTotalTimeoutMultiplier = 0;
                        m_timeOut.ReadTotalTimeoutConstant = 5000;
                        SetCommTimeouts (m_hFile, &m_timeOut);
                        GetCommTimeouts (m_hFile, &m_timeOut);
                        m_dwErrorCommTimeouts = GetLastError();
                    }
                    bContinue = FALSE;    // found the vendor so stop processing after freeing the heap.
                }
            }

            HeapFree(GetProcessHeap(), 0, DevIntfDetailData);
        }

        SetupDiDestroyDeviceInfoList(hDevInfo);
    }

    return 0;
}

POS 就绪 7 调查

回顾 Windows 7 PC,它似乎可以很好地配合体重秤使用,我们使用控制面板中的设备管理器查看了虚拟串行通信端口的驱动程序详细信息。驱动程序详细信息表明正在使用的驱动程序是由 www.winchiphead.com 提供的 CH341S64.SYS,并且 属性 "Inf name" 的值为 oem50.inf。我找到了一个论坛 post http://doityourselfchristmas.com/forums/showthread.php?14690-CH340-USB-RS232-Driver which provides a link to a driver download at http://www.winchiphead.com/download/CH341/CH341SER.ZIP however another version available from http://www.wch.cn/download/CH341SER_ZIP.html 可能是最新的。

将下载的zip文件,CH341SER.ZIP从后面放到POS Ready 7终端,我解压内容和运行 SETUP.EXE在文件夹CH341SER (zip 文件中有两个文件夹,一个名为 INSTALL 的文件夹似乎用于设备开发)显示一个对话框并允许我安装 CH341SER.INF。安装完成后,当我插入 USB 秤时,设备被识别并创建了一个虚拟串行通信端口,我的测试应用程序运行。

我确实找到了一些文档,但是都是中文的。 Google T运行slate 提供了 USB 设备文档的可读版本。当秤在使用中可能 unplugged/replugged 时,设备管理似乎还有额外的工作要做。

另外一件事 运行ge 事情 是秤现在使用不同的 COM 端口名称,COM5 而不是 COM4。查看高级设置,COM4 似乎是 "In Use",但未显示在端口列表中。进一步的实验表明,用于秤设备的 COM 端口名称取决于插入前面板两个 USB 端口中的哪一个。我最初插入左侧的 USB 端口,今天插入右侧的 USB 端口,结果是使用新的 COM 端口名称创建了虚拟串行通信端口。

然而,由于我们在 CreateFile() 中使用 USB 路径,因此 USB 示例测试应用程序中无需更改。

使用三根 USB 转串行转换器电缆对 POS Ready 7 进行的进一步测试表明不同供应商的电缆在 USB 路径中具有相同的供应商 ID 和产品代码。 USB 路径也根据电缆插入的 USB 端口而改变。在某些情况下,只有路径名中的最后一位不同。一个有趣的实验是,如果 USB 集线器连接到 USB 端口,然后 USB 连接到集线器,那么路径名是什么样的?