基于C#实现工业级16进制串口通信
一、核心通信类设计
using System;
using System.IO.Ports;
using System.Text;
using System.Timers;
public class HexSerialPort
{
private SerialPort _serialPort;
private readonly object _lock = new();
private Timer _receiveTimer = new Timer(50); // 数据完整性定时器
public event EventHandler<byte[]> DataReceived;
public event EventHandler<string> ErrorOccurred;
public HexSerialPort()
{
_serialPort = new SerialPort();
_receiveTimer.Elapsed += OnReceiveTimerElapsed;
}
// 初始化配置
public void Initialize(string portName, int baudRate, Parity parity = Parity.None,
StopBits stopBits = StopBits.One, int dataBits = 8)
{
_serialPort.PortName = portName;
_serialPort.BaudRate = baudRate;
_serialPort.Parity = parity;
_serialPort.StopBits = stopBits;
_serialPort.DataBits = dataBits;
_serialPort.DataReceived += OnDataReceived;
}
// 打开串口
public bool Open()
{
try
{
if (!_serialPort.IsOpen)
{
_serialPort.Open();
_receiveTimer.Start();
return true;
}
return false;
}
catch (Exception ex)
{
HandleError($"打开串口失败: {ex.Message}");
return false;
}
}
// 关闭串口
public void Close()
{
_receiveTimer.Stop();
if (_serialPort.IsOpen)
{
_serialPort.Close();
}
}
// 发送16进制数据
public void Send(byte[] data)
{
try
{
byte[] crc = CalculateCRC(data);
byte[] frame = new byte[data.Length + 2];
Array.Copy(data, 0, frame, 0, data.Length);
Array.Copy(crc, 0, frame, data.Length, 2);
_serialPort.Write(frame, 0, frame.Length);
OnDataSent(frame);
}
catch (Exception ex)
{
HandleError($"发送数据失败: {ex.Message}");
}
}
// 数据接收处理
private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
int bufferSize = _serialPort.BytesToRead;
byte[] buffer = new byte[bufferSize];
_serialPort.Read(buffer, 0, bufferSize);
Array.Copy(buffer, 0, ReceiveBuffer, ReceiveBuffer.Length, bufferSize);
_receiveTimer.Stop();
_receiveTimer.Start();
}
catch (TimeoutException)
{
ProcessBuffer();
}
catch (Exception ex)
{
HandleError($"接收数据异常: {ex.Message}");
}
}
private void OnReceiveTimerElapsed(object sender, ElapsedEventArgs e)
{
ProcessBuffer();
}
private void ProcessBuffer()
{
lock (_lock)
{
if (ReceiveBuffer.Length < 5) return; // 最小有效帧长度
// 解析数据帧
int headerIndex = Array.IndexOf(ReceiveBuffer, 0xAA);
if (headerIndex != 0) return; // 起始符校验
int footerIndex = Array.IndexOf(ReceiveBuffer, 0x55);
if (footerIndex == -1) return;
byte[] frame = new byte[footerIndex + 1];
Array.Copy(ReceiveBuffer, 0, frame, 0, footerIndex + 1);
// 校验CRC
byte[] crc = new byte[2] { frame[frame.Length - 2], frame[frame.Length - 1] };
if (!VerifyCRC(frame, crc))
{
HandleError("CRC校验失败");
return;
}
// 触发数据事件
byte[] payload = new byte[frame.Length - 4];
Array.Copy(frame, 2, payload, 0, payload.Length);
DataReceived?.Invoke(this, payload);
// 清空缓冲区
Array.Clear(ReceiveBuffer, 0, ReceiveBuffer.Length);
}
}
// CRC16-Modbus校验
private byte[] CalculateCRC(byte[] data)
{
ushort crc = 0xFFFF;
for (int i = 0; i < data.Length; i++)
{
crc ^= (ushort)(data[i] << 8);
for (int j = 0; j < 8; j++)
{
if ((crc & 0x8000) != 0)
{
crc = (ushort)((crc << 1) ^ 0x1021);
}
else
{
crc <<= 1;
}
}
}
return new byte[] { (byte)(crc & 0xFF), (byte)(crc >> 8) };
}
private bool VerifyCRC(byte[] frame, byte[] crc)
{
byte[] calculated = CalculateCRC(frame, 0, frame.Length - 2);
return calculated[0] == crc[0] && calculated[1] == crc[1];
}
// 数据缓冲区
private byte[] ReceiveBuffer = new byte[1024];
// 事件处理
protected virtual void OnDataSent(byte[] data)
{
// 实现发送日志记录
}
private void HandleError(string message)
{
ErrorOccurred?.Invoke(this, message);
}
}
二、数据帧格式定义
// 工业标准数据帧结构
public class IndustrialDataFrame
{
public const byte StartMarker = 0xAA;
public const byte EndMarker = 0x55;
public byte Address { get; set; } // 设备地址
public byte FunctionCode { get; set; }// 功能码
public byte[] Payload { get; set; } // 有效载荷
public ushort Crc { get; set; } // CRC校验码
// 构建数据帧
public static byte[] BuildFrame(byte address, byte functionCode, byte[] payload)
{
byte[] frame = new byte[2 + 1 + 1 + payload.Length + 2];
frame[0] = StartMarker;
frame[1] = address;
frame[2] = functionCode;
Array.Copy(payload, 0, frame, 3, payload.Length);
byte[] crc = CalculateCRC(frame, 0, frame.Length - 2);
frame[frame.Length - 2] = crc[0];
frame[frame.Length - 1] = crc[1];
frame[frame.Length - 0] = EndMarker;
return frame;
}
}
三、使用示例
// 初始化串口
var serial = new HexSerialPort();
serial.Initialize("COM3", 9600, Parity.None, StopBits.One, 8);
serial.DataReceived += (s, data) =>
{
// 处理接收到的数据
var frame = ParseDataFrame(data);
Console.WriteLine($"收到数据: {BitConverter.ToString(frame.Payload)}");
};
serial.Open();
// 发送数据
byte[] payload = new byte[] { 0x01, 0x03, 0x00, 0x00 };
byte[] frame = IndustrialDataFrame.BuildFrame(0x01, 0x03, payload);
serial.Send(frame);
四、关键功能实现
1. CRC16查表法优化
private static readonly ushort[] Crctable = new ushort[256]
{
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
// ... 完整查表数据
};
private ushort CalculateCRC(byte[] data, int offset, int length)
{
ushort crc = 0xFFFF;
for (int i = offset; i < offset + length; i++)
{
crc = (ushort)((crc >> 8) ^ Crctable[(crc ^ data[i]) & 0xFF]);
}
return crc;
}
2. 异常处理机制
private void HandleSerialError(Exception ex)
{
// 自动重连逻辑
if (ex is TimeoutException || ex is IOException)
{
Task.Delay(1000).ContinueWith(_ => Open());
}
// 日志记录
File.AppendAllText("error.log", $"{DateTime.Now}: {ex.Message}\n");
}
3. 数据解析器
public IndustrialDataFrame ParseDataFrame(byte[] data)
{
if (data.Length < 5) throw new ArgumentException("无效数据帧");
return new IndustrialDataFrame
{
Address = data[1],
FunctionCode = data[2],
Payload = new byte[data.Length - 4],
Crc = (ushort)(data[data.Length - 2] | (data[data.Length - 1] << 8))
};
}
五、工程实践建议
1. 配置管理
{
"SerialPort": {
"PortName": "COM3",
"BaudRate": 9600,
"Parity": "None",
"StopBits": "One",
"DataBits": 8,
"FlowControl": "None"
},
"Timeout": 5000,
"RetryCount": 3
}
2. 性能优化
- 双缓冲区技术:使用
ArrayPool<byte>减少内存分配 - 异步接收:采用
BeginRead/EndRead实现非阻塞IO - 批量写入:合并多个小数据包为单个发送操作
3. 调试工具
public static class DebugHelper
{
public static void LogFrame(byte[] data)
{
string hex = BitConverter.ToString(data).Replace("-", "");
File.AppendAllText("debug.log", $"{DateTime.Now}: {hex}\n");
}
}
六、扩展功能实现
1. 流量统计
public class TrafficMonitor
{
private long _sentBytes;
private long _receivedBytes;
public void UpdateSent(long bytes) => Interlocked.Add(ref _sentBytes, bytes);
public void UpdateReceived(long bytes) => Interlocked.Add(ref _receivedBytes, bytes);
}
2. 固件升级
public async Task FirmwareUpdateAsync(Stream firmwareStream)
{
const int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
while (await firmwareStream.ReadAsync(buffer, 0, bufferSize) > 0)
{
await SendWithAck(buffer, bufferSize);
}
}
private async Task SendWithAck(byte[] data, int length)
{
int retry = 0;
while (retry < 3)
{
Send(data, 0, length);
if (await WaitForAck(3000))
return;
retry++;
}
throw new TimeoutException("固件升级超时");
}
参考代码 c# 串口通讯实现工业上用的16HEX方式的报文通讯 www.youwenfan.com/contentcnn/93393.html
七、测试方案
1. 单元测试
[TestFixture]
public class SerialPortTests
{
[Test]
public void TestCRCCalculation()
{
byte[] testData = { 0x01, 0x03, 0x00, 0x00 };
ushort crc = HexSerialPort.CalculateCRC(testData);
Assert.AreEqual(0x0645, crc);
}
}
2. 压力测试
public void StressTest()
{
var tasks = new Task[100];
for (int i = 0; i < 100; i++)
{
tasks[i] = Task.Run(() =>
{
for (int j = 0; j < 1000; j++)
{
SendTestPacket();
}
});
}
Task.WaitAll(tasks);
}
浙公网安备 33010602011771号