我有一个销售点应用程序,它使用串行通讯端口(RS-232)与称重产品的秤进行通信。我现在正致力于直接支持USB设备,而不是使用虚拟串行通信端口,因为它们有一种令人讨厌的移动趋势。
我们发现虽然Windows 7似乎会自动创建虚拟串行通信端口,但其他版本的Windows(如POS Ready 7)可能不会。我们怀疑这是由于POS Ready 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后跟回车符)发送到比例,然后读取包含当前权重的16字节响应以及有关比例力学的状态信息,例如In Motion。
我正在学习的示例USB应用程序将成功提供重量。然后,它会在ReadFile()
返回零字节读取的行为中停止正常工作。一旦它停止工作,它将继续无法提供来自ReadFile()
的数据,即使我拔下并重新插上USB电缆或重新启动我的电脑。
学习应用程序的先前版本挂在ReadFile()
上,当使用Visual Studio完成Break All时,会显示一个暂停,然后显示一条指示死锁的消息。但是,由于我开始在SetCommTimeouts()
中使用ReadTotalTimeoutConstant
超时值为5000毫秒,因此在ReadFile()
返回之前看到一致的5秒暂停,读取零字节。
奇怪的是,如果我然后使用打开虚拟串行通信端口COM4的应用程序,该应用程序工作正常,并且比例报告项目的权重。
然后我可以返回使用直接USB而不是虚拟串行通信端口的示例应用程序,它将正常工作报告权重。
然而,如果我然后拔掉连接秤的USB线连接秤,它也会关闭秤,然后重新插入USB线,示例应用程序不再正常工作,我再次看到暂停超时。< / p>
然后我尝试使用原始销售点应用程序,该应用程序依赖于使用虚拟串行端口COM4的串行通信端口,并且该应用程序称重项目就好了。
然后,当我重试我的示例应用程序时,它也会报告项目权重。
我的问题。
如果USB设备在插入时创建了一个虚拟串行通信端口,那么是否只需要通过CreateFile()
呼叫指定通信端口COM4(在我的情况下为COM4)来使用虚拟串行端口? / p>
如果设备导致Windows生成虚拟通信端口,如何使用CreateFile()
与USB设备路径进行直接USB串行通信?
是否有某种方法可以指定任何版本的Windows在插入设备时自动为设备创建虚拟串行通信端口?
样本USB应用程序的源代码
使用Visual Studio 2005的示例USB Windows控制台应用程序的源代码如下,主要位于底部,其中大部分是查找特定USB设备然后允许ReadFile()
和{ {1}}:
WriteFile()
开发的其他信息
Microsoft MSDN https://msdn.microsoft.com/en-us/windows/hardware/drivers/install/overview-of-inf-files
中的INF文件概述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?有一个指向文档Debugging USB Device Installation on Windows的链接,此帖子Remove Windows Device Class in Registry包含更多信息。
来自Microsoft的 来自Microsoft的答案 0 :(得分:2)
运行Windows(USB主机)和秤(USB设备)的PC的通信遵循USB协议。如果您为Windows安装 libusb ,则可以在使用lsusb -v
时获得与PC从USB设备获取的类似信息。 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”发送到万用表以接收测量值)如果它实现了Mass Storage类您通常使用批量转移
要更改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如何实现作为虚拟COM的CDC ACM设备类,但是通常任何支持USB的Win版本都应该能够为CDC ACM设备类(虚拟COM)加载驱动程序,所以你这是正确的似乎是 .inf驱动程序文件的问题或驱动程序加载机制(可能是Brecknell CDC ACM实现的问题,但我不这么认为)
然后,如果Win加载了一个正常工作的驱动程序,那么就是你所做的:使用CreateFile()
和分配给USB设备的COM。
奇怪的是,如果我然后使用打开虚拟串行通信端口COM4的应用程序,那个应用程序工作正常,并且比例报告项目的重量。&lt; - 这不是奇怪的是,有些Win版本无法识别CDC USB设备。
CDC设备的标准驱动程序似乎是USBser.sys
(https://msdn.microsoft.com/de-de/library/windows/hardware/dn707976%28v=vs.85%29.aspx)
如果您搜索“Windows无法识别CDC设备”,您将获得结果
如果USB设备在插入时创建了一个虚拟串行通信端口,那么是否需要通过在CreateFile()调用中指定通信端口COM4(在我的情况下为COM4)来使用虚拟串行端口? 是的,如果USB设备实现虚拟COM,则使用此COM与此设备通信是最简单的方法
另请参见http://www.beyondlogic.org/usbnutshell/usb1.shtml USB简介
标准USB:设备描述符(类) - &gt;界面 - &gt; (配置) - &gt;端点
答案 1 :(得分:2)
使用修改后的USB串行示例应用程序进行测试表明,当拔出创建虚拟串行通信端口的USB设备时,创建的虚拟串行端口将被拆除,并从控制面板的设备管理器应用程序中的端口列表中消失。
当设备(在这种情况下为USB刻度)插入并打开时,虚拟串行通信端口会重新出现在设备管理器中。但是,在创建虚拟串行通信端口时,会使用默认串行端口设置(波特率,奇偶校验等)创建它,这些可能与实际设备不同。
总之,无论端口是作为COM端口打开还是USB设备路径名与CreateFile()
一起使用,虚拟串行通信端口设置似乎都适用。
我仍在调查使用POS Ready 7时不会自动创建虚拟串行端口,并且在我知道更多后会更新此答案。但是,Windows 7和POS Ready 7之间的初步比较显示,我的Windows 7 PC上指定usbser.sys
,mdmcpq.inf的文件不在文件夹C:\ Windows \中的POS Ready 7终端上INF。
有关.inf文件结构和各个部分的详细信息,请参阅The INF File。它有点陈旧,但似乎以可读的方式涵盖了基础知识。
我将问题中的函数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设备,另一种用于在我继续调查时实际打开。
COM端口大于COM9的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 Ready 7调查
回顾一下似乎与规模兼容的Windows 7 PC,我们使用“控制面板”中的“设备管理器”查看了虚拟串行通信端口的驱动程序详细信息。驱动程序详细信息表明正在使用的驱动程序是www.winchiphead.com提供的CH341S64.SYS和Property&#34; Inf name&#34;值为oem50.inf
。我找到了一个论坛帖子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提供的其他版本可能更新。
将下载的zip文件CH341SER.ZIP从稍后放入POS Ready 7终端,解压缩内容并在文件夹SETUP.EXE
中运行CH341SER
(zip中有两个文件夹)文件和一个名为INSTALL的文件似乎用于设备开发),它显示了一个对话框并允许我安装CH341SER.INF
。安装完成后,当我插入USB秤时,设备被识别并创建了一个虚拟串行通信端口,并且我的测试应用程序正常工作。
我确实找到了一些文件,但都是中文的。 Google Translate提供了USB设备文档的可读版本。当在使用中可能拔出/重新插入秤时,看起来还有其他工作要做设备管理。
另一个奇怪的事情是规模现在使用不同的COM端口名称COM5而不是COM4。查看高级设置,看来COM4是&#34;使用中&#34;虽然没有显示在端口列表中。进一步的实验表明,用于秤设备的COM端口名称取决于插入两个前面板USB端口中的哪一个。我最初插入左侧,今天插入正确的USB端口,其结果是使用新的COM端口名称创建了虚拟串行通信端口。
但是,由于我们在CreateFile()
中使用USB路径,因此USB样本测试应用程序无需进行任何更改。
使用三条USB转串口转换器电缆进行POS Ready 7的进一步测试表明,不同供应商的电缆在USB路径中具有相同的供应商ID和产品代码。 USB路径也根据插入电缆的USB端口而改变。在某些情况下,只有路径名中的最后一位数字不同。一个有趣的实验是,如果USB集线器连接到USB端口,然后USB连接到集线器,那么路径名称是什么样的呢?
答案 2 :(得分:0)
你混淆了两个问题,我们可能不可能将它们区分开来。
我这样说是因为您将ReadFile
问题与设备名称相关联。但是,ReadFile
适用于 HANDLE
。获取名称并将其转换为HANDLE
的函数称为CreateFile
。这意味着ReadFile
甚至知道关于它正在运行的名称。
这种误解也解释了其他一些行为。例如,当您拔下设备时,HANDLE
变为无效,并且无效。重新插入设备可能会恢复名称,但不能恢复HANDLE
。