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

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

系列标签C# Modbus RTU NModbus4 PLC 继电器控制

本文目标:掌握 Modbus RTU 协议,实现 C# 与 PLC/继电器的寄存器读写和剔除控制


一、前言

在工业现场,PLC(可编程逻辑控制器)是控制产线设备的"大脑"。视觉检测系统检测到不合格品后,需要通知 PLC 执行剔除动作。Modbus 是最广泛使用的工业通信协议之一,它简单、开放、免费,几乎所有的 PLC 都支持。

本文将讲解 Modbus RTU 协议的核心原理,以及如何用 C# 实现剔除控制。


二、Modbus 协议概述

2.1 Modbus 变种

类型 物理层 特点
Modbus RTU RS232/RS485 二进制格式,紧凑高效,工业最常用
Modbus ASCII RS232/RS485 ASCII 字符格式,可读但效率低
Modbus TCP 以太网 基于 TCP/IP,适合局域网

本项目使用 Modbus RTU,通过串口与继电器模块通信。

2.2 Modbus RTU 帧结构

一条 Modbus RTU 消息包含 4 个部分:

复制代码
+--------+----------+-------+---------+
| 从机地址 | 功能码   | 数据   | CRC校验 |
| 1字节   | 1字节    | N字节  | 2字节   |
+--------+----------+-------+---------+
  • 从机地址:0-247,0 为广播地址,1-247 为单个设备
  • 功能码:定义操作类型
  • 数据:请求参数或返回数据
  • CRC 校验:循环冗余校验,确保数据完整性

2.3 常用功能码

功能码 名称 说明
0x01 读线圈 读取开关量输出状态
0x02 读离散输入 读取开关量输入状态
0x03 读保持寄存器 读取模拟量/参数
0x05 写单个线圈 控制单个开关
0x06 写单个寄存器 写入单个参数
0x0F 写多个线圈 批量控制开关
0x10 写多个寄存器 批量写入参数

三、NModbus4 库实战

手动拼接 Modbus 帧容易出错,本项目使用 NModbus4 库简化开发。

3.1 安装

bash 复制代码
Install-Package NModbus4

3.2 创建 Modbus RTU Master

csharp 复制代码
using Modbus.Device;

public static class SerialPortHelper
{
    public static SerialPort comPort = new SerialPort();
    public static ModbusSerialMaster master;
    public static byte slaveId = 1;

    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;

            // 创建 Modbus RTU Master
            master = ModbusSerialMaster.CreateRtu(comPort);
            master.Transport.ReadTimeout = 800;
            master.Transport.WriteTimeout = 800;
            
            return true;
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
            return false;
        }
    }
}

3.3 读取保持寄存器

csharp 复制代码
public static double Read(ushort startAddress, ushort number)
{
    double result = 0;
    
    // 从 slaveId=1 的设备,读取起始地址为 startAddress 的 number 个寄存器
    ushort[] data = master.ReadHoldingRegisters(slaveId, startAddress, number);
    
    int originalData = int.Parse(data[0].ToString());
    result = Math.Floor(originalData / 10.0) / 10.0;
    
    return result;
}

3.4 继电器控制(写线圈)

继电器模块通常将每个输出映射为一个线圈地址:

csharp 复制代码
// 打开继电器(对应剔除动作启动)
private void TurnOnKick()
{
    SerialPortHelper.SendHexString("A00101A2");
}

// 关闭继电器(对应剔除动作结束)
private void TurnOffKick()
{
    SerialPortHelper.SendHexString("A00100A1");
}

指令解析 (以 A0 01 01 A2 为例):

  • A0:帧头
  • 01:从机地址
  • 01:打开指令
  • A2:校验和

注:本项目的继电器模块使用自定义协议,非标准 Modbus。若使用标准 Modbus 继电器,应调用 master.WriteSingleCoil(slaveId, coilAddress, true)


四、剔除控制时序设计

4.1 为什么需要精确的时序?

产线上的产品以一定速度运动,剔除机构(气吹/推杆)需要在正确的时间窗口动作:

  • 太早:打到前一个产品
  • 太晚:目标已错过
  • 持续时间太短:打不干净
  • 持续时间太长:打到后一个产品

4.2 时序参数

Product 表中定义了两个关键参数:

参数 说明 示例值
delay_time 从检测到剔除的延时 0.5 秒
hold_time 剔除动作保持时间 0.3 秒

4.3 剔除逻辑代码

csharp 复制代码
if (ret == 1)
{
    totalCount++;
    if (outSlug.is_qualified == true)
    {
        qualifiedCount++;
    }
    else
    {
        unQualifiedCount++;
        
        // 延时后启动剔除
        Thread.Sleep((int)(double.Parse(Config.delayTime) * 1000));
        TurnOnKick();
        
        // 保持一段时间后关闭
        Thread.Sleep((int)(double.Parse(Config.holdTime) * 1000));
        TurnOffKick();
    }
    
    SaveSlug(outSlug);
}

注意:当前代码中剔除逻辑被注释掉了,因为:

  1. 不同现场的剔除机构不同(气吹/推杆/翻转板)
  2. 时序参数需要根据产线速度现场调试
  3. 调试阶段直接剔除可能损坏设备

五、Modbus 通信调试技巧

5.1 使用 Modbus Poll / Modbus Slave

这两款工具是 Modbus 开发的必备利器:

  • Modbus Poll:作为 Master,主动读写从设备
  • Modbus Slave:模拟从设备,供上位机测试

5.2 串口抓包

使用串口监视工具(如 AccessPort、CommMonitor)可以抓取串口上的原始数据,验证帧格式是否正确。

5.3 常见错误码

异常码 含义
0x01 非法功能码
0x02 非法数据地址
0x03 非法数据值
0x04 从机故障

六、总结

本文讲解了工业通信的核心技术:

  1. Modbus RTU 协议帧结构与功能码
  2. NModbus4 库的快速集成
  3. 寄存器读写:读取传感器数据、控制执行机构
  4. 剔除时序:delay_time 和 hold_time 的工程设计

下篇预告:《从零打造 C# 工业视觉检测系统(九):工厂模式实现 SQL Server / MySQL / SQLite 三库切换》

我们将深入数据持久化层,讲解如何设计支持多种数据库的数据访问架构。


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

相关推荐
sunburn-1 小时前
Java 队列全面详解:从入门到面试实战
java·开发语言·汇编·ide·idea
AI直播技术杂谈1 小时前
AI直播推流链路中延迟优化的通用技术方案
开发语言·人工智能·php
Dxy12393102162 小时前
python 中的 APScheduler 使用详解
开发语言·python
福如意如我心意4 小时前
java虚拟线程和Go协程的区别
开发语言
weixin_440784114 小时前
Java基础常见题
java·开发语言·python·java基础
(Charon)4 小时前
【C++】多线程死锁:产生原因、复现与解决方法
开发语言·c++·算法
跨境小彭4 小时前
Python列表去重的6种高效方法(含保留顺序+性能对比)
开发语言·python
今天AI了吗4 小时前
Python 基础语法从入门到使用详解
开发语言·人工智能·python
花落人散处5 小时前
CDiskClean 开发历程——忽略进程_拖拽功能实现参考
c#