基于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);
}
posted @ 2025-12-16 17:53  yu8yu7  阅读(4)  评论(0)    收藏  举报