.Net串口通讯中的若干问题(C#多串口硬件识别、热插拔、Close方法报错问题、IsOpen的可靠性问题)
一、需求场景
最近有时间静下心来研究SDK,串口通讯的。要求实现识别cp210x和cp2303驱动的两款硬件,并且2303的优先级高,即有2303识别之,没有再识别210x;要求实现热插拔,拔掉自动断开,插上自动连接。
二、问题一:如何实现串口硬件的识别呢?
1、如果方便的话,SerialPort的Handshake这个字段值得深入研究,可以利用这个实现;
2、添加自定义的握手协议,本人用一个5字节的串进行校验(第三位是硬件版本标识),校验算法如下:
for (int i = 0; i < btData.Length; i++) { if ((i % 5) == 0) commit = 0; if (btData[i].ToString().Equals(byteMitt[i % 5]) || i % 5 == 3) commit++; if (commit == 5) { SendMessage(EnumDataConverter.GetCommandStatus(SendCommandType.Test) + intDeviceCount.ToString().PadLeft(2, '0')); tpVersion = btData[3].ToString(); IsConnected = true; strDeviceName = tempPort.Value; return true; } } //校验完毕没有成功,进入下次循环
二、热插拔的监听问题
经过实践,cp210x可以在串口设备表中查询到,cp2303不可不发现,故而采用查询计算机设备表的方式
备注:
System.IO.Ports.SerialPort.GetPortNames()只能获取设备名,不能获取详细信息(类型等),我下面用来进行一些比对的逻辑操作
private void CreateUSBWatcher() { Ports = System.IO.Ports.SerialPort.GetPortNames(); //建立监听 ManagementScope scope = new ManagementScope("root\\CIMV2"); scope.Options.EnablePrivileges = true; //建立插入监听 try { WqlEventQuery USBInsertQuery = new WqlEventQuery("__InstanceCreationEvent", "TargetInstance ISA 'Win32_PnPEntity'"); USBInsertQuery.WithinInterval = new TimeSpan(0, 0, 2); USBInsert = new ManagementEventWatcher(scope, USBInsertQuery); USBInsert.EventArrived += USBInsert_EventArrived; USBInsert.Start(); } catch (Exception ex) { if (USBInsert != null) { USBInsert.Stop(); } throw ex; } //建立拔出监听 try { WqlEventQuery USBRemoveQuery = new WqlEventQuery("__InstanceDeletionEvent", "TargetInstance ISA 'Win32_PnPEntity'"); USBRemoveQuery.WithinInterval = new TimeSpan(0, 0, 2); USBRemove = new ManagementEventWatcher(scope, USBRemoveQuery); USBRemove.EventArrived += USBRemove_EventArrived; USBRemove.Start(); } catch (Exception ex) { if (USBRemove != null) { USBRemove.Stop(); } throw ex; } }
/// <summary> /// USB设备插入 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void USBInsert_EventArrived(object sender, EventArrivedEventArgs e) { string[] tempPorts = System.IO.Ports.SerialPort.GetPortNames(); if (tempPorts.Count() == Ports.Count()) return; else Ports = tempPorts; if (IsConnected) return; if (blnDesireConnected && Open()) commExecuteInterface?.DeviceArrivaled(); } /// <summary> /// USB设备拔出 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void USBRemove_EventArrived(object sender, EventArrivedEventArgs e) { string[] tempPorts = System.IO.Ports.SerialPort.GetPortNames(); if (tempPorts.Count() == Ports.Count()) return; else Ports = tempPorts; if (!IsConnected) return; IsConnected = false; spUSB.Close(); commExecuteInterface?.DeviceRemoved(); }
三、调用Close方法会提示“ unsafe handler 已关闭”
微软的方法有问题的,微软的SerialPort的这个类有问题,一但断开硬件的连接,会触发部分对象资源的释放(SerialPort不是单纯的托管资源,非托管资源被释放了),因此调用Close会出问题,除非此进程完全退出,所以SerialPort的资源需要重置;SerialPort的初始化方法中有一个是有IContainer参数的,IContainer提供了非托管资源的管理方法,因此,把SerialPort对象放到一个容器中可解决Close问题;即,想实现热插拔,必须把SerialPort放到容器中!
spUSB = new SerialPort(container); KeyValuePair<string, string> tempPort = queueSerialPorts.Dequeue(); spUSB.BaudRate = 115200; spUSB.PortName = tempPort.Key; spUSB.Open(); SendMessage(EnumDataConverter.GetCommandStatus(SendCommandType.ECC)); //尝试让主控机复位 byte[] btData = ReadData();
四、IsOpen属性并不可靠,尽量少用
有问题可以加qq群:568055323