简体   繁体   English

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

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

I have a point of sale application that uses Serial Communication ports (RS-232) to communicate with a scale for weighing products. 我有一个销售点应用程序,它使用串行通信端口(RS-232)与称重产品的秤进行通信。 I am now working on being able to support USB devices directly rather than using a Virtual Serial Communication port as they have an annoying tendency to move around. 我现在正致力于直接支持USB设备,而不是使用虚拟串行通信端口,因为它们有一种令人讨厌的移动趋势。

What we have found is that while Windows 7 seems to automatically create the Virtual Serial Communication port, other versions of Windows such as POS Ready 7 may not. 我们发现虽然Windows 7似乎会自动创建虚拟串行通信端口,但其他版本的Windows(如POS Ready 7)可能不会。 We suspect this is due to a specific .inf file with Windows 7 that is missing from POS Ready 7. Can someone confirm that? 我们怀疑这是由于POS Ready 7中缺少一个特定的.inf文件,但是有人确认了吗?

I have a USB sample application that works intermittently. 我有一个间歇性工作的USB示例应用程序。 I am having a problem with the USB level communication with the ReadFile() Windows API function. 我遇到了与ReadFile() Windows API函数进行USB级别通信的问题。 I am using CreateFile() specifying the USB device path to obtain an I/O handle followed by using WriteFile() and ReadFile() to communicate with the scale. 我正在使用CreateFile()指定USB设备路径来获取I / O句柄,然后使用WriteFile()ReadFile()与比例进行通信。 The ReadFile() is not providing data in some cases. 在某些情况下, ReadFile()不提供数据。

Background Information 背景资料

The particular scale I am using, Brecknell 67xx bench scale, worked with using Virtual Serial Communication port directly out of the box with the point of sale application. 我正在使用的特定规模,Brecknell 67xx台秤,使用虚拟串行通信端口直接开箱即用的销售点应用程序。 I connected the scale to my Windows 7 PC with a USB cable and Windows automatically installed the drivers to create a Virtual Serial port, COM4 in my case. 我用USB电缆将秤连接到我的Windows 7 PC,Windows自动安装了驱动程序,在我的情况下创建了一个虚拟串口COM4。 I then configured the application to talk to the scale through COM4 and everything worked fine. 然后我配置应用程序通过COM4与规模通信,一切正常。

The protocol for using the scale is to send a two byte command, "W\\r" (capital letter W followed by a carriage return character) to the scale and to then read a 16 byte response which contains the current weight as well as status information about scale mechanics such as In Motion. 使用比例的协议是将两个字节的命令“W \\ r”(大写字母W后跟一个回车符)发送到比例,然后读取一个16字节的响应,其中包含当前的权重和状态有关比例力学的信息,例如In Motion。

The sample USB application that I am learning from will work successfully providing a weight. 我正在学习的示例USB应用程序将成功地提供重量。 Then it will stop working properly with the behavior of the ReadFile() returning zero bytes read. 然后它将继续正常工作, ReadFile()的行为返回零字节读取。 Once it stops working it will continue failing to provide data from the ReadFile() even if I unplug and replug the USB cable or restart my PC. 一旦它停止工作,它将继续无法从ReadFile()提供数据,即使我拔下并重新插上USB电缆或重新启动我的电脑。

A previous version of the learning application was hanging on the ReadFile() and when a Break All was done with Visual Studio, a pause followed by a message indicating a deadlock would be displayed. 先前版本的学习应用程序挂在ReadFile() ,当使用Visual Studio完成Break All时,将显示一个暂停,后跟一条指示死锁的消息。 However since I started using SetCommTimeouts() with a 5000 millisecond timeout value in ReadTotalTimeoutConstant I see a consistent 5 second pause before the ReadFile() returns with zero bytes read. 但是,由于我在SetCommTimeouts()开始使用SetCommTimeouts()并具有5000毫秒的超时值, ReadTotalTimeoutConstantReadFile()返回之前看到一致的5秒暂停,读取零字节。

The strange thing is that if I then use the application which opens the Virtual Serial Communication port, COM4, that application works fine and the scale reports the weight of items. 奇怪的是,如果我然后使用打开虚拟串行通信端口COM4的应用程序,该应用程序工作正常,并且比例报告项目的权重。

I can then return to the sample application that uses direct USB rather than the Virtual Serial Communication port and it will work fine reporting weights. 然后我可以返回到使用直接USB而不是虚拟串行通信端口的示例应用程序,它将工作良好的报告权重。

However if I then unplug the USB cable connecting scale with PC, which powers off the scale as well, then plug the USB cable back in, the sample application no longer functions correctly and once again I see the pause with timeout. 但是,如果我然后拔掉USB电缆连接秤与PC电源关闭,然后重新插入USB电缆,示例应用程序不再正常工作,我再次看到暂停超时。

Then I try using the original point of sale application that depends on Serial Communication ports using the Virtual Serial port, COM4, and that application weighs items just fine. 然后我尝试使用原始销售点应用程序,该应用程序依赖于使用虚拟串行端口COM4的串行通信端口,并且该应用程序称重项目就好了。

And when I then retry my sample application, it also will report item weights. 然后当我重试我的示例应用程序时,它也会报告项目权重。

My Questions. 我的问题。

If a USB device creates a Virtual Serial Communications port when it is plugged in then is it required to only use the Virtual Serial port by specifying the communications port, COM4 in my case, in the CreateFile() call? 如果USB设备在插入时创建了一个虚拟串行通信端口,那么是否只需要在CreateFile()调用中通过指定通信端口COM4来使用虚拟串行端口?

How is it possible to have direct USB serial communication by using CreateFile() with the USB device path if the device causes Windows to generate a Virtual Communication port? 如果设备导致Windows生成虚拟通信端口,如何使用带有USB设备路径的CreateFile()进行直接USB串行通信?

Is there some way of specifying that any version of Windows is to automatically create a Virtual Serial Communications port for the device when it is plugged in? 是否有某种方法可以指定任何版本的Windows在插入设备时自动为设备创建虚拟串行通信端口?

Source Code of the Sample USB Application 样本USB应用程序的源代码

The source code from my sample USB Windows Console application using Visual Studio 2005 is as follows with the main being at the bottom and much of this being the class for finding a particular USB device and then allowing ReadFile() and WriteFile() : 我使用Visual Studio 2005的示例USB Windows控制台应用程序的源代码如下,主要位于底部,其中大部分是查找特定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;
}

Additional Information Developed 附加信息开发

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

Stackoverflow Do I need to write my own host side USB driver for a CDC device Stackoverflow我是否需要为CDC设备编写自己的主机端USB驱动程序

Stackoverflow how to get vendor id and product id of a plugged usb device on windows Stackoverflow如何在Windows上获取插入的USB设备的供应商ID和产品ID

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 has a bit more info. 有一个文件的链接在Windows上调试USB设备安装和这个帖子在注册表中删除Windows设备类有更多的信息。

USB serial driver (Usbser.sys) from Microsoft. 来自Microsoft的USB串行驱动程序(Usbser.sys)

USB device class drivers included in Windows from Microsoft. Microsoft提供的Windows中包含的USB设备类驱动程序

The communication of the PC that runs windows (USB host) and the scale (USB device) obeys the USB protocol. 运行Windows(USB主机)和秤(USB设备)的PC的通信服从USB协议。 If you install libusb for windows you can get similar informations as the PC gets from the USB device, when using lsusb -v . 如果你安装libusb for windows,你可以获得类似于PC从USB设备获取的信息,当使用lsusb -v It is possible for a USB device to implement more than one USB class. USB设备可以实现多个USB类。

If the USB device creates a Virtual COM port it for sure implements the CDC ACM class (Communication Device Class Abstract Control Model) beside this it can also implement other USB classes like Mass Storage class,... 如果USB设备创建一个虚拟COM端口,它肯定会实现CDC ACM类(通信设备类抽象控制模型),它还可以实现其他USB类,如海量存储类,...

Direct communication with USB device depends also on what device classes it implements and its interfaces and endpoints. 与USB设备的直接通信还取决于它实现的设备类及其接口和端点。 If the USB device implements a CDC ACM (Virtual COM) you use the specific RS-232 commands (ie https://www.commfront.com/pages/3-easy-steps-to-understand-and-control-your-rs232-devices or send a hexadecimal 'D' to a multimeter to receive the measured value) if it implements the Mass Storage class you normally use bulk transfers 如果USB设备实现了CDC ACM(虚拟COM),则使用特定的RS-232命令(即https://www.commfront.com/pages/3-easy-steps-to-understand-and-control-your- rs232设备或将十六进制“D”发送到万用表以接收测量值)如果它实现了Mass Storage类,则通常使用批量传输

To change the mode of the USB device you use control transfers (see USB in a nutshell) 要更改USB设备的模式,请使用控制传输 (简而言之,请参阅USB)

In this link is how Win determines which driver to load after determining the USB class of the device 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 ) 在此链接中,Win确定在确定设备的USB类后要加载哪个驱动程序https://msdn.microsoft.com/en-us/library/windows/hardware/ff538820%28v=vs.85%29.aspxhttps://msdn.microsoft.com/en-us/library/windows/hardware/jj649944%28v=vs.85%29.aspx

i do not know how Brecknell implemented the CDC ACM device class that is the Virtual COM however normally any Win version that supports USB should be able to load a driver for a CDC ACM device class (Virtual COM) so you are correct this seems to be a problem of the .inf driver file or the driver loading mechanism (maybe a problem of the Brecknell CDC ACM implementation but i do not think so) 我不知道Brecknell如何实现作为虚拟COM的CDC ACM设备类,但通常任何支持USB的Win版本都应该能够为CDC ACM设备类(虚拟COM)加载驱动程序,所以你似乎是正确的.inf驱动程序文件或驱动程序加载机制的问题(可能是Brecknell CDC ACM实现的问题,但我不这么认为)

Then, if Win loads a working driver the normal way is what you did: use CreateFile() with the COM that is assigned to the USB device. 然后,如果Win加载了一个正常工作的驱动程序,那么就是你所做的:使用CreateFile()和分配给USB设备的COM。

The strange thing is that if I then use the application which opens the Virtual Serial Communication port, COM4, that application works fine and the scale reports the weight of items. 奇怪的是,如果我然后使用打开虚拟串行通信端口COM4的应用程序,该应用程序工作正常,并且比例报告项目的权重。 <- this is not strange, strange is that some Win versions do not recognize a CDC USB device . < - 这并不奇怪,奇怪的是有些Win版本无法识别CDC USB设备。 The standard driver for CDC devices seems to be USBser.sys ( https://msdn.microsoft.com/de-de/library/windows/hardware/dn707976%28v=vs.85%29.aspx ) If you search 'windows does not recognize CDC device' you get results CDC设备的标准驱动程序似乎是USBser.syshttps://msdn.microsoft.com/de-de/library/windows/hardware/dn707976%28v=vs.85%29.aspx )如果搜索'windows无法识别CDC设备'你得到了结果

If a USB device creates a Virtual Serial Communications port when it is plugged in then is it required to only use the Virtual Serial port by specifying the communications port, COM4 in my case, in the CreateFile() call? 如果USB设备在插入时创建了一个虚拟串行通信端口,那么是否只需要在CreateFile()调用中通过指定通信端口COM4来使用虚拟串行端口? Yes, if a USB device implements a virtual COM it is the easiest way to use this COM to communicate with this device 是的,如果USB设备实现虚拟COM,则使用此COM与此设备通信是最简单的方法

See also http://www.beyondlogic.org/usbnutshell/usb1.shtml USB in a nutshell 另请参见http://www.beyondlogic.org/usbnutshell/usb1.shtml USB

standard USB: device descriptor (class) -> interface -> (configuration) -> endpoint 标准USB:设备描述符(类) - >接口 - >(配置) - >端点

Testing with a modified USB Serial sample application indicates that when a USB device that creates a Virtual Serial Communications port is unplugged the Virtual Serial Port created is torn down and disappears from the port listing in Device Manager app of Control Panel. 使用修改后的USB串行示例应用程序进行测试表明,当拔出创建虚拟串行通信端口的USB设备时,创建的虚拟串行端口将被拆除,并从控制面板的设备管理器应用程序中的端口列表中消失。

When the device, a USB scale in this case, is plugged in and turned on the Virtual Serial Communications port reappears in Device Manager. 当设备(在这种情况下为USB刻度)插入并打开时,虚拟串行通信端口将再次出现在设备管理器中。 However when the Virtual Serial Communications port is created, it is created with default serial port settings (baud rate, parity, etc.) and these may not be the same as for your actual device. 但是,创建虚拟串行通信端口时,会使用默认串行端口设置(波特率,奇偶校验等)创建它,这些可能与实际设备不同。

In summary it appears that the Virtual Serial Communications port settings apply regardless of whether the port is opened as a COM port or if the USB device path name is used with the CreateFile() . 总之,无论端口是作为COM端口打开还是USB设备路径名与CreateFile()一起使用,似乎都应用虚拟串行通信端口设置。

I am still investigating the Virtual Serial Port not automatically being created when using POS Ready 7 and will update this answer once I know more. 我仍在调查使用POS Ready 7时不会自动创建虚拟串行端口,并且在我知道更多后会更新此答案。 However preliminary comparison between Windows 7 and POS Ready 7 is showing that a file that specifies usbser.sys , mdmcpq.inf, that is on my Windows 7 PC is not on the POS Ready 7 terminal in the folder C:\\Windows\\inf. 但是,Windows 7和POS Ready 7之间的初步比较显示,在我的Windows 7 PC上指定usbser.sys ,mdmcpq.inf的文件不在文件夹C:\\ Windows \\ inf中的POS Ready 7终端上。

See The INF File for a write up on the .inf file structure and the various sections. 请参阅INF文件以了解.inf文件结构和各个部分的内容。 It is a bit old however it seems to cover the basics in a readable manner. 它有点旧,但似乎以可读的方式涵盖了基础知识。

I modified the function CreateEndPoint() in the question to the following along with a change to the class and the constructor to create a set of default communication port settings for my scale. 我将问题中的函数CreateEndPoint()修改为以下内容以及对类和构造函数的更改,以便为我的比例创建一组默认通信端口设置。

The class and the constructor now contain a set of defaults for the communication port settings (9600 baud, 7 data bits, one stop bit, even parity for the scale) and look like: 类和构造函数现在包含一组通信端口设置的默认值(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;
}

The function CreateEndPoint() is modified so that after doing the CreateFile() to open the USB device using the pathname of the USB Device, it will now also set the communication port parameters. 修改函数CreateEndPoint() ,以便在使用USB设备的路径名执行CreateFile()打开USB设备后,它现在还将设置通信端口参数。

An additional experimental change to the method was to check if a communications port name was also created and if so to generate the proper COM port specification to be used with CreateFile() . 该方法的另一个实验性更改是检查是否还创建了通信端口名称,如果是,则生成与CreateFile()一起使用的正确COM端口规范。 I plan to split out the CreateEndPoint() method into two methods, one to do a look up of the USB device and a second to actually do the open as I continue my investigation. 我计划将CreateEndPoint()方法拆分为两个方法,一个用于查找USB设备,另一个用于在我继续调查时实际打开。

The format for the COM port specifier for CreateFile() for COM ports greater than COM9 appear to need the \\\\.\\ as a prefix. COM端口大于COM9的CreateFile() COM端口说明符的格式似乎需要\\\\.\\作为前缀。 See HOWTO: Specify Serial Ports Larger than COM9 from Microsoft Support. 请参阅HOWTO:指定大于COM支持的COM9的串行端口

The new version of CreateEndPoint() looks like: 新版本的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 Ready 7 Investigation POS Ready 7调查

Looking back on the Windows 7 PC which seems to work fine with the scale, we looked in the driver details for the Virtual Serial Communications port using Device Manager from the Control Panel. 回顾一下似乎与规模一致的Windows 7 PC,我们使用“控制面板”中的“设备管理器”查看了虚拟串行通信端口的驱动程序详细信息。 The driver details indicated that the driver being used was CH341S64.SYS provided by www.winchiphead.com and the Property "Inf name" has a value of oem50.inf . 驱动程序详细信息表明正在使用的驱动程序是www.winchiphead.com提供的CH341S64.SYS,属性“Inf名称”的值为oem50.inf I found a forum 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 may be more up to date. 我发现了一个论坛帖子http://doityourselfchristmas.com/forums/showthread.php?14690-CH340-USB-RS232-Driver ,它提供了一个驱动程序下载链接, 网址http://www.winchiphead.com/download/CH341 /CH341SER.ZIP但是http://www.wch.cn/download/CH341SER_ZIP.html提供的另一个版本可能更新。

Putting the downloaded zip file, CH341SER.ZIP from the later on to the POS Ready 7 terminal, I unzipped the contents and ran SETUP.EXE in the folder CH341SER (there were two folders in the zip file and the one called INSTALL seemed for device development) which displayed a dialog and allowed me to install the CH341SER.INF . 将下载的zip文件CH341SER.ZIP从后来放到POS Ready 7终端,解压缩内容并在文件夹CH341SER运行SETUP.EXE (zip文件中有两个文件夹,一个名为INSTALL的文件夹似乎用于设备显示一个对话框,允许我安装CH341SER.INF Once the install completed, when I plugged in the USB scale, the device was recognized and a Virtual Serial Communications port was created and my test application worked. 安装完成后,当我插入USB刻度时,设备被识别并创建了一个虚拟串行通信端口,并且我的测试应用程序正常工作。

I did find some documentation however it was all in Chinese. 我确实找到了一些文件,但都是中文的。 Google Translate provided a readable version of the USB device documentation. Google Translate提供了USB设备文档的可读版本。 It looks like there is additional work to be done for device management when the scale may be unplugged/replugged while in use. 当使用时可以拔出/重新插入秤时,看起来还有其他工作需要进行设备管理。

One other strange thing is that the scale is now using a different COM port name, COM5 rather than COM4. 另一个奇怪的事情是规模现在使用不同的COM端口名称COM5而不是COM4。 Looking in the Advanced Settings it appears that COM4 is "In Use" though not showing in the list of ports. 查看高级设置,看起来COM4是“正在使用”,但未显示在端口列表中。 Further experiments indicates that the COM port name used for the scale device depends on which of the two front panel USB ports are plugged into. 进一步的实验表明,用于秤设备的COM端口名称取决于插入两个前面板USB端口中的哪一个。 I had originally plugged into the left one and today, plugged into the right USB port with the result of the Virtual Serial Communications port being created with a new COM port name. 我最初插入左侧,今天插入正确的USB端口,其结果是使用新的COM端口名称创建了虚拟串行通信端口。

However since we are using the USB path in the CreateFile() , no change was needed in the USB sample test application. 但是,由于我们在CreateFile()中使用USB路径,因此USB样本测试应用程序不需要进行任何更改。

Further testing with POS Ready 7 using three USB to Serial converter cables showed that different vendor's cables had the same vendor id and product code in the USB path. 使用三个USB转串口转换器电缆POS Ready 7进行的进一步测试表明,不同供应商的电缆在USB路径中具有相同的供应商ID和产品代码。 The USB path also changed depending on which USB port a cable was plugged into. USB路径也根据插入电缆的USB端口而改变。 In some cases only the last digit in the path name differed. 在某些情况下,只有路径名中的最后一位数字不同。 An interesting experiment would be if a USB hub is connected to a USB port and then USB connections are made to the hub what does the path name look like then? 一个有趣的实验是,如果USB集线器连接到USB端口,然后USB连接到集线器,那么路径名称是什么样的呢?

You are confusing two issues, and it's probably not viable for us to tell them apart. 你混淆了两个问题,我们可能不可能将它们区分开来。

I say this because you link ReadFile problems to the device name. 我这样说是因为你将ReadFile问题链接到设备名称。 However, ReadFile works on a HANDLE . 但是, ReadFile适用于HANDLE The function which takes a name and converts it into a HANDLE is called CreateFile . 获取名称并将其转换为HANDLE函数称为CreateFile That means ReadFile doesn't even know on what name it's operating. 这意味着ReadFile甚至不知道它在运行什么名称。

This misunderstanding also explains a few other behaviors. 这种误解也解释了一些其他行为。 For instance, when you unplug the device, the HANDLE becomes invalid, and it stays invalid. 例如,拔下设备时, HANDLE变为无效,并且保持无效。 Replugging the device may restore the name, but not the HANDLE . 重新插入设备可能会恢复名称,但不能恢复HANDLE

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM