小言_互联网的博客

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

278人阅读  评论(0)

参考文档

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


  
  1. [ DllImport("SetupAPI.dll")]
  2. public static extern IntPtr SetupDiGetClassDevs(
  3. ref Guid ClassGuid,
  4. UInt32 Enumerator,
  5. IntPtr hwndParent,
  6. UInt32 Flags
  7. );

最后一个参数Flags的定义:


  
  1. public const UInt32 DIGCF_DEFAULT = 0x00000001; // only valid with DIGCF_DEVICEINTERFACE
  2. public const UInt32 DIGCF_PRESENT = 0x00000002;
  3. public const UInt32 DIGCF_ALLCLASSES = 0x00000004;
  4. public const UInt32 DIGCF_PROFILE = 0x00000008;
  5. public const UInt32 DIGCF_DEVICEINTERFACE = 0x00000010;

调用例程:


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

1.2. API函数SetupDiEnumDeviceInfo


  
  1. [ StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)]
  2. public struct SP_DEVINFO_DATA
  3. {
  4. public int cbSize;
  5. public Guid ClassGuid;
  6. public IntPtr DevInst;
  7. public IntPtr Reserved;
  8. }
  9. [ DllImport("SetupAPI.dll")]
  10. public static extern Boolean SetupDiEnumDeviceInfo(
  11. IntPtr DeviceInfoSet,
  12. UInt32 MemberIndex,
  13. ref SP_DEVINFO_DATA DeviceInfoData
  14. );

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

1.3 API函数SetupDiGetDeviceInstanceId


  
  1. [ DllImport("SetupAPI.dll")]
  2. public static extern Boolean SetupDiGetDeviceInstanceId(
  3. IntPtr DeviceInfoSet,
  4. ref SP_DEVINFO_DATA DeviceInfoData,
  5. byte[] DeviceInstanceId,
  6. UInt32 DeviceInstanceIdSize,
  7. ref UInt32 RequiredSize
  8. );

调用例程:


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

1.4 API函数SetupDiGetDeviceRegistryProperty


  
  1. [ DllImport("SetupAPI.dll", CharSet = CharSet.Ansi)]
  2. public static extern Boolean SetupDiGetDeviceRegistryProperty(
  3. IntPtr DeviceInfoSet,
  4. ref SP_DEVINFO_DATA DeviceInfoData,
  5. UInt32 Property,
  6. ref UInt32 PropertyRegDataType,
  7. byte[] PropertyBuffer,
  8. UInt32 PropertyBufferSize,
  9. ref UInt32 RequiredSize
  10. );

调用例程:


  
  1. //读取Hardware ID
  2. byte[] PropertyBuffer = new byte[ 260];
  3. UInt32 PropertyRegDataType = 0;
  4. SetupAPI.SetupDiGetDeviceRegistryProperty(
  5. hDevInfo,
  6. ref sDevInfoData,
  7. SetupAPI.SPDRP_HARDWAREID,
  8. ref PropertyRegDataType, PropertyBuffer,
  9. (UInt32)PropertyBuffer.Length,
  10. ref nSize);
  11. strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer);

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


  
  1. public class USB2SerialPort: System.IO.Ports.SerialPort
  2. {
  3. }

2.1 API函数GetCom


  
  1. public static eUSB2SerialPortStatus GetCom(
  2. UInt16 vid,
  3. UInt16 pid,
  4. byte[] port
  5. )

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

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

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


  
  1. string strVidPid; //= string.Format("VID_{0:X4}&PID_{1:X4}", vid, pid);
  2. string strVid, strPid;
  3. if (vid == 0)
  4. strVid = "";
  5. else
  6. strVid = string.Format( "VID_{0:X4}", vid);
  7. if (pid == 0)
  8. strPid = "";
  9. else
  10. strPid = string.Format( "PID_{0:X4}", pid);
  11. strVidPid = strVid + "&" + strPid;
  12. strVidPid.ToUpper();

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


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

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


  
  1. sDevInfoData.cbSize = Marshal.SizeOf( new SetupAPI.SP_DEVINFO_DATA());
  2. sDevInfoData.ClassGuid = Guid.Empty;
  3. sDevInfoData.DevInst = IntPtr.Zero;
  4. sDevInfoData.Reserved = IntPtr.Zero;
  5. for (UInt32 i = 0; SetupAPI.SetupDiEnumDeviceInfo(hDevInfo, i, ref sDevInfoData); i++)
  6. {
  7. }

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


  
  1. byte[] PropertyBuffer = new byte[ 4096];
  2. UInt32 PropertyRegDataType = 0;
  3. Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
  4. SetupAPI.SetupDiGetDeviceRegistryProperty
  5. (
  6. hDevInfo,
  7. ref sDevInfoData,
  8. SetupAPI.SPDRP_CLASS,
  9. ref PropertyRegDataType, PropertyBuffer,
  10. (UInt32)PropertyBuffer.Length,
  11. ref nSize);
  12. if(nSize > 0)
  13. strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, ( int)nSize - 1
  14. );

2.2.3.2找到Class为“PORTS”的设备


  
  1. if(strTemp.ToUpper() == "PORTS")
  2. {
  3. }

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


  
  1. nSize = 0;
  2. Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
  3. SetupAPI.SetupDiGetDeviceRegistryProperty(hDevInfo, ref sDevInfoData,
  4. SetupAPI.SPDRP_FRIENDLYNAME,
  5. ref PropertyRegDataType, PropertyBuffer,
  6. (UInt32)PropertyBuffer.Length,
  7. ref nSize);
  8. // "XXX Virtual Com Port (COM?)"
  9. if (nSize > 0)
  10. strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, ( int)nSize - 1);
  11. strTemp = strTemp.ToUpper();
  12. int nStart = -1;
  13. int nEnd = -1;
  14. // 寻找串口信息
  15. nStart = strTemp.IndexOf( "(COM");
  16. nEnd = strTemp.IndexOf( ")");
  17. if (nStart > -1 && nEnd > -1)
  18. {
  19. strTemp = strTemp.Substring(nStart + 4, nEnd - nStart - 4);
  20. comPort = byte.Parse(strTemp);
  21. }
  22. else
  23. continue;

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


  
  1. Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
  2. SetupAPI.SetupDiGetDeviceRegistryProperty(
  3. hDevInfo,
  4. ref sDevInfoData,
  5. SetupAPI.SPDRP_HARDWAREID,
  6. ref PropertyRegDataType, PropertyBuffer,
  7. (UInt32)PropertyBuffer.Length,
  8. ref nSize);
  9. if (nSize > 0)
  10. strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, ( int)nSize - 1);
  11. // 根据设备信息寻找VID PID一致的设备
  12. strTemp.ToUpper();
  13. if (strTemp.IndexOf(strVidPid) == -1)
  14. {
  15. continue;
  16. }
  17. port[n] = comPort;
  18. n++;

2.2.4 遍历完成后释放设备  

SetupAPI.SetupDiDestroyDeviceInfoList(hDevInfo);

2.2 API函数GetInfo


  
  1. public static eUSB2SerialPortStatus GetInfo(
  2. UInt16 vid,
  3. UInt16 pid,
  4. ref stUsb2UartDevice[] pstDev
  5. )

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

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


  
  1. public struct stUsb2UartDevice
  2. {
  3. public byte port;
  4. public byte usbIF;
  5. public string ParentSN;
  6. public string Manufacturer;
  7. public string DeviceDsc;
  8. public string SerialNumber;
  9. };

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

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


  
  1. SetupAPI.SP_DEVINFO_DATA sDevInfoData = new SetupAPI.SP_DEVINFO_DATA();
  2. sDevInfoData.cbSize = Marshal.SizeOf( new SetupAPI.SP_DEVINFO_DATA());
  3. sDevInfoData.ClassGuid = Guid.Empty;
  4. sDevInfoData.DevInst = IntPtr.Zero;
  5. sDevInfoData.Reserved = IntPtr.Zero;
  6. for (UInt32 i = 0; SetupAPI.SetupDiEnumDeviceInfo(hDevInfo, i, ref sDevInfoData); i++)
  7. {
  8. }

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


  
  1. //读取Class
  2. byte[] PropertyBuffer = new byte[ 1024];
  3. UInt32 PropertyRegDataType = 0;
  4. Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
  5. SetupAPI.SetupDiGetDeviceRegistryProperty
  6. (
  7. hDevInfo,
  8. ref sDevInfoData,
  9. SetupAPI.SPDRP_CLASS,
  10. ref PropertyRegDataType, PropertyBuffer,
  11. (UInt32)PropertyBuffer.Length,
  12. ref nSize);
  13. if (nSize > 0)
  14. strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, ( int)nSize - 1);
  15. if (strTemp.ToUpper() == "USB")
  16. {
  17. }

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


  
  1. string szID = "";
  2. nSize = 0;
  3. Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
  4. Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
  5. SetupAPI.SetupDiGetDeviceInstanceId(hDevInfo, ref sDevInfoData, PropertyBuffer, (UInt32)PropertyBuffer.Length, ref nSize);
  6. if (nSize > 0)
  7. szID = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, ( int)nSize - 1);
  8. szID.ToUpper();
  9. if (szID.IndexOf(strVidPid) != -1)
  10. {
  11. }

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


  
  1. nSize = 0;
  2. string szPath = "";
  3. Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
  4. SetupAPI.SetupDiGetDeviceRegistryProperty(hDevInfo, ref sDevInfoData,
  5. SetupAPI.SPDRP_LOCATION_PATHS,
  6. ref PropertyRegDataType, PropertyBuffer,
  7. (UInt32)PropertyBuffer.Length,
  8. ref nSize);
  9. if (nSize > 0)
  10. szPath = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, ( int)nSize - 1);
  11. usbParent.Path = szPath.Substring( 0, szPath.IndexOf( '\0'));
  12. usbParent.SN = szID.Substring(szID.LastIndexOf(( '\\')) + 1, szID.Length - szID.LastIndexOf(( '\\')) - 1);
  13. ParentList.Add(usbParent);

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

2.2.2.1 读取USB设备的SerialNumber


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

2.2.2.2 读取USB设备的Manufacturer


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

2.2.2.3 读取USB设备的DeviceDsc


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

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


  
  1. nSize = 0;
  2. Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
  3. SetupAPI.SetupDiGetDeviceRegistryProperty(hDevInfo, ref sDevInfoData,
  4. SetupAPI.SPDRP_LOCATION_PATHS,
  5. ref PropertyRegDataType, PropertyBuffer,
  6. (UInt32)PropertyBuffer.Length,
  7. ref nSize);
  8. if (nSize > 0)
  9. strTemp = System.Text.Encoding.Default.GetString(PropertyBuffer, 0, ( int)nSize - 1);
  10. strTemp = strTemp.Substring( 0, strTemp.IndexOf( '\0'));
  11. int parantLen = 0;
  12. for( int j = 0; j < ParentList.Count; j++)
  13. {
  14. stUSBParent parent = (stUSBParent)ParentList[j];
  15. if (strTemp.IndexOf(parent.Path) != -1 && parent.Path.Length > parantLen)
  16. {
  17. pstDev[n].ParentSN = parent.SN;
  18. parantLen = parent.Path.Length;
  19. }
  20. }

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


  
  1. nStart = strTemp.IndexOf( "#USBMI(");
  2. nEnd = strTemp.LastIndexOf( ")");
  3. if (nStart > -1 && nEnd > -1)
  4. {
  5. strTemp = strTemp.Substring(nStart + 7, nEnd - nStart - 7);
  6. pstDev[n].usbIF = byte.Parse(strTemp);
  7. }

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


  
  1. nSize = 0;
  2. Array.Clear(PropertyBuffer, 0, PropertyBuffer.Length);
  3. SetupAPI.SetupDiGetDeviceRegistryProperty(hDevInfo, ref sDevInfoData,
  4. SetupAPI.SPDRP_SECURITY_SDS,
  5. ref PropertyRegDataType, PropertyBuffer,
  6. (UInt32)PropertyBuffer.Length,
  7. ref nSize);
  8. if (nSize > 0)
  9. 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为方法还是可以得到设备的父子系关系,针对不同品牌的芯片方案可能需要调整自己的代码。

 

 

 

 

 

 


转载:https://blog.csdn.net/pq113_6/article/details/116272591
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场