串口通信是上位机开发中的核心技能,广泛应用于工业控制、物联网和自动化测试等领域。C# 通过 System.IO.Ports.SerialPort 类提供了完整的串口通信支持。本教程从配置参数、数据发送、数据接收、异步操作和错误处理五个方面,系统讲解 SerialPort 的发送与接收实现。核心逻辑可以概括为三步:配置参数 → 发送 Write → 事件接收 DataReceived。

1. 引言

在工业控制、嵌入式开发、仪器仪表等场景中,串口通信依然是最基础、最常用的通信方式之一。C# 通过 System.IO.Ports 命名空间下的 SerialPort 类,为开发者提供了简洁而强大的串口操作接口。本文将系统讲解 C# 中 SerialPort 的发送与接收机制,从基础配置到高级应用,帮助你快速掌握串口编程的核心要点。

2. 环境准备与基础配置

2.1 引入命名空间

使用 SerialPort 前,需要引入 System.IO.Ports 命名空间:

using System.IO.Ports;

2.2 创建并配置 SerialPort 实例

SerialPort serialPort = new SerialPort();
// 基本配置
serialPort.PortName = "COM3";          // 串口号
serialPort.BaudRate = 9600;            // 波特率
serialPort.DataBits = 8;               // 数据位
serialPort.Parity = Parity.None;       // 校验位
serialPort.StopBits = StopBits.One;    // 停止位
// 可选配置
serialPort.ReadTimeout = 500;          // 读取超时(毫秒)
serialPort.WriteTimeout = 500;         // 写入超时(毫秒)
serialPort.NewLine = "\r\n";           // 换行符(用于 ReadLine)

2.3 打开与关闭串口

// 打开串口
try
{
    serialPort.Open();
    Console.WriteLine("串口已打开");
}
catch (Exception ex)
{
    Console.WriteLine($"打开失败: {ex.Message}");
}
// 关闭串口
serialPort.Close();

3. 数据发送

3.1 发送字符串

// 直接发送字符串
serialPort.Write("Hello, Serial!");
// 发送带换行符的字符串
serialPort.WriteLine("AT+CSQ");

3.2 发送字节数组

// 构造要发送的字节数据
byte[] data = new byte[] { 0x01, 0x02, 0x03, 0xFF, 0x00 };
// 发送字节数组
serialPort.Write(data, 0, data.Length);

3.3 发送十六进制字符串

/// <summary>
/// 将十六进制字符串转换为字节数组并发送
/// </summary>
public static void SendHexString(SerialPort port, string hexString)
{
    // 去除空格
    hexString = hexString.Replace(" ", "");
    // 将十六进制字符串转换为字节数组
    byte[] data = new byte[hexString.Length / 2];
    for (int i = 0; i < data.Length; i++)
    {
        data[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
    }
    // 发送数据
    port.Write(data, 0, data.Length);
}
// 使用示例
SendHexString(serialPort, "01 03 00 00 00 0A C5 CD");

4. 数据接收

4.1 使用 DataReceived 事件(推荐)

DataReceived 事件是串口接收最常用的方式,它在数据到达时异步触发:

// 订阅事件
serialPort.DataReceived += SerialPort_DataReceived;
// 事件处理方法
private static void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    SerialPort sp = (SerialPort)sender;
    // 读取当前缓冲区中所有可用数据
    int bytesToRead = sp.BytesToRead;
    byte[] buffer = new byte[bytesToRead];
    sp.Read(buffer, 0, bytesToRead);
    // 处理接收到的数据
    string receivedText = System.Text.Encoding.UTF8.GetString(buffer);
    Console.WriteLine($"收到数据: {receivedText}");
    // 或者以十六进制显示
    string hexText = BitConverter.ToString(buffer);
    Console.WriteLine($"收到十六进制: {hexText}");
}

4.2 同步读取

// 读取一个字节
int byteValue = serialPort.ReadByte();
// 读取到指定缓冲区
byte[] buffer = new byte[1024];
int bytesRead = serialPort.Read(buffer, 0, buffer.Length);
// 读取一行(直到遇到 NewLine)
string line = serialPort.ReadLine();

4.3 完整接收示例:处理不定长数据

private static void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    SerialPort sp = (SerialPort)sender;
    // 防止多线程同时访问
    lock (sp)
    {
        // 读取所有可用数据
        int bytesToRead = sp.BytesToRead;
        byte[] buffer = new byte[bytesToRead];
        int bytesRead = sp.Read(buffer, 0, bytesToRead);
        if (bytesRead > 0)
        {
            // 将数据追加到接收缓冲区
            receiveBuffer.AddRange(buffer);
            // 尝试解析完整的数据帧
            ProcessReceivedData();
        }
    }
}
private static List<byte> receiveBuffer = new List<byte>();
private static void ProcessReceivedData()
{
    // 假设协议帧格式: 帧头(0xAA) + 长度(1字节) + 数据 + 校验(1字节)
    while (receiveBuffer.Count >= 3)
    {
        // 查找帧头
        if (receiveBuffer[0] != 0xAA)
        {
            receiveBuffer.RemoveAt(0);
            continue;
        }
        int dataLength = receiveBuffer[1];
        int totalLength = 2 + dataLength + 1; // 帧头 + 长度 + 数据 + 校验
        if (receiveBuffer.Count < totalLength)
        {
            break; // 数据不完整,等待更多数据
        }
        // 提取完整帧
        byte[] frame = receiveBuffer.GetRange(0, totalLength).ToArray();
        receiveBuffer.RemoveRange(0, totalLength);
        // 校验并处理帧
        if (VerifyChecksum(frame))
        {
            Console.WriteLine($"收到完整帧: {BitConverter.ToString(frame)}");
        }
    }
}
private static bool VerifyChecksum(byte[] frame)
{
    // 简单的校验和验证
    byte checksum = 0;
    for (int i = 0; i < frame.Length - 1; i++)
    {
        checksum ^= frame[i];
    }
    return checksum == frame[frame.Length - 1];
}

5. 完整示例:串口调试助手

下面是一个完整的控制台串口通信示例,包含发送和接收功能:

using System;
using System.IO.Ports;
using System.Text;
using System.Threading;
class SerialPortDemo
{
    private static SerialPort serialPort;
    private static bool isRunning = true;
    static void Main(string[] args)
    {
        // 初始化串口
        serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);
        serialPort.DataReceived += SerialPort_DataReceived;
        try
        {
            serialPort.Open();
            Console.WriteLine("串口已打开,输入 'exit' 退出程序");
            Console.WriteLine("输入 'send:内容' 发送字符串");
            Console.WriteLine("输入 'hex:01 02 03' 发送十六进制数据");
            // 主线程处理用户输入
            while (isRunning)
            {
                string input = Console.ReadLine();
                if (input == "exit")
                {
                    isRunning = false;
                }
                else if (input.StartsWith("send:"))
                {
                    string data = input.Substring(5);
                    serialPort.Write(data);
                    Console.WriteLine($"已发送: {data}");
                }
                else if (input.StartsWith("hex:"))
                {
                    string hexData = input.Substring(4);
                    SendHexString(hexData);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"错误: {ex.Message}");
        }
        finally
        {
            if (serialPort.IsOpen)
            {
                serialPort.Close();
            }
            Console.WriteLine("串口已关闭");
        }
    }
    private static void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        SerialPort sp = (SerialPort)sender;
        int bytesToRead = sp.BytesToRead;
        byte[] buffer = new byte[bytesToRead];
        sp.Read(buffer, 0, bytesToRead);
        string text = Encoding.UTF8.GetString(buffer);
        string hex = BitConverter.ToString(buffer);
        Console.WriteLine($"[接收] 文本: {text}");
        Console.WriteLine($"[接收] 十六进制: {hex}");
    }
    private static void SendHexString(string hexString)
    {
        hexString = hexString.Replace(" ", "");
        byte[] data = new byte[hexString.Length / 2];
        for (int i = 0; i < data.Length; i++)
        {
            data[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
        }
        serialPort.Write(data, 0, data.Length);
        Console.WriteLine($"已发送十六进制: {BitConverter.ToString(data)}");
    }
}

6. 常见问题与注意事项

6.1 串口被占用

// 检查串口是否可用
string[] ports = SerialPort.GetPortNames();
if (Array.IndexOf(ports, "COM3") == -1)
{
    Console.WriteLine("COM3 不可用");
    return;
}

6.2 跨线程访问 UI

在 WinForms 或 WPF 中,DataReceived 事件在后台线程触发,不能直接操作 UI 控件:

// WinForms 示例
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    if (this.InvokeRequired)
    {
        this.Invoke(new Action(() => SerialPort_DataReceived(sender, e)));
        return;
    }
    // 现在可以安全地操作 UI 控件
    textBox1.AppendText("收到数据");
}

6.3 数据粘包与拆包

串口通信中常见的数据粘包问题,解决方案:

  • 使用固定帧头帧尾
  • 使用长度字段
  • 使用超时机制
// 使用超时机制处理粘包
private static void ReadWithTimeout()
{
    DateTime startTime = DateTime.Now;
    List<byte> dataBuffer = new List<byte>();
    while ((DateTime.Now - startTime).TotalMilliseconds < 100)
    {
        if (serialPort.BytesToRead > 0)
        {
            int b = serialPort.ReadByte();
            dataBuffer.Add((byte)b);
            startTime = DateTime.Now; // 重置超时
        }
        Thread.Sleep(10);
    }
    if (dataBuffer.Count > 0)
    {
        Console.WriteLine($"接收完成: {BitConverter.ToString(dataBuffer.ToArray())}");
    }
}

7. 总结

本文详细介绍了 C# 中 SerialPort 的发送与接收方法:

  • 发送:支持字符串、字节数组、十六进制字符串等多种格式
  • 接收:推荐使用 DataReceived 事件实现异步接收
  • 协议处理:通过帧头、长度、校验等机制确保数据完整性
  • 注意事项:跨线程访问、数据粘包、串口占用等常见问题

掌握这些核心要点后,你就可以在 C# 项目中灵活实现串口通信功能。建议在实际开发中,根据具体的通信协议设计合适的收发逻辑,并做好异常处理和资源释放。

觉得上面的内容有用吗?快来点个赞吧!

点赞() 我要打赏

温馨提示 : 本站内容来自会员投稿以及互联网,所有源码及教程均为作者总结编辑,请大家在使用过程中提前做好备份,以免发生无法预知的错误,源码类教程请勿直接用于生产环境!

 可能感兴趣的文章