C# USB转串口编程 - 查找COM口

参考文档

https://www.cnblogs.com/catzhou/archive/2018/06/08/9156863.html

C#怎么获取已知USB设备驱动信息(请看内容) (microsoft.com)

遍历Windows设备树的几种方法_飞鹤的程序员人生-CSDN博客

目录

1. 新建类SetupAPI.cs,引用SetupAPI.dll中的操作

1.1 API函数SetupDiGetClassDevs

1.2. API函数SetupDiEnumDeviceInfo

1.3 API函数SetupDiGetDeviceInstanceId

1.4 API函数SetupDiGetDeviceRegistryProperty

2. 新建类USB2SerialPort,继承System.IO.Ports.SerialPort

2.1 API函数GetCom

2.2.1 生成VID和PID字符串,后面会用这个字符串过滤COM口

2.2.2 调用SetupDiGetClassDevs获取全部类别的所有已安装设备的信息

2.2.3 通过SetupDiEnumDeviceInfo遍历所有设备,找到COM端口号

2.2.4 遍历完成后释放设备  

2.2 API函数GetInfo

2.2.1 前面与GetCom一样,到遍历所有设备,这里需要遍历2遍,第一遍是遍历到Class为“USB”,去获取设备的父系设备。

2.2.2 第二遍遍历和GetCom类似,先找到对应VID和PID的COM口,然后读取各个信息


1. 新建类SetupAPI.cs,引用SetupAPI.dll中的操作

1.1 API函数SetupDiGetClassDevs

[DllImport("SetupAPI.dll")]
public static extern IntPtr SetupDiGetClassDevs(
    ref Guid ClassGuid,
    UInt32 Enumerator,
    IntPtr hwndParent,
    UInt32 Flags
);

最后一个参数Flags的定义:

public const UInt32 DIGCF_DEFAULT = 0x00000001;  // only valid with DIGCF_DEVICEINTERFACE
public const UInt32 DIGCF_PRESENT = 0x00000002;
public const UInt32 DIGCF_ALLCLASSES = 0x00000004;
public const UInt32 DIGCF_PROFILE = 0x00000008;
public const UInt32 DIGCF_DEVICEINTERFACE = 0x00000010;

调用例程:

// 获取全部类别的所有已安装设备的信息
UInt32 dwFlag = (SetupAPI.DIGCF_ALLCLASSES | SetupAPI.DIGCF_PRESENT);
Guid usbGuid = Guid.Empty;
hDevInfo = SetupAPI.SetupDiGetClassDevs(ref usbGuid, 0, IntPtr.Zero, dwFlag);

1.2. API函数SetupDiEnumDeviceInfo

[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)]
public struct SP_DEVINFO_DATA
{
    public int cbSize;
    public Guid ClassGuid;
    public IntPtr DevInst;
    public IntPtr Reserved;
}

[DllImport("SetupAPI.dll")]
public static extern Boolean SetupDiEnumDeviceInfo(
    IntPtr DeviceInfoSet, 
    UInt32 MemberIndex, 
    ref SP_DEVINFO_DATA DeviceInfoData
);

注意这里的SP_DEVINFO_DATA结构需要按照上面的方式声明,否则调用SetupDiGetClassDevs会返回False。SetupDiEnumDeviceInfo是获取DeviceInfoData信息。

1.3 API函数SetupDiGetDeviceInstanceId

[DllImport("SetupAPI.dll")]
public static extern Boolean SetupDiGetDeviceInstanceId(
    IntPtr DeviceInfoSet,
    ref SP_DEVINFO_DATA DeviceInfoData,
    byte[] DeviceInstanceId, 
    UInt32 DeviceInstanceIdSize,
    ref UInt32 RequiredSize
);

调用例程:

// 获取ID
if (!SetupAPI.SetupDiGetDeviceInstanceId(hDevInfo, ref sDevInfoData, PropertyBuffer, (UInt32)PropertyBuffer.Length, ref nSize))
{
    continue;
}
strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer);

1.4 API函数SetupDiGetDeviceRegistryProperty

[DllImport("SetupAPI.dll", CharSet = CharSet.Ansi)]
public static extern Boolean SetupDiGetDeviceRegistryProperty(
    IntPtr DeviceInfoSet,
    ref SP_DEVINFO_DATA DeviceInfoData,
    UInt32 Property,
    ref UInt32 PropertyRegDataType,
    byte[] PropertyBuffer,
    UInt32 PropertyBufferSize,
    ref UInt32 RequiredSize
);

调用例程:

//读取Hardware ID            
byte[] PropertyBuffer = new byte[260];
UInt32 PropertyRegDataType = 0;
SetupAPI.SetupDiGetDeviceRegistryProperty(
    hDevInfo,
    ref sDevInfoData,
    SetupAPI.SPDRP_HARDWAREID,
    ref PropertyRegDataType, PropertyBuffer,
    (UInt32)PropertyBuffer.Length,
    ref nSize);
strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer);

2. 新建类USB2SerialPort,继承System.IO.Ports.SerialPort

public class USB2SerialPort:System.IO.Ports.SerialPort
{

}

2.1 API函数GetCom

public static eUSB2SerialPortStatus GetCom(
    UInt16 vid,
    UInt16 pid,
    byte[] port
)

作用:找到对应VID和PID的串口COM编号。

参数:vid和pid分别是USB转串口芯片的VID和PID,为0时表示该过滤条件为空;port是返回的COM编号数组。

2.2.1 生成VID和PID字符串,后面会用这个字符串过滤COM口

string strVidPid; //= string.Format("VID_{0:X4}&PID_{1:X4}", vid, pid);
string strVid, strPid;
if (vid == 0)
    strVid = "";
else
    strVid = string.Format("VID_{0:X4}", vid);
if (pid == 0)
    strPid = "";
else
    strPid = string.Format("PID_{0:X4}", pid);
strVidPid = strVid + "&" + strPid;
strVidPid.ToUpper();

2.2.2 调用SetupDiGetClassDevs获取全部类别的所有已安装设备的信息

IntPtr hDevInfo = SetupAPI.INVALID_HANDLE_VALUE;
UInt32 dwFlag = (SetupAPI.DIGCF_ALLCLASSES | SetupAPI.DIGCF_PRESENT);
Guid usbGuid = Guid.Empty;
hDevInfo = SetupAPI.SetupDiGetClassDevs(ref usbGuid, 0, IntPtr.Zero, dwFlag);
if (SetupAPI.INVALID_HANDLE_VALUE == hDevInfo)
    return eUSB2SerialPortStatus.NO_DEVICE;

2.2.3 通过SetupDiEnumDeviceInfo遍历所有设备,找到COM端口号

sDevInfoData.cbSize = Marshal.SizeOf(new SetupAPI.SP_DEVINFO_DATA());
sDevInfoData.ClassGuid = Guid.Empty;
sDevInfoData.DevInst = IntPtr.Zero;
sDevInfoData.Reserved = IntPtr.Zero;

for (UInt32 i = 0; SetupAPI.SetupDiEnumDeviceInfo(hDevInfo, i, ref sDevInfoData); i++)
{

}

2.2.3.1 通过SetupDiGetDeviceRegistryProperty读取设备的Class,这里数组PropertyBuffer只定义了4096个字节,更好的方式是调用2次SetupDiGetDeviceRegistryProperty获取实际数据的长度。

byte[] PropertyBuffer = new byte[4096];
UInt32 PropertyRegDataType = 0;
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty
(
    hDevInfo,
    ref sDevInfoData,
    SetupAPI.SPDRP_CLASS,
    ref PropertyRegDataType, PropertyBuffer,
    (UInt32)PropertyBuffer.Length,
    ref nSize);
    if(nSize > 0)
        strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1
);

2.2.3.2找到Class为“PORTS”的设备

if(strTemp.ToUpper() == "PORTS")
{
}

2.2.3.3读取该设备的友好名称,找到设备的COM端口号

nSize = 0;
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty(hDevInfo, ref sDevInfoData,
    SetupAPI.SPDRP_FRIENDLYNAME,
    ref PropertyRegDataType, PropertyBuffer,
    (UInt32)PropertyBuffer.Length,
    ref nSize);

// "XXX Virtual Com Port (COM?)"
if (nSize > 0)
    strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);
strTemp = strTemp.ToUpper();

int nStart = -1;
int nEnd = -1;
// 寻找串口信息
nStart = strTemp.IndexOf("(COM");
nEnd = strTemp.IndexOf(")");

if (nStart > -1 && nEnd > -1)
{
    strTemp = strTemp.Substring(nStart + 4, nEnd - nStart - 4);
    comPort = byte.Parse(strTemp);
}
else
    continue;

2.2.3.4 读取该设备的Hardware ID,判断VID和PID是否一致,如果一致则将COM编号保存到port数组中。


Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty(
    hDevInfo,
    ref sDevInfoData,
    SetupAPI.SPDRP_HARDWAREID,
    ref PropertyRegDataType, PropertyBuffer,
    (UInt32)PropertyBuffer.Length,
    ref nSize);
if (nSize > 0)
    strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);

// 根据设备信息寻找VID PID一致的设备
strTemp.ToUpper();
if (strTemp.IndexOf(strVidPid) == -1)
{
    continue;
}

port[n] = comPort;
n++;

2.2.4 遍历完成后释放设备  

SetupAPI.SetupDiDestroyDeviceInfoList(hDevInfo);

2.2 API函数GetInfo

public static eUSB2SerialPortStatus GetInfo(
    UInt16 vid,
    UInt16 pid,
    ref stUsb2UartDevice[] pstDev
)

获取USB转串口设备的详细信息,包括供应商,序列号,描述符等。

参数:vid和pid分别是USB转串口芯片的VID和PID,为0时表示该过滤条件为空;pstDev是返回的详细信息,其定义如下:

public struct stUsb2UartDevice
{
    public byte port;
    public byte usbIF;
    public string ParentSN;
    public string Manufacturer;
    public string DeviceDsc;
    public string SerialNumber;
};

其中port表示该设备的COM编号;usbIF是设备是第几个Interface;ParentSN是设备父系的序列号;Manufacturer是供应商;DeviceDsc是设备的描述符;SerialNumber是设备的序列号。

2.2.1 前面与GetCom一样,到遍历所有设备,这里需要遍历2遍,第一遍是遍历到Class为“USB”,去获取设备的父系设备。

SetupAPI.SP_DEVINFO_DATA sDevInfoData = new SetupAPI.SP_DEVINFO_DATA();
sDevInfoData.cbSize = Marshal.SizeOf(new SetupAPI.SP_DEVINFO_DATA());
sDevInfoData.ClassGuid = Guid.Empty;
sDevInfoData.DevInst = IntPtr.Zero;
sDevInfoData.Reserved = IntPtr.Zero;
for (UInt32 i = 0; SetupAPI.SetupDiEnumDeviceInfo(hDevInfo, i, ref sDevInfoData); i++)
{

}

2.2.1.1 通过SetupDiGetDeviceRegistryProperty获取设备的Class,判读是否是“USB”设备

//读取Class
byte[] PropertyBuffer = new byte[1024];
UInt32 PropertyRegDataType = 0;
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty
(
    hDevInfo,
    ref sDevInfoData,
    SetupAPI.SPDRP_CLASS,
    ref PropertyRegDataType, PropertyBuffer,
    (UInt32)PropertyBuffer.Length,
    ref nSize);
if (nSize > 0)
    strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);

if (strTemp.ToUpper() == "USB")
{

}

2.2.1.2 通过SetupDiGetDeviceInstanceId读入ID,这个ID包括VID、PID和SerialNumber,判断是否VID和PID一致

string szID = "";

nSize = 0;
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceInstanceId(hDevInfo, ref sDevInfoData, PropertyBuffer, (UInt32)PropertyBuffer.Length, ref nSize);
if (nSize > 0)
    szID = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);
szID.ToUpper();

if (szID.IndexOf(strVidPid) != -1)
{

}

2.2.1.3 VID和PID一致后读入设备的Location Path,保存在一个ArrayList中,等查找到Port时再看是否为父系。

nSize = 0;
string szPath = "";
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty(hDevInfo, ref sDevInfoData,
    SetupAPI.SPDRP_LOCATION_PATHS,
    ref PropertyRegDataType, PropertyBuffer,
    (UInt32)PropertyBuffer.Length,
    ref nSize);
if (nSize > 0)
    szPath = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);

usbParent.Path = szPath.Substring(0, szPath.IndexOf('\0'));
usbParent.SN = szID.Substring(szID.LastIndexOf(('\\')) + 1, szID.Length - szID.LastIndexOf(('\\')) - 1);
ParentList.Add(usbParent);

2.2.2 第二遍遍历和GetCom类似,先找到对应VID和PID的COM口,然后读取各个信息

2.2.2.1 读取USB设备的SerialNumber

Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
if (!SetupAPI.SetupDiGetDeviceInstanceId(hDevInfo, ref sDevInfoData, PropertyBuffer, (UInt32)PropertyBuffer.Length, ref nSize))
{
    continue;
}
if (nSize > 0)
    strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);
pstDev[n].SerialNumber = strTemp.Substring(strTemp.LastIndexOf(('\\')) + 1, strTemp.Length - strTemp.LastIndexOf(('\\')) - 1);

2.2.2.2 读取USB设备的Manufacturer

nSize = 0;
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty(hDevInfo, ref sDevInfoData,
    SetupAPI.SPDRP_MFG,
    ref PropertyRegDataType, PropertyBuffer,
    (UInt32)PropertyBuffer.Length,
    ref nSize);
if (nSize > 0)
    pstDev[n].Manufacturer = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);

2.2.2.3 读取USB设备的DeviceDsc

nSize = 0;
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty(hDevInfo, ref sDevInfoData,
    SetupAPI.SPDRP_DEVICEDESC,
    ref PropertyRegDataType, PropertyBuffer,
    (UInt32)PropertyBuffer.Length,
    ref nSize);
if (nSize > 0)
    pstDev[n].DeviceDsc = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);

2.2.2.3 读取USB设备的Location Path,然后判断这个Path是否在前面找到父系路径内(父系路径最长那个是最终的结果,所以要遍历所有的父系设备)

nSize = 0;
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty(hDevInfo, ref sDevInfoData,
    SetupAPI.SPDRP_LOCATION_PATHS,
    ref PropertyRegDataType, PropertyBuffer,
    (UInt32)PropertyBuffer.Length,
    ref nSize);
if (nSize > 0)
    strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);
                    
strTemp = strTemp.Substring(0, strTemp.IndexOf('\0'));
int parantLen = 0;
for(int j = 0; j < ParentList.Count; j++)
{
    stUSBParent parent = (stUSBParent)ParentList[j];
    if (strTemp.IndexOf(parent.Path) != -1 && parent.Path.Length > parantLen)
    {
        pstDev[n].ParentSN = parent.SN;
        parantLen = parent.Path.Length;
    }
}

2.2.2.4 通过关键字USBMI找到接口编号(似乎这个关键字并不是所有的USB转串口设备都有)

nStart = strTemp.IndexOf("#USBMI(");
nEnd = strTemp.LastIndexOf(")");

if (nStart > -1 && nEnd > -1)
{
    strTemp = strTemp.Substring(nStart + 7, nEnd - nStart - 7);
    pstDev[n].usbIF = byte.Parse(strTemp);
                        
}

另外通过读取SPDRP_SECURITY_SDS可以得到一个值,和USBMI的编号一致(不确定是否就是接口编号)。

nSize = 0;
Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
SetupAPI.SetupDiGetDeviceRegistryProperty(hDevInfo, ref sDevInfoData,
    SetupAPI.SPDRP_SECURITY_SDS,
    ref PropertyRegDataType, PropertyBuffer,
    (UInt32)PropertyBuffer.Length,
    ref nSize);
if (nSize > 0)
    strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, (int)nSize - 1);
                    

获取父系的方式不是通用的,对于FTDI的FT4232H来说,它的路径都在USB类里面,以下面的打印信息为例:

 Parent Path: PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(4)#USB(4)#USB(2)#USBMI(2)
 Parent Path: PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(4)#USB(4)#USB(2)
 Parent Path: PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(4)#USB(4)#USB(2)#USBMI(3)
 Parent Path: PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(4)#USB(4)#USB(2)#USBMI(0)
 Parent Path: PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(4)#USB(4)#USB(2)#USBMI(1)


得到的父系路径已经包括了子系路径,而PORTS类里面得到的路径是

LocationPath: FTDIBUS\VID_0403+PID_6011+FT9EF1UB\0000

但是以Path为方法还是可以得到设备的父子系关系,针对不同品牌的芯片方案可能需要调整自己的代码。

 

 

 

 

 

 

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值