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



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

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

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

背景信息

我正在使用的特定秤,Brecknell 67xx台式秤,直接使用销售点应用程序开箱即用的虚拟串行通信端口。我用USB电缆将天平连接到我的Windows 7 PC,Windows自动安装了驱动程序,以创建一个虚拟串行端口,在我的情况下是COM4。然后,我将应用程序配置为通过COM4与天平对话,一切都很好。

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

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

以前版本的学习应用程序挂在ReadFile()上,当Visual Studio完成Break All时,将显示一个暂停,然后显示一条指示死锁的消息。然而,由于我在ReadTotalTimeoutConstant中开始使用超时值为5000毫秒的SetCommTimeouts(),在ReadFile()返回零字节读取之前,我看到了持续的5秒暂停。

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

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

然而,如果我拔下连接磅秤和PC的USB电缆(电脑也关闭了磅秤的电源),然后重新插入USB电缆,示例应用程序将无法正常工作,我再次看到暂停和超时。

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

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

我的问题

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

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

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

示例USB应用程序的源代码

我使用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() = %dn      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() = %dn      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() = %dn      ERROR_INVALID_HANDLE: The handle is invalid.n      CreateFile() failed?n"), myDev.m_dwError);
break;
default:
wprintf (_T("   CreateFile() failed. GetLastError() = %dn"), myDev.m_dwError);
break;
}
if (myDev.m_dwError == 0) {
char   reqWeight[] = "Wr";
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

Stackoverflow我需要为CDC设备编写自己的主机端USB驱动程序吗

Stackoverflow如何在Windows上获取插入的usb设备的供应商id和产品id

有可能在机器之间"移植"驱动程序吗?有一个链接到一个文档"在Windows上调试USB设备安装",这篇文章"在注册表中删除Windows设备类"有更多信息。

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

Microsoft Windows中包含的USB设备类驱动程序。

运行windows的PC(USB主机)与天平(USB设备)的通信遵循USB协议。如果您为windows安装libusb,当使用lsusb -v时,您可以从USB设备获得与PC类似的信息。USB设备可以实现多个USB类。

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

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

要更改USB设备的模式,请使用控制传输(请参阅USB)

在这个链接中,Win是如何在确定设备的USB类别后确定要加载哪个驱动程序的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是如何实现CDC ACM设备类(即虚拟COM)的,但通常情况下,任何支持USB的Win版本都应该能够加载CDC ACM装置类的驱动程序(虚拟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)如果你搜索"窗口无法识别CDC设备",你会得到结果

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

另请参阅http://www.beyondlogic.org/usbnutshell/usb1.shtmlUSB简言之

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

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

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

总之,无论端口是否作为COM端口打开,或者USB设备路径名称是否与CreateFile()一起使用,都会应用虚拟串行通信端口设置。

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

有关.INF文件结构和各个部分的详细信息,请参阅INF文件。它有点老,但它似乎以可读的方式涵盖了基本内容。

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

该类和构造函数现在包含一组通信端口设置的默认值(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(),以便在使用USB设备的路径名执行CreateFile()打开USB设备之后,它现在还将设置通信端口参数。

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

对于大于COM9的COM端口,CreateFile()的COM端口说明符的格式似乎需要\.作为前缀。请参阅HOWTO:从Microsoft支持中指定大于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 Ready 7调查

回顾Windows 7 PC,它似乎可以很好地与规模配合使用,我们从控制面板中使用设备管理器查看了虚拟串行通信端口的驱动程序详细信息。驱动程序详细信息表明,使用的驱动程序是www.winchiphehead.com提供的CH341S64.SYS,"Inf name"属性的值为oem50.inf。我发现了一个论坛帖子http://doityourselfchristmas.com/forums/showthread.php?14690-CH340-USB-RS232-驱动程序,它提供了一个链接到驱动程序下载http://www.winchiphead.com/download/CH341/CH341SER.ZIP但是,另一个版本可从http://www.wch.cn/download/CH341SER_ZIP.html可能是最新的。

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

我确实找到了一些文件,但都是中文的。谷歌翻译提供了一个可读版本的USB设备文档。当天平在使用中可能被拔下/重新插入插头时,设备管理似乎还有额外的工作要做。

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

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

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

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

我这么说是因为您将ReadFile问题与设备名称联系起来。但是,ReadFile适用于HANDLE。取一个名称并将其转换为HANDLE的函数称为CreateFile。这意味着ReadFile甚至不知道其运行的名称。

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

最新更新