我有一个销售点应用程序,它使用串行通信端口(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()
不提供数据 .
Background Information
我正在使用的特定规模,Brecknell 67xx台秤,使用虚拟串行通信端口直接开箱即用的销售点应用程序 . 我用USB电缆将秤连接到我的Windows 7 PC,Windows自动安装了驱动程序,在我的情况下创建了一个虚拟串口COM4 . 然后我配置应用程序通过COM4与规模通信,一切正常 .
使用比例的协议是将两个字节的命令“W \ r”(大写字母W后跟一个回车符)发送到比例,然后读取一个16字节的响应,其中包含当前的权重和状态有关比例力学的信息,例如In Motion .
我正在学习的示例USB应用程序将成功地提供重量 . 然后它将停止正常工作 ReadFile()
返回零字节读取的行为 . 一旦它停止工作,它将继续无法提供来自 ReadFile()
的数据,即使我拔下并重新插上USB电缆或重新启动我的电脑 .
学习应用程序的先前版本挂在 ReadFile()
上,当使用Visual Studio完成Break All时,将显示暂停,然后显示指示死锁的消息 . 但是,因为我开始在 ReadTotalTimeoutConstant
中使用 SetCommTimeouts()
,其超时值为5000毫秒,所以在 ReadFile()
返回之前看到一致的5秒暂停,读取零字节 .
奇怪的是,如果我然后使用打开虚拟串行通信端口COM4的应用程序,该应用程序工作正常,并且比例报告项目的权重 .
然后我可以返回到使用直接USB而不是虚拟串行通信端口的示例应用程序,它将工作良好的报告权重 .
但是,如果我然后拔掉USB电缆连接秤与PC电源关闭,然后重新插入USB电缆,示例应用程序不再正常工作,我再次看到暂停超时 .
然后我尝试使用原始销售点应用程序,该应用程序依赖于使用虚拟串行端口COM4的串行通信端口,并且该应用程序称重项目就好了 .
然后当我重试我的示例应用程序时,它也会报告项目权重 .
My Questions.
如果USB设备在插入时创建了一个虚拟串行通信端口,那么是否需要通过在 CreateFile()
呼叫中指定通信端口(我的情况下是COM4)来使用虚拟串行端口?
如果设备导致Windows生成虚拟通信端口,如何使用 CreateFile()
与USB设备路径进行直接USB串行通信?
是否有某种方法可以指定任何版本的Windows在插入设备时自动为设备创建虚拟串行通信端口?
Source Code of the Sample USB Application
我使用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
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 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的USB serial driver (Usbser.sys) .
来自Microsoft的USB device class drivers included in Windows .
3 回答
运行的PC的通信Windows(USB主机)和秤(USB设备)遵守USB协议 . 如果您为Windows安装了 libusb ,那么当使用
lsusb -v
时,您可以获得与PC从USB设备获取的类似信息 . USB设备可以实现多个USB类 .如果USB设备创建一个虚拟COM端口,它肯定会实现CDC ACM类(通信设备类抽象控制模型),它还可以实现其他USB类,如海量存储类,...
Direct 与USB设备的通信还取决于它实现的设备类及其接口和 endpoints . 如果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)加载驱动程序,所以你似乎是正确的 a problem of the .inf driver file 或驱动程序加载机制(可能是Brecknell CDC ACM实现的问题,但我不这么认为)
然后,如果Win加载了一个正常工作的驱动程序,那么你所做的就是:将
CreateFile()
与分配给USB设备的COM一起使用 .奇怪的是,如果我然后使用打开虚拟串行通信端口COM4的应用程序,该应用程序工作正常,并且比例报告项目的权重 . < - 这并不奇怪,奇怪的是有些Win版本无法识别CDC USB设备 . CDC设备的标准驱动程序似乎是
USBser.sys
(https://msdn.microsoft.com/de-de/library/windows/hardware/dn707976%28v=vs.85%29.aspx)如果您搜索'windows does not recognize CDC device',您会得到结果如果USB设备在插入时创建了一个虚拟串行通信端口,那么是否只需要在CreateFile()调用中通过指定通信端口COM4来使用虚拟串行端口?是的,如果USB设备实现虚拟COM,则使用此COM与此设备通信是最简单的方法
另请参阅http://www.beyondlogic.org/usbnutshell/usb1.shtml USB简介
标准USB:设备描述符(类) - >接口 - >(配置) - > endpoints
使用修改后的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文件结构和各个部分的说明,请参见The INF File . 它有点旧,但似乎以可读的方式涵盖了基础知识 .
我将问题中的函数
CreateEndPoint()
修改为以下内容以及对类和构造函数的更改,以便为我的比例创建一组默认通信端口设置 .类和构造函数现在包含一组通信端口设置的默认值(9600波特,7个数据位,一个停止位,甚至是标度的奇偶校验),如下所示:
修改函数
CreateEndPoint()
,以便在使用USB设备的路径名执行CreateFile()
打开USB设备后,它现在还将设置通信端口参数 .该方法的另一个实验性更改是检查是否还创建了通信端口名称以及是否所以生成适用于
CreateFile()
的COM端口规范 . 我计划将CreateEndPoint()
方法拆分为两种方法,一种是查看USB设备,另一种是在我继续调查时实际打开 .对于大于COM9的COM端口,
CreateFile()
的COM端口说明符的格式似乎需要\\.\
作为前缀 . 请参阅Microsoft支持中的HOWTO: Specify Serial Ports Larger than COM9 .新版
CreateEndPoint()
看起来像:POS Ready 7 Investigation
回顾一下似乎与规模一致的Windows 7 PC,我们使用“控制面板”中的“设备管理器”查看了虚拟串行通信端口的驱动程序详细信息 . 驱动程序详细信息表明正在使用的驱动程序是www.winchiphead.com提供的CH341S64.SYS,属性"Inf name"的值为
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终端,解压缩内容并在
CH341SER
文件夹中运行SETUP.EXE
(zip文件中有两个文件夹,一个名为INSTALL的文件夹似乎用于设备开发)它显示了一个对话框,允许我安装CH341SER.INF
. 安装完成后,当我插入USB刻度时,设备被识别并创建了一个虚拟串行通信端口,并且我的测试应用程序正常工作 .我确实找到了一些文件,但都是中文的 . Google Translate提供了USB设备文档的可读版本 . 当使用时可以拔出/重新插入秤时,看起来还有其他工作需要进行设备管理 .
One other strange thing 是规模现在使用不同的COM端口名称COM5而不是COM4 . 查看高级设置,虽然未显示在端口列表中,但COM4似乎是"In Use" . 进一步的实验表明,用于秤设备的COM端口名称取决于插入两个前面板USB端口中的哪一个 . 我最初插入左侧,今天插入正确的USB端口,其结果是使用新的COM端口名称创建了虚拟串行通信端口 .
但是,由于我们在
CreateFile()
中使用USB路径,因此USB样本测试应用程序不需要进行任何更改 .Further testing with POS Ready 7 using three USB to Serial converter cables 显示不同供应商的电缆在USB路径中具有相同的供应商ID和产品代码 . USB路径也根据插入电缆的USB端口而改变 . 在某些情况下,只有路径名中的最后一位数字不同 . 一个有趣的实验是,如果USB集线器连接到USB端口,然后USB连接到集线器,那么路径名称是什么样的呢?
你混淆了两个问题,我们可能不可能将它们区分开来 .
我这样说是因为你将
ReadFile
问题链接到设备名称 . 但是,ReadFile
适用于 HANDLE . 获取名称并将其转换为HANDLE
的函数称为CreateFile
. 这意味着ReadFile
甚至不知道它的名称是什么 .这种误解也解释了一些其他行为 . 例如,拔下设备时,
HANDLE
变为无效,并且保持无效 . 重新插入设备可能会恢复名称,但不能恢复HANDLE
.