有这样一个场景:使用特定品牌型号usb-serial时,插入不同的usb接口,或者有多个串口设备存在时,端口编号会互相干扰,无法固定下来,只能通过肉眼识别甚至插拔观察来进行串口区分。本文将演示如何在Windows下和Ubuntu下,通过USB的VID+PID来自动识别对应串口号。

首先介绍VID和PID:VID指的是Vendor ID,即厂商编号;PID指的是产品编号,用于区别不同设备。特定的VID代表特定的厂商,而特定的PID代表特定的设备,二者组合,系统识别设备即是通过这种唯一性组合。

某些情况下,我们会在一台工控设备上通过多个USB转TTL/RS232来接入多台设备。假定需要接入ABCD四个串口设备,我们可以采用四个不同型号的USB-Serial数据线做接入,这样即可以通过串口来唯一确定一个设备。虽然插入顺序、对应不同的USB接口均会使得端口编号发生变化,但由于提前知道了VID和PID,我们完全可以在代码里动态识别出端口号即可用正确的端口和设备通讯。

一、Windows

Windows中可以使用WMI查询获取到MSSerial_PortName集合,然后遍历每个设备的VID和PID(本例使用了FTDI的FT232转换线,VID0403,PID6001)是否匹配。此处不推荐WIN32_SerialPort,这个集合对usb设备支持很差,很多设备都无法识别,但如果需要遍历的全部是原生COM口,用WIN32_SerialPort也完全没问题。
MSSerial_PortName只包含InstanceName和PortName两个属性,如果希望得到更完整的设备信息,可以借助Win32_PnPEntity进行遍历。


using ManagementObjectSearcher searcher =
            new("root\\WMI", "SELECT * FROM MSSerial_PortName");
var devices = searcher.Get();
foreach (var device in devices)
{
    //var deviceID = (string)device.GetPropertyValue("InstanceName");
    //var portName = (string)device.GetPropertyValue("PortName");
    foreach (var prop in device.Properties)
    {
        Console.WriteLine(prop.Name + "\t" + prop.Value);
    }
}

二、Ubuntu

Windows系统里,我们可以借助Win32 API很轻松地查到串口详情,但是在Linux里缺少这样的支持,只能通过解析文件或命令结果来确认要找的串口。

具体的思路是:遍历所有串口,然后到串口目录里查询uevent文件,对应路径 /sys/bus/usb-serial/devices/ttyUSB0/../uevent(在我电脑上,它的实际路径是/sys/devices/pci0000:00/0000:00:15.0/0000:03:00.0/usb3/3-2/3-2:1.0/uevent)

这中间还存在一个软链接问题:/sys/bus/usb-serial/devices目录下,每个ttyUSBx对应有各自的目录,但这目录却是软链接,无法用System.IO.File.ReadAllText直接读取,因为路径会被错误地解析成/sys/bus/usb-serial/devices/uevent,所以只能借助Process类,调用cat grep之类的命令把文件内容取出来,捕获输出重定向内容。


foreach (var port in SerialPort.GetPortNames())
{
    string dev = Regex.Match(port, "(?<=.+/).*").Value;
    Console.WriteLine($"PORT\t{port}\t{dev}");
    // VID和PID保存在 /sys/bus/usb-serial/devices/ttyUSB0/../uevent
    string file = $"/sys/bus/usb-serial/devices/{dev}/../uevent";
    ProcessStartInfo startInfo =
        new("grep", $"PRODUCT {file}")
        {
            RedirectStandardOutput = true,
            UseShellExecute = false,
        };
    var proc = Process.Start(startInfo);
    string info = proc.StandardOutput.ReadToEnd();
    Console.WriteLine(Regex.Match(info, "(?<=^PRODUCT=).*$").Value);
}
分类: articles