从零打造 C# 工业视觉检测系统(七):串口通信 SerialPort 封装与开发

从零打造 C# 工业视觉检测系统(七):串口通信 SerialPort 封装与开发

系列标签C# SerialPort RS232 RS485 串口调试 HEX 收发
本文目标:掌握 C# 串口通信编程,实现带 HEX 收发功能的工业串口调试工具


一、前言

在工业自动化领域,串口通信是最经典、最可靠的设备互联方式。虽然以太网日益普及,但大量 PLC、变频器、称重仪表、继电器模块仍然只提供 RS232/RS485 接口。

本项目的剔除控制就是通过串口连接继电器模块实现的。本文将从串口基础讲起,一直到封装出一个工业级的串口调试工具。


二、串口通信基础

2.1 RS232 vs RS485

特性RS232RS485
传输距离< 15m< 1200m
连接方式点对点总线(一主多从)
抗干扰较弱
工业应用近距离调试产线设备联网

2.2 串口参数五要素

串口通信必须双方参数一致,否则收到的全是乱码:

  1. 波特率 (BaudRate):每秒传输的码元数,常用 9600、115200
  2. 数据位 (DataBits):每个字节的有效位数,通常是 8
  3. 校验位 (Parity):None(无)、Odd(奇)、Even(偶)
  4. 停止位 (StopBits):One(1)、Two(2)
  5. 流控制 (Handshake):None(本项目不使用)

三、C# SerialPort 核心 API

.NET 提供了 System.IO.Ports.SerialPort 类,封装了完整的串口功能。

3.1 打开与关闭

SerialPort comPort = new SerialPort();

// 配置参数
comPort.PortName = "COM3";
comPort.BaudRate = 9600;
comPort.Parity = Parity.None;
comPort.DataBits = 8;
comPort.StopBits = StopBits.One;

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

// 关闭
comPort.Close();

3.2 发送数据

// 发送字符串
string msg = "Hello PLC";
comPort.Write(msg);

// 发送字节数组
byte[] data = new byte[] { 0xA0, 0x01, 0x01, 0xA2 };
comPort.Write(data, 0, data.Length);

3.3 接收数据

// 查询方式读取
int bytesToRead = comPort.BytesToRead;
if (bytesToRead > 0)
{
    byte[] buffer = new byte[bytesToRead];
    comPort.Read(buffer, 0, bytesToRead);
    string received = Encoding.UTF8.GetString(buffer);
}

四、SerialPortHelper 封装

本项目将串口功能封装为静态工具类 SerialPortHelper,便于全局调用。

4.1 枚举值定义

public enum DataBits : int
{
    Five = 5, Six = 6, Sevent = 7, Eight = 8
}

public enum BaudRates : int
{
    BR_9600 = 9600,
    BR_19200 = 19200,
    BR_38400 = 38400,
    BR_57600 = 57600,
    BR_115200 = 115200
}

4.2 初始化与打开

public static class SerialPortHelper
{
    public static SerialPort comPort = new SerialPort();
    
    private static string portName = "COM1";
    private static BaudRates baudRate = BaudRates.BR_9600;
    private static Parity parity = Parity.None;
    private static StopBits stopBits = StopBits.One;
    private static DataBits dataBits = DataBits.Eight;

    public static void Initialize(string name, string baud, 
        string par, string dBits, string sBits)
    {
        portName = name;
        baudRate = (BaudRates)Enum.Parse(typeof(BaudRates), baud);
        parity = (Parity)Enum.Parse(typeof(Parity), par);
        dataBits = (DataBits)Enum.Parse(typeof(DataBits), dBits);
        stopBits = (StopBits)Enum.Parse(typeof(StopBits), sBits);
    }

    public static bool Open()
    {
        if (comPort.IsOpen) comPort.Close();

        comPort.PortName = portName;
        comPort.BaudRate = (int)baudRate;
        comPort.Parity = parity;
        comPort.DataBits = (int)dataBits;
        comPort.StopBits = stopBits;

        try
        {
            comPort.Open();
            comPort.ReadTimeout = 1000;
            comPort.WriteTimeout = 1000;
            return true;
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
            return false;
        }
    }

    public static void Close()
    {
        if (comPort.IsOpen) comPort.Close();
    }
}

4.3 HEX 字符串收发

工业设备常使用十六进制指令,如继电器控制:A0 01 01 A2

// HEX 字符串转字节数组
public static byte[] StringToByteArray(string hexString)
{
    int length = hexString.Length / 2;
    byte[] bytes = new byte[length];
    for (int i = 0; i < length; ++i)
    {
        bytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
    }
    return bytes;
}

// 发送 HEX 字符串(带校验)
public static void SendHexString(string hexString)
{
    if (string.IsNullOrEmpty(hexString))
        throw new ArgumentException("Hex string cannot be null or empty.");
    if (hexString.Length % 2 != 0)
        throw new ArgumentException("Hex string must have an even number of characters.");

    // 验证是否为合法十六进制字符
    foreach (char c in hexString)
    {
        if (!Uri.IsHexDigit(c))
            throw new ArgumentException($"Invalid hex character: '{c}'");
    }

    byte[] buffer = StringToByteArray(hexString);
    Write(buffer);
}

4.4 读取数据

public static bool Read(ref string result)
{
    int bytesToRead = comPort.BytesToRead;
    byte[] recvData = new byte[bytesToRead];
    comPort.Read(recvData, 0, bytesToRead);
    
    if (recvData.Length > 0)
    {
        result = Encoding.UTF8.GetString(recvData);
        return true;
    }
    return false;
}

五、串口参数配置界面

SerialOptionForm 提供了完整的串口参数设置界面。

5.1 下拉框数据绑定

private void InitCmb()
{
    // 绑定端口号
    SerialPortHelper.SetPortNameValues(comboBox_Com.Items);
    comboBox_Com.DropDownStyle = ComboBoxStyle.DropDownList;
    
    // 波特率
    SerialPortHelper.SetBauRateValues(comboBox_BaudRates.Items);
    comboBox_BaudRates.DropDownStyle = ComboBoxStyle.DropDownList;
    
    // 数据位
    SerialPortHelper.SetDataBitsValues(comboBox_DataBits.Items);
    comboBox_DataBits.DropDownStyle = ComboBoxStyle.DropDownList;
    
    // 校验位
    SerialPortHelper.SetParityValues(comboBox_Parity.Items);
    comboBox_Parity.DropDownStyle = ComboBoxStyle.DropDownList;
    
    // 停止位
    SerialPortHelper.SetStopBitValues(comboBox_StopBits.Items);
    comboBox_StopBits.DropDownStyle = ComboBoxStyle.DropDownList;
}

5.2 打开/关闭串口

private void button_Open_Click(object sender, EventArgs e)
{
    SerialPortHelper.Initialize(comboBox_Com.Text,
        comboBox_BaudRates.Text,
        comboBox_Parity.Text,
        comboBox_DataBits.Text,
        comboBox_StopBits.Text);

    if (!SerialPortHelper.Open())
    {
        SerialStateLight.State = UILightState.Off;
        MessageBox.Show("失败", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
    }
    else
    {
        SerialStateLight.State = UILightState.On;
        MessageBox.Show("成功");
    }
}

private void button_Close_Click(object sender, EventArgs e)
{
    SerialPortHelper.Close();
    textBox_Recive.Text = "";
    SerialStateLight.State = UILightState.Off;
}

六、串口数据接收线程

为了不阻塞 UI,数据接收通常在后台线程中进行:

private Task task;
private CancellationTokenSource cts;

private void StartReadTask()
{
    cts = new CancellationTokenSource();
    task = new Task(() =>
    {
        while (true)
        {
            if (cts.IsCancellationRequested) break;

            string result = string.Empty;
            bool isGet = SerialPortHelper.Read(ref result);
            if (isGet)
            {
                this.Invoke(new Action(() =>
                {
                    textBox_Recive.Text = result;
                }));
            }
            Thread.Sleep(500);
        }
    }, cts.Token);
    task.Start();
}

private void CloseReadTask()
{
    if (cts != null)
    {
        cts.Cancel();
        task.Wait();
        cts.Dispose();
    }
}

七、常见问题与排查

Q1:串口打开失败?

  • 检查 COM 口是否被其他程序占用
  • 检查是否有权限(以管理员身份运行)
  • 检查 USB 转串口驱动是否安装

Q2:发送成功但设备无响应?

  • 检查波特率等参数是否与设备一致
  • 确认 HEX 指令格式正确(如校验和)
  • 检查接线:TX 接 RX,RX 接 TX,GND 接 GND

Q3:接收数据乱码?

  • 波特率不匹配是最常见原因
  • 确认编码格式(UTF-8 / ASCII / GB2312)

八、总结

本文完成了串口通信模块的讲解:

  1. RS232/RS485 基础与工业应用场景
  2. SerialPort 核心 API(打开、发送、接收、关闭)
  3. SerialPortHelper 静态封装类
  4. HEX 收发 与数据校验
  5. 后台接收线程 与 UI 更新

下篇预告:《从零打造 C# 工业视觉检测系统(八):Modbus RTU 协议解析与 PLC 剔除控制实战》

我们将基于串口封装,实现工业标准的 Modbus RTU 协议,完成不合格品的自动剔除控制。


欢迎点赞、收藏、关注,你的支持是我持续输出的动力!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在世修行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值