编辑
2025-09-25
C#
00

目录

摘要
正文

摘要


写一个完整SerialPort Helper类,其实SerialPort功能封装已经很ok了。后面一个例子是闲着用MVC模式再写一把。

正文


模拟工具

image.png

C#
public class SerialPortHelper { public delegate void SerialPortCallback(object sender, string message); public event SerialPortCallback ReceiveEvent; SerialPort _serialPort; /// <summary> /// 返回所有Com Port /// </summary> public static List<string> ComPorts { get { return System.IO.Ports.SerialPort.GetPortNames().ToList(); } } public string PortName { get; set; } public int BaudRate { get; set; } public Parity Parity { get; set; } public int DataBits { get; set; } public StopBits StopBits { get; set; } public Handshake Handshake { get; set; } /// <summary> /// 接收数据类型 /// </summary> public ReceiveType DataType { get; set; } = ReceiveType.Hex; /// <summary> /// 接收数据的编码格式 /// </summary> public System.Text.Encoding DataEncoding { get; set; } = Encoding.UTF8; /// <summary> /// 接收数据是的间隔,可以防止粘包 /// </summary> public int ReceiveInterval { get; set; } /// <summary> /// 接收数据长度 /// </summary> public int DataSize { get; set; } = 8; public enum ReceiveType { String, Hex } public SerialPortHelper() { } public SerialPortHelper(string portName, int baudRate, int dataBits, Parity parity = Parity.None , StopBits stopBits = StopBits.One, Handshake handshake = Handshake.None, ReceiveType dataType = ReceiveType.Hex) { this.PortName = portName; this.BaudRate = baudRate; this.Parity = parity; this.DataBits = dataBits; this.StopBits = stopBits; this.Handshake = handshake; this.DataType = dataType; Init(); } public void Init() { _serialPort = new SerialPort(); _serialPort.PortName = PortName; _serialPort.BaudRate = BaudRate; _serialPort.Parity = Parity; _serialPort.DataBits = DataBits; _serialPort.StopBits = StopBits; _serialPort.Handshake = Handshake; _serialPort.DataReceived += _serialPort_DataReceived; } public bool Open() { try { if (_serialPort != null && !_serialPort.IsOpen) { _serialPort.Open(); return true; } return false; } catch { return false; } } private void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { byte[] b = new byte[DataSize]; int A = _serialPort.Read(b, 0, b.Length); string s = ""; if (DataType == ReceiveType.Hex) { s = ByteToHex(b); } else { s = DataEncoding.GetString(b); } if (ReceiveInterval > 0) { Thread.Sleep(ReceiveInterval); } if (ReceiveEvent != null) { ReceiveEvent(_serialPort, s); } } public void Close() { if (_serialPort != null && _serialPort.IsOpen) { _serialPort.DataReceived -= _serialPort_DataReceived; _serialPort.Close(); } } private string ByteToHex(byte[] Bytes) { string str = string.Empty; foreach (byte Byte in Bytes) { str += String.Format("{0:X2}", Byte) + " "; } return str.Trim(); } /// <summary> /// 发送数据 /// </summary> /// <param name="value"></param> public bool SendData(string value) { if (_serialPort == null || !_serialPort.IsOpen) { return false; } try { _serialPort.Write(value); return true; } catch { return false; } } }

继续改进,我们发现这里是不是可以继承SerialPort,这样更简单些吧?

C#
public class SerialPortHelper : SerialPort { public event Action<object, string> ReceiveEvent; /// <summary> /// 返回所有Com Port /// </summary> public static List<string> ComPorts { get { return System.IO.Ports.SerialPort.GetPortNames().ToList(); } } /// <summary> /// 接收数据类型 /// </summary> public ReceiveType DataType { get; set; } = ReceiveType.Hex; /// <summary> /// 接收数据的编码格式 /// </summary> public System.Text.Encoding DataEncoding { get; set; } = Encoding.UTF8; /// <summary> /// 接收数据是的间隔,可以防止粘包 /// </summary> public int ReceiveInterval { get; set; } /// <summary> /// 接收数据长度 /// </summary> public int DataSize { get; set; } = 8; public enum ReceiveType { String, Hex } public SerialPortHelper() { } public SerialPortHelper(string portName, int baudRate, int dataBits, Parity parity = Parity.None , StopBits stopBits = StopBits.One, Handshake handshake = Handshake.None, ReceiveType dataType = ReceiveType.Hex) { this.PortName = portName; this.BaudRate = baudRate; this.Parity = parity; this.DataBits = dataBits; this.StopBits = stopBits; this.Handshake = handshake; this.DataType = dataType; } public bool Open() { try { if (!this.IsOpen) { this.DataReceived += _serialPort_DataReceived; base.Open(); return true; } return false; } catch { return false; } } private void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { byte[] b = new byte[DataSize]; int A = this.Read(b, 0, b.Length); string s = ""; if (DataType == ReceiveType.Hex) { s = ByteToHex(b); } else { s = DataEncoding.GetString(b); } if (ReceiveInterval > 0) { Thread.Sleep(ReceiveInterval); } if (ReceiveEvent != null) { ReceiveEvent(this, s); } } public void Close() { if (this.IsOpen) { this.DataReceived -= _serialPort_DataReceived; base.Close(); } } private string ByteToHex(byte[] Bytes) { string str = string.Empty; foreach (byte Byte in Bytes) { str += String.Format("{0:X2}", Byte) + " "; } return str.Trim(); } /// <summary> /// 发送数据 /// </summary> /// <param name="value"></param> public bool SendData(string value) { if (!this.IsOpen) { return false; } try { this.Write(value); return true; } catch { return false; } } }

我们再试一下,我不想有Event,直接用Action是不是也可以,这个就是回调

C#
public Action<object, string> ReceiveAction;

修改一下Receive事件

C#
if (ReceiveAction != null) { ReceiveAction(this, s); }

这个调用变成

C#
serialPort.ReceiveAction = new Action<object, string>((obj, msg) => { this.Invoke(new Action(() => { txtReceive.AppendText(System.Environment.NewLine + msg); })); });

来个MVC模式

创建Model - SerialPortHelper

在C#中使用SerialPort类时,PinChanged事件可以用于检测某些串口信号的变化,例如CD (Carrier Detect), CTS (Clear to Send), DSR (Data Set Ready), RI (Ring Indicator)等。然而,PinChanged事件并不能直接用于检测服务端连接的断开,因为串口通信本质上是点对点的物理连接,没有明确的“服务端”和“客户端”概念。

不过,您可以通过检测DCD (Data Carrier Detect)信号的变化来间接判断连接状态。DCD信号通常用于表示调制解调器连接状态,但具体取决于您的硬件和应用场景。

C#
using System; using System.Collections.Generic; using System.IO.Ports; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SerialPortTools { public class SerialPortEventArge : EventArgs { public bool isOpened { get; set; } = false; public string Message { get; set; } = ""; } public delegate void SerialPortEventHandler(object sender, SerialPortEventArge e); public class SerialPortHelper : SerialPort { public event SerialPortEventHandler OpenEvent = null; public event SerialPortEventHandler CloseEvent = null; public event SerialPortEventHandler ReceiveDataEvent = null; public event SerialPortEventHandler DisconnectEvent = null; public static List<string> ComPorts { get { return SerialPort.GetPortNames().ToList(); } } public ReceiveType DataType { get; set; } = ReceiveType.Hex; public enum ReceiveType { String, Hex } public SerialPortHelper() { } public SerialPortHelper(string portName, int baudRate, int dataBits, Parity parity, StopBits stopBits = StopBits.None , Handshake handshake = Handshake.None, ReceiveType dataType = ReceiveType.Hex) { this.PortName = portName; this.BaudRate = baudRate; this.DataBits = dataBits; this.Parity = parity; this.StopBits = stopBits; this.Handshake = handshake; this.DataType = dataType; } private void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { byte[] b = new byte[8]; int A = this.Read(b, 0, 8); string s = ""; if (DataType == ReceiveType.Hex) { s = ByteToHex(b); } else { s = System.Text.Encoding.Default.GetString(b); } if (ReceiveDataEvent != null) { ReceiveDataEvent.Invoke(this, new SerialPortEventArge() { isOpened = true, Message = s }); } } new public void Open() { try { if (!this.IsOpen) { this.DataReceived += _serialPort_DataReceived; this.PinChanged += SerialPortHelper_PinChanged; base.Open(); } } catch (Exception ex) { //log } if (OpenEvent != null) { OpenEvent.Invoke(this, new SerialPortEventArge() { isOpened = this.IsOpen, Message = "打开" }); } } /// <summary> /// 检测某些串口信号的变化,例如CD (Carrier Detect), CTS (Clear to Send), DSR (Data Set Ready), RI (Ring Indicator) /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void SerialPortHelper_PinChanged(object sender, SerialPinChangedEventArgs e) { SerialPort sp = (SerialPort)sender; if (e.EventType == SerialPinChange.CDChanged) { bool isConnected = sp.CDHolding; if (!isConnected) { if (DisconnectEvent != null) { DisconnectEvent(this, new SerialPortEventArge() { isOpened = false, Message = "目标断开" }); } } } } new public void Close() { try { if (this.IsOpen) { this.DataReceived -= _serialPort_DataReceived; base.Close(); } } catch (Exception ex) { //log } if (CloseEvent != null) { CloseEvent(this, new SerialPortEventArge() { isOpened = this.IsOpen, Message = "关闭" }); } } public bool SendData(string value) { if (!this.IsOpen) { return false; } try { this.Write(value); return true; } catch { return false; } } private string ByteToHex(byte[] Bytes) { string str = string.Empty; foreach (byte Byte in Bytes) { str += String.Format("{0:X2}", Byte) + " "; } return str.Trim(); } } }

定义一个接口,主要是绑定事件IView

C#
internal interface IView { void OpenComEvent(Object sender, SerialPortEventArgs e); void CloseComEvent(Object sender, SerialPortEventArgs e); void ReceiveDataEvent(Object sender, SerialPortEventArgs e); void DisconnectEvent(object sender, SerialPortEventArge e); }

定义一个Controller ,这里做交互用的

C#
internal class Controller { SerialPortHelper serialModel = new SerialPortHelper(); IView view; public Controller(IView view) { serialModel.OpenEvent += new SerialPortEventHandler(view.OpenComEvent); serialModel.CloseEvent += new SerialPortEventHandler(view.CloseComEvent); serialModel.ReceiveDataEvent += new SerialPortEventHandler(view.ReceiveDataEvent); } public void OpenSerialPort(string portName, int baudRate, int dataBits, Parity parity = Parity.None , StopBits stopBits = StopBits.One, Handshake handshake = Handshake.None, ReceiveType dataType = ReceiveType.Hex { if(serialModel.IsOpen) { serialModel.Close(); } serialModel.PortName=portName; serialModel.BaudRate=baudRate; serialModel.DataBits=dataBits; serialModel.Parity=parity; serialModel.StopBits=stopBits; serialModel.Handshake=handshake; serialModel.DataType=dataType; serialModel.Open(); } public void CloseSerialPort() { if (serialModel.IsOpen) { serialModel.Close(); } } public void ChangeDataType(ReceiveType dataType) { serialModel.DataType=dataType; } public void SendData(string value) { serialModel.SendData(value); } }

界面设计,继承这个IView

C#
public partial class FrmMain : Form, IView { private Controller controller; public FrmMain() { InitializeComponent(); Init(); System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); controller = new Controller(this); } private void LoadPorts() { cboPort.Items.Clear(); foreach (var item in SerialPortHelper.ComPorts) { cboPort.Items.Add(item); } cboPort.SelectedIndex = 0; } private void Init() { LoadPorts(); object[] baudRate = { 4800, 9600, 19200, 38400, 57600, 115200, 230400 }; cboBaudRate.Items.AddRange(baudRate); cboBaudRate.SelectedIndex = 1; cboDataBits.Items.Add(7); cboDataBits.Items.Add(8); cboDataBits.SelectedIndex = 1; cboStopBits.Items.Add("One"); cboStopBits.Items.Add("OnePointFive"); cboStopBits.Items.Add("Two"); cboStopBits.SelectedIndex = 0; //Parity cboParity.Items.Add("None"); cboParity.Items.Add("Even"); cboParity.Items.Add("Mark"); cboParity.Items.Add("Odd"); cboParity.Items.Add("Space"); cboParity.SelectedIndex = 0; cboHandshaking.Items.Add("None"); cboHandshaking.Items.Add("XOnXOff"); cboHandshaking.Items.Add("RequestToSend"); cboHandshaking.Items.Add("RequestToSendXOnXOff"); cboHandshaking.SelectedIndex = 0; btnClosePort.Enabled = false; } private void btnRefresh_Click(object sender, EventArgs e) { LoadPorts(); } private void btnOpenPort_Click(object sender, EventArgs e) { controller.OpenSerialPort(cboPort.Text, int.Parse(cboBaudRate.Text), int.Parse(cboDataBits.Text), (Parity)Enum.Parse(typeof(Parity), cboParity.Text), (StopBits)Enum.Parse(typeof(StopBits), cboStop (Handshake)Enum.Parse(typeof(Handshake), cboHandshaking.Text), rdoHex.Checked ? SerialPortHelper.R } private void btnClosePort_Click(object sender, EventArgs e) { controller.CloseSerialPort(); } private void btnSend_Click(object sender, EventArgs e) { controller.SendData(txtSend.Text); } private void rdoHex_CheckedChanged(object sender, EventArgs e) { controller.ChangeDataType(SerialPortHelper.ReceiveType.Hex); } private void rdoString_CheckedChanged(object sender, EventArgs e) { controller.ChangeDataType(SerialPortHelper.ReceiveType.String); } public void OpenComEvent(object sender, SerialPortEventArgs e) { if (e.isOpend) { stsMain_lblStatus.Text = "端口已打开"; btnOpenPort.Enabled = false; btnClosePort.Enabled = true; } else { stsMain_lblStatus.Text = "端口出错"; btnOpenPort.Enabled = true; btnClosePort.Enabled = false; } } public void CloseComEvent(object sender, SerialPortEventArgs e) { if (!e.isOpend) { stsMain_lblStatus.Text = "端口已关闭"; btnOpenPort.Enabled = true; btnClosePort.Enabled = false; } else { stsMain_lblStatus.Text = "端口关闭出错"; btnOpenPort.Enabled = false; btnClosePort.Enabled = true; } } public void ReceiveDataEvent(object sender, SerialPortEventArgs e) { this.Invoke(new Action(() => { txtReceive.AppendText(System.Environment.NewLine + e.Message); txtReceive.ScrollToCaret(); })); } /// <summary> /// 目标断开 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> public void DisconnectEvent(object sender, SerialPortEventArge e) { this.Invoke(new Action(() => { txtReceive.AppendText(System.Environment.NewLine + e.Message); txtReceive.ScrollToCaret(); })); } }

image.png

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!