C# 串口通信完整教程 (.NET Framework 4.0)
C# 串口通信完整教程 (.NET Framework 4.0)
适用于 Visual Studio 2010 / .NET Framework 4.0
目录
- 基础概念
- SerialPort类详解
- 多线程串口通信
- 超时处理机制
- 异常处理最佳实践
- 数据转换
- 多进程通信问题
- 完整示例代码
- 常见问题解决
基础概念
什么是串口通信
串口通信(Serial Communication)是一种串行数据传输方式,数据按位顺序传输。在工业控制、设备通信等领域广泛应用。
引用命名空间
using System;
using System.IO.Ports;
using System.Text;
using System.Threading;
using System.Collections.Generic;
SerialPort类详解
创建串口对象
// 方法1:无参构造
SerialPort serialPort = new SerialPort();// 方法2:指定端口名
SerialPort serialPort = new SerialPort("COM1");// 方法3:完整参数
SerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
重要属性配置
public class SerialPortConfig
{public void ConfigurePort(SerialPort port){// 端口名称(COM1, COM2 等)port.PortName = "COM1";// 波特率(常用:9600, 19200, 38400, 57600, 115200)port.BaudRate = 9600;// 数据位(5-8,常用8)port.DataBits = 8;// 停止位port.StopBits = StopBits.One; // None=0, One=1, Two=2, OnePointFive=1.5// 奇偶校验port.Parity = Parity.None; // None, Odd, Even, Mark, Space// 握手协议port.Handshake = Handshake.None; // None, XOnXOff, RequestToSend, RequestToSendXOnXOff// 读取超时(毫秒,-1表示无限等待)port.ReadTimeout = 500;// 写入超时(毫秒)port.WriteTimeout = 500;// 接收缓冲区大小(字节)port.ReadBufferSize = 4096;// 发送缓冲区大小(字节)port.WriteBufferSize = 2048;// 编码格式port.Encoding = Encoding.UTF8;// RTS(请求发送)信号port.RtsEnable = false;// DTR(数据终端就绪)信号port.DtrEnable = false;}
}
获取可用串口
public static string[] GetAvailablePorts()
{return SerialPort.GetPortNames();
}// 使用示例
string[] ports = GetAvailablePorts();
foreach (string port in ports)
{Console.WriteLine("可用串口: " + port);
}
多线程串口通信
为什么需要多线程
- 避免UI阻塞:串口读取可能等待数据,阻塞主线程会导致界面卡死
- 异步处理:发送和接收可以并行进行
- 数据缓冲:使用队列缓存待发送数据
基于事件的接收(推荐方式)
public class EventBasedSerialPort
{private SerialPort serialPort;private object lockObject = new object();public EventBasedSerialPort(string portName, int baudRate){serialPort = new SerialPort(portName, baudRate);serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);}// 数据接收事件处理(运行在独立线程)private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e){try{SerialPort sp = (SerialPort)sender;// 检查可用字节数int bytesToRead = sp.BytesToRead;if (bytesToRead > 0){byte[] buffer = new byte[bytesToRead];// 读取数据int bytesRead = sp.Read(buffer, 0, bytesToRead);// 处理接收到的数据ProcessReceivedData(buffer, bytesRead);}}catch (TimeoutException ex){Console.WriteLine("读取超时: " + ex.Message);}catch (Exception ex){Console.WriteLine("接收数据异常: " + ex.Message);}}private void ProcessReceivedData(byte[] data, int length){// 转换为十六进制字符串显示string hex = ByteArrayToHexString(data, length);Console.WriteLine("接收到数据(HEX): " + hex);// 转换为ASCII字符串string text = Encoding.ASCII.GetString(data, 0, length);Console.WriteLine("接收到数据(ASCII): " + text);}public void Open(){if (!serialPort.IsOpen){serialPort.Open();}}public void Close(){if (serialPort.IsOpen){serialPort.Close();}}private string ByteArrayToHexString(byte[] data, int length){StringBuilder sb = new StringBuilder(length * 3);for (int i = 0; i < length; i++){sb.Append(data[i].ToString("X2"));if (i < length - 1)sb.Append(" ");}return sb.ToString();}
}
基于线程池的发送队列
public class ThreadPoolSerialPort
{private SerialPort serialPort;private Queue<byte[]> sendQueue;private object queueLock = new object();private bool isProcessing = false;public ThreadPoolSerialPort(string portName, int baudRate){serialPort = new SerialPort(portName, baudRate);serialPort.WriteTimeout = 1000;sendQueue = new Queue<byte[]>();}public void Open(){if (!serialPort.IsOpen){serialPort.Open();}}public void Close(){if (serialPort.IsOpen){serialPort.Close();}}// 添加数据到发送队列public void QueueSend(byte[] data){lock (queueLock){sendQueue.Enqueue(data);// 如果没有正在处理的任务,启动处理if (!isProcessing){isProcessing = true;ThreadPool.QueueUserWorkItem(ProcessSendQueue);}}}// 线程池处理发送队列private void ProcessSendQueue(object state){while (true){byte[] dataToSend = null;lock (queueLock){if (sendQueue.Count > 0){dataToSend = sendQueue.Dequeue();}else{isProcessing = false;break;}}if (dataToSend != null){SendData(dataToSend);}}}// 实际发送数据private void SendData(byte[] data){try{if (serialPort.IsOpen){serialPort.Write(data, 0, data.Length);Console.WriteLine("发送成功: " + data.Length + " 字节");}else{Console.WriteLine("串口未打开");}}catch (TimeoutException){Console.WriteLine("发送超时");}catch (Exception ex){Console.WriteLine("发送异常: " + ex.Message);}}
}
独立接收线程(轮询方式)
public class PollingSerialPort
{private SerialPort serialPort;private Thread receiveThread;private bool isRunning = false;public PollingSerialPort(string portName, int baudRate){serialPort = new SerialPort(portName, baudRate);serialPort.ReadTimeout = 100; // 短超时避免长时间阻塞}public void Open(){if (!serialPort.IsOpen){serialPort.Open();StartReceiveThread();}}public void Close(){StopReceiveThread();if (serialPort.IsOpen){serialPort.Close();}}private void StartReceiveThread(){isRunning = true;receiveThread = new Thread(ReceiveThreadFunction);receiveThread.IsBackground = true; // 设置为后台线程receiveThread.Start();}private void StopReceiveThread(){isRunning = false;if (receiveThread != null && receiveThread.IsAlive){receiveThread.Join(1000); // 等待线程结束,最多1秒}}// 接收线程函数private void ReceiveThreadFunction(){byte[] buffer = new byte[1024];while (isRunning){try{if (serialPort.IsOpen && serialPort.BytesToRead > 0){int bytesRead = serialPort.Read(buffer, 0, buffer.Length);if (bytesRead > 0){// 创建实际大小的数组byte[] data = new byte[bytesRead];Array.Copy(buffer, 0, data, 0, bytesRead);// 处理数据OnDataReceived(data);}}else{Thread.Sleep(10); // 短暂休眠避免空轮询占用CPU}}catch (TimeoutException){// 读取超时是正常的,继续循环}catch (Exception ex){Console.WriteLine("接收线程异常: " + ex.Message);Thread.Sleep(100); // 发生异常后短暂休眠}}}// 数据接收事件(可以改为事件模式)protected virtual void OnDataReceived(byte[] data){Console.WriteLine("接收到 " + data.Length + " 字节");}
}
超时处理机制
读取超时
public class TimeoutHandling
{private SerialPort serialPort;public TimeoutHandling(SerialPort port){this.serialPort = port;}// 带超时的读取字符串public string ReadLineWithTimeout(int timeoutMs){serialPort.ReadTimeout = timeoutMs;try{return serialPort.ReadLine();}catch (TimeoutException){Console.WriteLine("读取超时");return null;}}// 带超时和重试的读取public byte[] ReadBytesWithRetry(int bytesToRead, int timeoutMs, int retryCount){serialPort.ReadTimeout = timeoutMs;for (int i = 0; i < retryCount; i++){try{byte[] buffer = new byte[bytesToRead];int offset = 0;while (offset < bytesToRead){int read = serialPort.Read(buffer, offset, bytesToRead - offset);offset += read;}return buffer;}catch (TimeoutException){Console.WriteLine("读取超时,重试 " + (i + 1) + "/" + retryCount);if (i == retryCount - 1){throw;}Thread.Sleep(100); // 重试前短暂等待}}return null;}// 带超时的等待指定数据量public byte[] WaitForBytes(int expectedBytes, int totalTimeoutMs){int startTime = Environment.TickCount;List<byte> receivedData = new List<byte>();while (Environment.TickCount - startTime < totalTimeoutMs){if (serialPort.BytesToRead > 0){byte[] buffer = new byte[serialPort.BytesToRead];int read = serialPort.Read(buffer, 0, buffer.Length);for (int i = 0; i < read; i++){receivedData.Add(buffer[i]);}if (receivedData.Count >= expectedBytes){return receivedData.ToArray();}}Thread.Sleep(10);}throw new TimeoutException(string.Format("等待超时,期望 {0} 字节,实际接收 {1} 字节",expectedBytes, receivedData.Count));}// 带超时的命令应答模式public byte[] SendAndWaitResponse(byte[] command, int responseLength, int timeoutMs){// 清空接收缓冲区serialPort.DiscardInBuffer();// 发送命令serialPort.Write(command, 0, command.Length);// 等待应答return WaitForBytes(responseLength, timeoutMs);}
}
写入超时
public class WriteTimeoutHandling
{private SerialPort serialPort;public void SafeWrite(byte[] data, int timeoutMs){serialPort.WriteTimeout = timeoutMs;try{serialPort.Write(data, 0, data.Length);Console.WriteLine("写入成功");}catch (TimeoutException){Console.WriteLine("写入超时,可能发送缓冲区已满");// 尝试清空发送缓冲区serialPort.DiscardOutBuffer();}catch (InvalidOperationException ex){Console.WriteLine("串口未打开: " + ex.Message);}}
}
异常处理最佳实践
常见异常类型
public class ExceptionHandling
{private SerialPort serialPort;public void ComprehensiveExceptionHandling(){try{// 打开串口if (!serialPort.IsOpen){serialPort.Open();}// 执行操作byte[] data = new byte[] { 0x01, 0x02, 0x03 };serialPort.Write(data, 0, data.Length);}catch (UnauthorizedAccessException ex){// 串口被其他程序占用Console.WriteLine("串口访问被拒绝,可能被其他程序占用: " + ex.Message);// 建议:提示用户关闭占用串口的程序}catch (ArgumentException ex){// 参数错误,如端口名无效Console.WriteLine("参数错误: " + ex.Message);// 建议:检查端口名称是否正确}catch (IOException ex){// IO错误,如设备被移除Console.WriteLine("IO错误,设备可能已断开: " + ex.Message);// 建议:尝试重新连接或提示用户检查设备}catch (InvalidOperationException ex){// 操作无效,如端口未打开就进行读写Console.WriteLine("操作无效: " + ex.Message);// 建议:确保操作前串口已正确打开}catch (TimeoutException ex){// 超时异常Console.WriteLine("操作超时: " + ex.Message);// 建议:增加超时时间或检查设备响应}catch (Exception ex){// 其他未预料的异常Console.WriteLine("未知异常: " + ex.Message);Console.WriteLine("堆栈跟踪: " + ex.StackTrace);}finally{// 确保资源释放if (serialPort != null && serialPort.IsOpen){try{serialPort.Close();}catch{// 关闭时的异常不再抛出}}}}// 安全的串口打开public bool SafeOpen(string portName, int baudRate, int retryCount){for (int i = 0; i < retryCount; i++){try{if (serialPort == null){serialPort = new SerialPort(portName, baudRate);}if (!serialPort.IsOpen){serialPort.Open();Console.WriteLine("串口打开成功");return true;}}catch (UnauthorizedAccessException){Console.WriteLine("串口被占用,尝试 " + (i + 1) + "/" + retryCount);Thread.Sleep(1000);}catch (Exception ex){Console.WriteLine("打开失败: " + ex.Message);return false;}}return false;}// 安全的串口关闭public void SafeClose(){try{if (serialPort != null){if (serialPort.IsOpen){// 先停止接收serialPort.DataReceived -= DataReceivedHandler;// 清空缓冲区serialPort.DiscardInBuffer();serialPort.DiscardOutBuffer();// 关闭串口serialPort.Close();}// 释放资源serialPort.Dispose();serialPort = null;}}catch (Exception ex){Console.WriteLine("关闭串口时发生异常: " + ex.Message);}}private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e){// 数据接收处理}
}
线程安全的异常处理
public class ThreadSafeExceptionHandling
{private SerialPort serialPort;private object lockObject = new object();private bool isDisposed = false;public void ThreadSafeWrite(byte[] data){lock (lockObject){if (isDisposed){throw new ObjectDisposedException("SerialPort已被释放");}try{if (serialPort.IsOpen){serialPort.Write(data, 0, data.Length);}}catch (Exception ex){HandleException(ex);}}}private void HandleException(Exception ex){// 集中的异常处理逻辑if (ex is TimeoutException){Console.WriteLine("操作超时");}else if (ex is IOException){Console.WriteLine("IO错误,可能设备已断开");// 触发重连机制TryReconnect();}else{Console.WriteLine("异常: " + ex.Message);}}private void TryReconnect(){// 重连逻辑}public void Dispose(){lock (lockObject){if (!isDisposed){if (serialPort != null){if (serialPort.IsOpen){serialPort.Close();}serialPort.Dispose();}isDisposed = true;}}}
}
数据转换
byte数组与字符串互转
public class DataConversion
{// 1. ASCII编码转换public static string BytesToASCII(byte[] data){return Encoding.ASCII.GetString(data);}public static byte[] ASCIIToBytes(string text){return Encoding.ASCII.GetBytes(text);}// 2. UTF-8编码转换public static string BytesToUTF8(byte[] data){return Encoding.UTF8.GetString(data);}public static byte[] UTF8ToBytes(string text){return Encoding.UTF8.GetBytes(text);}// 3. GB2312编码转换(中文)public static string BytesToGB2312(byte[] data){return Encoding.GetEncoding("GB2312").GetString(data);}public static byte[] GB2312ToBytes(string text){return Encoding.GetEncoding("GB2312").GetBytes(text);}// 4. 十六进制字符串转换public static string BytesToHexString(byte[] data){StringBuilder sb = new StringBuilder(data.Length * 2);foreach (byte b in data){sb.Append(b.ToString("X2"));}return sb.ToString();}public static byte[] HexStringToBytes(string hex){// 移除空格和其他分隔符hex = hex.Replace(" ", "").Replace("-", "").Replace(":", "");if (hex.Length % 2 != 0){throw new ArgumentException("十六进制字符串长度必须是偶数");}byte[] bytes = new byte[hex.Length / 2];for (int i = 0; i < bytes.Length; i++){bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);}return bytes;}// 5. 十六进制字符串(带空格)public static string BytesToHexStringWithSpace(byte[] data){StringBuilder sb = new StringBuilder(data.Length * 3);for (int i = 0; i < data.Length; i++){sb.Append(data[i].ToString("X2"));if (i < data.Length - 1){sb.Append(" ");}}return sb.ToString();}// 6. 整数与byte数组转换(大端序)public static byte[] IntToBigEndianBytes(int value){byte[] bytes = BitConverter.GetBytes(value);if (BitConverter.IsLittleEndian){Array.Reverse(bytes);}return bytes;}public static int BigEndianBytesToInt(byte[] bytes, int startIndex){if (bytes.Length - startIndex < 4){throw new ArgumentException("字节数不足");}byte[] temp = new byte[4];Array.Copy(bytes, startIndex, temp, 0, 4);if (BitConverter.IsLittleEndian){Array.Reverse(temp);}return BitConverter.ToInt32(temp, 0);}// 7. 整数与byte数组转换(小端序)public static byte[] IntToLittleEndianBytes(int value){return BitConverter.GetBytes(value);}public static int LittleEndianBytesToInt(byte[] bytes, int startIndex){return BitConverter.ToInt32(bytes, startIndex);}// 8. 短整数转换public static byte[] ShortToBytes(short value, bool bigEndian){byte[] bytes = BitConverter.GetBytes(value);if (bigEndian && BitConverter.IsLittleEndian){Array.Reverse(bytes);}return bytes;}public static short BytesToShort(byte[] bytes, int startIndex, bool bigEndian){byte[] temp = new byte[2];Array.Copy(bytes, startIndex, temp, 0, 2);if (bigEndian && BitConverter.IsLittleEndian){Array.Reverse(temp);}return BitConverter.ToInt16(temp, 0);}// 9. 浮点数转换public static byte[] FloatToBytes(float value, bool bigEndian){byte[] bytes = BitConverter.GetBytes(value);if (bigEndian && BitConverter.IsLittleEndian){Array.Reverse(bytes);}return bytes;}public static float BytesToFloat(byte[] bytes, int startIndex, bool bigEndian){byte[] temp = new byte[4];Array.Copy(bytes, startIndex, temp, 0, 4);if (bigEndian && BitConverter.IsLittleEndian){Array.Reverse(temp);}return BitConverter.ToSingle(temp, 0);}// 10. BCD码转换(常用于仪表通信)public static byte DecimalToBCD(int value){if (value < 0 || value > 99){throw new ArgumentException("BCD值必须在0-99之间");}int high = value / 10;int low = value % 10;return (byte)((high << 4) | low);}public static int BCDToDecimal(byte bcd){int high = (bcd >> 4) & 0x0F;int low = bcd & 0x0F;return high * 10 + low;}
}
使用示例
public class DataConversionExample
{public void Examples(){// ASCII转换string text = "Hello";byte[] ascii = DataConversion.ASCIIToBytes(text);string back = DataConversion.BytesToASCII(ascii);Console.WriteLine("ASCII: " + back);// 十六进制转换byte[] data = new byte[] { 0x01, 0x02, 0xAB, 0xCD };string hex = DataConversion.BytesToHexString(data);Console.WriteLine("HEX: " + hex); // 输出: 0102ABCDbyte[] restored = DataConversion.HexStringToBytes("01 02 AB CD");// 整数转换int number = 0x12345678;byte[] bigEndian = DataConversion.IntToBigEndianBytes(number);Console.WriteLine("大端: " + DataConversion.BytesToHexString(bigEndian));// BCD转换byte bcd = DataConversion.DecimalToBCD(59);Console.WriteLine("BCD: 0x" + bcd.ToString("X2")); // 输出: 0x59int dec = DataConversion.BCDToDecimal(bcd);Console.WriteLine("Decimal: " + dec); // 输出: 59}
}
多进程通信问题
串口独占性问题
串口是独占资源,同一时间只能被一个进程打开。如果需要多进程访问串口,需要以下解决方案:
方案1:命名管道通信
// 服务进程(串口管理器)
public class SerialPortServer
{private SerialPort serialPort;// 注意:.NET 4.0 不支持 NamedPipeServerStream// 需要使用其他IPC机制,如Windows消息、TCP Socket或WCF
}
方案2:共享内存(推荐.NET 4.0)
using System.IO.MemoryMappedFiles; // .NET 4.0支持public class SharedMemorySerialPort
{private const string MAP_NAME = "SerialPortSharedMemory";private const int BUFFER_SIZE = 4096;// 写入共享内存public void WriteToSharedMemory(byte[] data){using (MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen(MAP_NAME, BUFFER_SIZE)){using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor()){// 写入数据长度accessor.Write(0, data.Length);// 写入数据accessor.WriteArray(4, data, 0, data.Length);}}}// 从共享内存读取public byte[] ReadFromSharedMemory(){using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting(MAP_NAME)){using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor()){// 读取数据长度int length = accessor.ReadInt32(0);if (length > 0 && length < BUFFER_SIZE){byte[] data = new byte[length];accessor.ReadArray(4, data, 0, length);return data;}}}return null;}
}
方案3:TCP Socket服务器(最灵活)
using System.Net;
using System.Net.Sockets;// 串口服务器进程
public class SerialPortTcpServer
{private SerialPort serialPort;private TcpListener tcpListener;private List<TcpClient> clients;private object clientsLock = new object();public SerialPortTcpServer(string portName, int baudRate, int tcpPort){serialPort = new SerialPort(portName, baudRate);serialPort.DataReceived += SerialPort_DataReceived;tcpListener = new TcpListener(IPAddress.Loopback, tcpPort);clients = new List<TcpClient>();}public void Start(){// 打开串口serialPort.Open();// 启动TCP监听tcpListener.Start();// 异步接受客户端tcpListener.BeginAcceptTcpClient(AcceptCallback, null);Console.WriteLine("串口服务器已启动");}private void AcceptCallback(IAsyncResult ar){try{TcpClient client = tcpListener.EndAcceptTcpClient(ar);lock (clientsLock){clients.Add(client);}Console.WriteLine("客户端已连接");// 继续接受下一个客户端tcpListener.BeginAcceptTcpClient(AcceptCallback, null);// 处理客户端请求Thread clientThread = new Thread(HandleClient);clientThread.IsBackground = true;clientThread.Start(client);}catch (Exception ex){Console.WriteLine("接受客户端异常: " + ex.Message);}}private void HandleClient(object obj){TcpClient client = (TcpClient)obj;NetworkStream stream = client.GetStream();byte[] buffer = new byte[1024];try{while (client.Connected){if (stream.DataAvailable){int bytesRead = stream.Read(buffer, 0, buffer.Length);if (bytesRead > 0){byte[] data = new byte[bytesRead];Array.Copy(buffer, 0, data, 0, bytesRead);// 转发到串口serialPort.Write(data, 0, data.Length);}}Thread.Sleep(10);}}catch (Exception ex){Console.WriteLine("客户端处理异常: " + ex.Message);}finally{lock (clientsLock){clients.Remove(client);}client.Close();}}private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e){try{int bytesToRead = serialPort.BytesToRead;if (bytesToRead > 0){byte[] buffer = new byte[bytesToRead];int bytesRead = serialPort.Read(buffer, 0, bytesToRead);// 广播给所有客户端BroadcastToClients(buffer, bytesRead);}}catch (Exception ex){Console.WriteLine("串口接收异常: " + ex.Message);}}private void BroadcastToClients(byte[] data, int length){lock (clientsLock){foreach (TcpClient client in clients.ToArray()){try{if (client.Connected){NetworkStream stream = client.GetStream();stream.Write(data, 0, length);}}catch{// 客户端断开,忽略}}}}public void Stop(){serialPort.Close();tcpListener.Stop();lock (clientsLock){foreach (TcpClient client in clients){client.Close();}clients.Clear();}}
}// 客户端进程
public class SerialPortTcpClient
{private TcpClient tcpClient;private NetworkStream stream;public void Connect(string serverIp, int port){tcpClient = new TcpClient();tcpClient.Connect(serverIp, port);stream = tcpClient.GetStream();// 启动接收线程Thread receiveThread = new Thread(ReceiveData);receiveThread.IsBackground = true;receiveThread.Start();}public void SendData(byte[] data){if (tcpClient.Connected){stream.Write(data, 0, data.Length);}}private void ReceiveData(){byte[] buffer = new byte[1024];while (tcpClient.Connected){try{if (stream.DataAvailable){int bytesRead = stream.Read(buffer, 0, buffer.Length);if (bytesRead > 0){byte[] data = new byte[bytesRead];Array.Copy(buffer, 0, data, 0, bytesRead);// 处理接收到的数据OnDataReceived(data);}}Thread.Sleep(10);}catch (Exception ex){Console.WriteLine("接收异常: " + ex.Message);break;}}}protected virtual void OnDataReceived(byte[] data){Console.WriteLine("接收到数据: " + DataConversion.BytesToHexString(data));}public void Disconnect(){if (tcpClient != null){tcpClient.Close();}}
}
完整示例代码
完整的串口通信类
using System;
using System.IO.Ports;
using System.Text;
using System.Threading;
using System.Collections.Generic;public class AdvancedSerialPort : IDisposable
{// 串口对象private SerialPort serialPort;// 线程控制private Thread receiveThread;private bool isRunning;// 发送队列private Queue<byte[]> sendQueue;private object queueLock = new object();private AutoResetEvent sendEvent;// 数据缓冲private List<byte> receiveBuffer;private object bufferLock = new object();// 事件public event EventHandler<byte[]> DataReceived;public event EventHandler<string> ErrorOccurred;public event EventHandler Connected;public event EventHandler Disconnected;// 属性public bool IsOpen{get { return serialPort != null && serialPort.IsOpen; }}public AdvancedSerialPort(){receiveBuffer = new List<byte>();sendQueue = new Queue<byte[]>();sendEvent = new AutoResetEvent(false);}// 配置并打开串口public bool Open(string portName, int baudRate, int dataBits, Parity parity, StopBits stopBits){try{// 创建串口对象serialPort = new SerialPort();serialPort.PortName = portName;serialPort.BaudRate = baudRate;serialPort.DataBits = dataBits;serialPort.Parity = parity;serialPort.StopBits = stopBits;serialPort.ReadTimeout = 500;serialPort.WriteTimeout = 500;serialPort.ReadBufferSize = 4096;serialPort.WriteBufferSize = 2048;// 打开串口serialPort.Open();// 启动线程StartThreads();// 触发连接事件OnConnected();return true;}catch (Exception ex){OnError("打开串口失败: " + ex.Message);return false;}}// 关闭串口public void Close(){try{// 停止线程StopThreads();// 关闭串口if (serialPort != null && serialPort.IsOpen){serialPort.Close();}// 触发断开事件OnDisconnected();}catch (Exception ex){OnError("关闭串口失败: " + ex.Message);}}// 发送数据(加入队列)public void SendAsync(byte[] data){lock (queueLock){sendQueue.Enqueue(data);sendEvent.Set(); // 通知发送线程}}// 同步发送数据public bool SendSync(byte[] data, int timeoutMs){try{if (serialPort.IsOpen){serialPort.WriteTimeout = timeoutMs;serialPort.Write(data, 0, data.Length);return true;}}catch (TimeoutException){OnError("发送超时");}catch (Exception ex){OnError("发送失败: " + ex.Message);}return false;}// 发送字符串public void SendString(string text, Encoding encoding){byte[] data = encoding.GetBytes(text);SendAsync(data);}// 发送十六进制字符串public void SendHexString(string hexString){try{byte[] data = DataConversion.HexStringToBytes(hexString);SendAsync(data);}catch (Exception ex){OnError("发送十六进制失败: " + ex.Message);}}// 清空缓冲区public void ClearBuffers(){if (serialPort != null && serialPort.IsOpen){serialPort.DiscardInBuffer();serialPort.DiscardOutBuffer();}lock (bufferLock){receiveBuffer.Clear();}}// 启动线程private void StartThreads(){isRunning = true;// 接收线程receiveThread = new Thread(ReceiveThreadFunction);receiveThread.IsBackground = true;receiveThread.Start();// 发送线程Thread sendThread = new Thread(SendThreadFunction);sendThread.IsBackground = true;sendThread.Start();}// 停止线程private void StopThreads(){isRunning = false;sendEvent.Set(); // 唤醒发送线程以便退出if (receiveThread != null && receiveThread.IsAlive){receiveThread.Join(1000);}}// 接收线程private void ReceiveThreadFunction(){byte[] buffer = new byte[1024];while (isRunning){try{if (serialPort.IsOpen && serialPort.BytesToRead > 0){int bytesRead = serialPort.Read(buffer, 0, buffer.Length);if (bytesRead > 0){byte[] data = new byte[bytesRead];Array.Copy(buffer, 0, data, 0, bytesRead);// 添加到缓冲区lock (bufferLock){receiveBuffer.AddRange(data);}// 触发事件OnDataReceived(data);}}else{Thread.Sleep(10);}}catch (TimeoutException){// 超时正常,继续}catch (Exception ex){OnError("接收异常: " + ex.Message);Thread.Sleep(100);}}}// 发送线程private void SendThreadFunction(){while (isRunning){try{// 等待信号sendEvent.WaitOne(100);// 处理队列while (true){byte[] data = null;lock (queueLock){if (sendQueue.Count > 0){data = sendQueue.Dequeue();}else{break;}}if (data != null){SendSync(data, 1000);}}}catch (Exception ex){OnError("发送线程异常: " + ex.Message);}}}// 事件触发protected virtual void OnDataReceived(byte[] data){if (DataReceived != null){DataReceived(this, data);}}protected virtual void OnError(string message){if (ErrorOccurred != null){ErrorOccurred(this, message);}}protected virtual void OnConnected(){if (Connected != null){Connected(this, EventArgs.Empty);}}protected virtual void OnDisconnected(){if (Disconnected != null){Disconnected(this, EventArgs.Empty);}}// 释放资源public void Dispose(){Close();if (serialPort != null){serialPort.Dispose();serialPort = null;}if (sendEvent != null){sendEvent.Close();}}
}
使用示例
public class Program
{private static AdvancedSerialPort serialPort;static void Main(string[] args){// 创建串口对象serialPort = new AdvancedSerialPort();// 注册事件serialPort.DataReceived += SerialPort_DataReceived;serialPort.ErrorOccurred += SerialPort_ErrorOccurred;serialPort.Connected += SerialPort_Connected;serialPort.Disconnected += SerialPort_Disconnected;// 打开串口if (serialPort.Open("COM1", 9600, 8, Parity.None, StopBits.One)){Console.WriteLine("串口打开成功");// 发送测试数据serialPort.SendString("Hello World\r\n", Encoding.ASCII);serialPort.SendHexString("01 02 03 04");// 等待用户输入Console.WriteLine("按任意键关闭...");Console.ReadKey();}else{Console.WriteLine("串口打开失败");}// 关闭串口serialPort.Close();serialPort.Dispose();}private static void SerialPort_DataReceived(object sender, byte[] data){string hex = DataConversion.BytesToHexStringWithSpace(data);string ascii = Encoding.ASCII.GetString(data);Console.WriteLine("接收: HEX=" + hex);Console.WriteLine(" ASCII=" + ascii);}private static void SerialPort_ErrorOccurred(object sender, string message){Console.WriteLine("错误: " + message);}private static void SerialPort_Connected(object sender, EventArgs e){Console.WriteLine("已连接");}private static void SerialPort_Disconnected(object sender, EventArgs e){Console.WriteLine("已断开");}
}
常见问题解决
1. 串口被占用
// 检测串口是否被占用
public static bool IsPortAvailable(string portName)
{try{SerialPort port = new SerialPort(portName);port.Open();port.Close();return true;}catch (UnauthorizedAccessException){return false; // 被占用}catch{return false; // 其他错误}
}
2. 数据粘包问题
public class PacketParser
{private List<byte> buffer = new List<byte>();// 假设协议:头(0xAA 0x55) + 长度(1字节) + 数据 + 校验(1字节)public List<byte[]> ParsePackets(byte[] newData){List<byte[]> packets = new List<byte[]>();// 添加新数据到缓冲区buffer.AddRange(newData);while (buffer.Count >= 4) // 最小包长度{// 查找包头int headerIndex = -1;for (int i = 0; i < buffer.Count - 1; i++){if (buffer[i] == 0xAA && buffer[i + 1] == 0x55){headerIndex = i;break;}}if (headerIndex == -1){// 没有找到包头,清空缓冲区buffer.Clear();break;}// 移除包头前的无效数据if (headerIndex > 0){buffer.RemoveRange(0, headerIndex);}// 检查是否有足够的数据if (buffer.Count < 4){break;}// 读取数据长度int dataLength = buffer[2];int packetLength = 4 + dataLength; // 头(2) + 长度(1) + 数据 + 校验(1)if (buffer.Count < packetLength){// 数据不完整,等待更多数据break;}// 提取完整包byte[] packet = buffer.GetRange(0, packetLength).ToArray();// 验证校验和if (ValidateChecksum(packet)){packets.Add(packet);}// 移除已处理的包buffer.RemoveRange(0, packetLength);}return packets;}private bool ValidateChecksum(byte[] packet){byte checksum = 0;for (int i = 0; i < packet.Length - 1; i++){checksum += packet[i];}return checksum == packet[packet.Length - 1];}
}
3. 串口重连机制
public class AutoReconnectSerialPort
{private AdvancedSerialPort serialPort;private string portName;private int baudRate;private Timer reconnectTimer;private bool autoReconnect = true;public void EnableAutoReconnect(int intervalMs){autoReconnect = true;reconnectTimer = new Timer(ReconnectTimerCallback, null, intervalMs, intervalMs);}private void ReconnectTimerCallback(object state){if (autoReconnect && !serialPort.IsOpen){Console.WriteLine("尝试重新连接...");if (serialPort.Open(portName, baudRate, 8, Parity.None, StopBits.One)){Console.WriteLine("重连成功");}}}
}
4. 性能优化技巧
// 批量发送优化
public void SendBatch(List<byte[]> dataList)
{// 合并多个小包为一个大包int totalLength = 0;foreach (byte[] data in dataList){totalLength += data.Length;}byte[] combined = new byte[totalLength];int offset = 0;foreach (byte[] data in dataList){Array.Copy(data, 0, combined, offset, data.Length);offset += data.Length;}// 一次性发送SendSync(combined, 2000);
}
5. 日志记录
public class SerialPortLogger
{private string logFilePath;private object logLock = new object();public SerialPortLogger(string filePath){logFilePath = filePath;}public void Log(string direction, byte[] data){lock (logLock){try{string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");string hex = DataConversion.BytesToHexStringWithSpace(data);string line = string.Format("{0} [{1}] {2}\r\n", timestamp, direction, hex);System.IO.File.AppendAllText(logFilePath, line);}catch (Exception ex){Console.WriteLine("日志写入失败: " + ex.Message);}}}
}
总结
本教程涵盖了C#串口通信的核心内容:
- 基础配置:SerialPort类的各项参数设置
- 多线程处理:事件驱动、线程池、独立接收线程等多种方式
- 超时控制:读写超时、带重试机制、命令应答模式
- 异常处理:各类异常的捕获和处理策略
- 数据转换:多种编码格式、字节序、BCD码等转换
- 多进程方案:共享内存、TCP服务器等IPC机制
- 实战示例:完整可用的串口通信类
最佳实践建议
- 始终使用
try-catch
保护串口操作 - 使用事件方式接收数据,避免轮询
- 发送大量数据时使用队列机制
- 正确处理粘包问题
- 实现自动重连提高可靠性
- 记录通信日志便于调试
- 多进程访问需要特殊设计
注意事项
- .NET Framework 4.0 不支持
async/await
- 避免使用 C# 6.0+ 的新语法
- 串口操作要考虑线程安全
- 及时释放串口资源
- 超时值要根据实际设备调整