从零打造 C# 工业视觉检测系统(八):Modbus RTU 协议解析与 PLC 剔除控制实战
系列标签 :
C#ModbusRTUNModbus4PLC继电器控制本文目标:掌握 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);
}
注意:当前代码中剔除逻辑被注释掉了,因为:
- 不同现场的剔除机构不同(气吹/推杆/翻转板)
- 时序参数需要根据产线速度现场调试
- 调试阶段直接剔除可能损坏设备
五、Modbus 通信调试技巧
5.1 使用 Modbus Poll / Modbus Slave
这两款工具是 Modbus 开发的必备利器:
- Modbus Poll:作为 Master,主动读写从设备
- Modbus Slave:模拟从设备,供上位机测试
5.2 串口抓包
使用串口监视工具(如 AccessPort、CommMonitor)可以抓取串口上的原始数据,验证帧格式是否正确。
5.3 常见错误码
| 异常码 | 含义 |
|---|---|
| 0x01 | 非法功能码 |
| 0x02 | 非法数据地址 |
| 0x03 | 非法数据值 |
| 0x04 | 从机故障 |
六、总结
本文讲解了工业通信的核心技术:
- Modbus RTU 协议帧结构与功能码
- NModbus4 库的快速集成
- 寄存器读写:读取传感器数据、控制执行机构
- 剔除时序:delay_time 和 hold_time 的工程设计
下篇预告:《从零打造 C# 工业视觉检测系统(九):工厂模式实现 SQL Server / MySQL / SQLite 三库切换》
我们将深入数据持久化层,讲解如何设计支持多种数据库的数据访问架构。
欢迎点赞、收藏、关注,你的支持是我持续输出的动力!