Richard Chambers Richard Chambers - 11 days ago 5
C++ Question

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. 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.

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. We suspect this is due to a specific .inf file with Windows 7 that is missing from POS Ready 7. Can someone confirm that?

I have a USB sample application that works intermittently. I am having a problem with the USB level communication with the

ReadFile()
Windows API function. 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. The
ReadFile()
is not providing data in some cases.

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. 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. I then configured the application to talk to the scale through COM4 and everything worked fine.

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.

The sample USB application that I am learning from will work successfully providing a weight. Then it will stop working properly with the behavior of the
ReadFile()
returning zero bytes read. 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.

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. 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.

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.

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.

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.

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.

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?

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?

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?

Source Code of the Sample USB Application

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()
:

// 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
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;

if (sLength != 16 ||
*(puchBuffer + 10) != '\n' ||
*(puchBuffer + 11) != 'S' ||
*(puchBuffer + 14) != '\r' ||
*(puchBuffer + 15) != UIE_SCALE_ETX) {
return (UIE_SCALE_DATAFORMAT); /* exit ... */
}

/* --- check status of low byte --- */
uchByte = *(puchBuffer + 13) - (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 + 12) - (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

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

Stackoverflow 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 has a bit more info.

USB serial driver (Usbser.sys) from Microsoft.

USB device class drivers included in Windows from Microsoft.

Answer

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.

When the device, a USB scale in this case, is plugged in and turned on the Virtual Serial Communications port reappears in Device Manager. 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().

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. 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.

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.

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:

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.

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(). 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.

The format for the COM port specifier for CreateFile() for COM ports greater than COM9 appear to need the \\.\\ as a prefix. See HOWTO: Specify Serial Ports Larger than COM9 from Microsoft Support.

The new version of CreateEndPoint() looks like:

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

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. 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. 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.

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. 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.

I did find some documentation however it was all in Chinese. Google Translate provided a readable version of the USB device documentation. 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. Looking in the Advanced Settings it appears that COM4 is "In Use" though not showing in the list of ports. 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. 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.

However since we are using the USB path in the CreateFile(), no change was needed in the USB sample test application.