跳到主要内容

Modbus RTU/TCP 通讯

Darra PLC 通过 ModbusRtuAdapter 支持 Modbus RTU (RS-485) 和 Modbus TCP 两种模式, 可同时作为主站轮询多个从站设备。

支持模式

模式传输层典型场景
Modbus RTURS-485 串口变频器、温控器、仪表
Modbus TCPTCP/IP 网络远程 IO、网关、智能设备

支持功能码

功能码名称操作数据类型
01Read Coils读线圈BOOL
02Read Discrete Inputs读离散输入BOOL
03Read Holding Registers读保持寄存器WORD/DWORD/REAL
04Read Input Registers读输入寄存器WORD/DWORD/REAL
05Write Single Coil写单个线圈BOOL
06Write Single Register写单个寄存器WORD
15Write Multiple Coils写多个线圈BOOL[]
16Write Multiple Registers写多个寄存器WORD[]

连接配置

Modbus RTU

{
"protocol": "ModbusRTU",
"serialPort": "COM3",
"baudRate": 9600,
"dataBits": 8,
"parity": "Even",
"stopBits": 1,
"slaveId": 1,
"timeout": 1000
}

Modbus TCP

{
"protocol": "ModbusTCP",
"host": "192.168.1.100",
"port": 502,
"slaveId": 1,
"timeout": 3000,
"keepAlive": true
}

PLC 变量映射

Modbus 地址与 PLC 变量之间通过 PLCDataServer 建立映射:

Modbus 地址范围功能码PLC 地址数据类型
00001 ~ 0999901/05/15MX0.0 ~ MX1249.7BOOL
10001 ~ 1999902IX0.0 ~ IX1249.7BOOL (只读)
30001 ~ 3999904IW0 ~ IW9998WORD (只读)
40001 ~ 4999903/06/16MW0 ~ MW9998WORD
字节序

Modbus 协议默认大端序 (Big-Endian)。对于 32 位浮点数 (REAL), 支持配置字节/字序:

  • AB CD (大端, 默认)
  • CD AB (字交换)
  • BA DC (字节交换)
  • DC BA (小端)

轮询配置

支持多从站、多区域的分组轮询:

// 添加轮询组
var group = new ModbusPollGroup
{
SlaveId = 1,
FunctionCode = 3, // 读保持寄存器
StartAddress = 0,
Quantity = 100,
PollIntervalMs = 100, // 100ms 轮询周期
Priority = PollPriority.High
};
adapter.AddPollGroup(group);

轮询策略:

  • 高优先级组: 50~100ms (控制数据)
  • 中优先级组: 200~500ms (状态监控)
  • 低优先级组: 1000~5000ms (参数读取)

自动重连

通讯中断处理

Modbus 适配器在连接断开后自动重连, 采用指数退避策略:

  • 首次重试: 1 秒
  • 后续重试: 2s → 4s → 8s → ... → 最大 60s
  • 重连期间所有读写操作返回上次缓存值, 并标记为 Quality.Bad

代码示例

// 创建 Modbus TCP 连接
var adapter = PLCDriverFactory.Create(ProtocolId.ModbusTCP);
await adapter.ConnectAsync(new ConnectionConfig
{
Host = "192.168.1.100",
Port = 502,
SlaveId = 1
});

// 读取保持寄存器 (40001~40010)
var data = await adapter.ReadAsync("40001", 10);
float temperature = BitConverter.ToSingle(data, 0);

// 写入单个寄存器
await adapter.WriteAsync("40001", BitConverter.GetBytes((short)1000));

// 断开连接
await adapter.DisconnectAsync();

Modbus 帧格式

Modbus RTU 帧

| 从站ID (1B) | 功能码 (1B) | 数据 (N B) | CRC16 (2B) |
  • 帧间隔: 3.5 字符时间, 用于区分相邻帧
  • 字符帧: 起始位 1 + 8 数据位 + 校验位 0/1 + 停止位 1/2 = 10/11 位
  • CRC16 多项式: 0xA001 (逆序表示)

Modbus TCP 帧

| 事务 ID (2B) | 协议 ID (2B) | 长度 (2B) | 单元 ID (1B) | 功能码 (1B) | 数据 (N B) |
  • 事务 ID: 客户端分配, 用于匹配响应
  • 协议 ID: 固定 0x0000 (Modbus)
  • 单元 ID: 对应 RTU 的从站 ID, TCP 下作为网关后端从站标识

ASCII (较少用)

每字节用 2 个 ASCII 十六进制字符表示, 帧以 : 开头, 以 \r\n 结尾, 校验用 LRC。常见于老设备或需要调试的场景。

多从站管理

一个 Modbus 适配器实例可管理多个从站 (TCP 同一网关下, RTU 同一串口上):

adapter.AddSlave(new SlaveConfig { SlaveId = 1, Name = "变频器A" });
adapter.AddSlave(new SlaveConfig { SlaveId = 2, Name = "温控器B" });
adapter.AddSlave(new SlaveConfig { SlaveId = 3, Name = "IO模块C" });

// 带从站参数的读取
var data = await adapter.ReadAsync(slaveId: 2, "40001", 10);

主站 / 从站角色

Darra PLC 默认作 主站 (Master, Client) 主动请求。也支持作 从站 (Slave, Server) 被动响应:

var server = new ModbusTcpServer();
server.Start(port: 502);
server.RegisterHoldingRegisters(startAddress: 0, count: 1000);
// 外部客户端读 40001~41000 时, 自动从 PLC 的 MW0~MW999 返回

从站模式下, Modbus 地址与 PLC 地址的映射由 PLCDataServer 管理。PLC 不用写代码, 外部客户端即可访问。

字节序与字序

32 位浮点 (REAL) 和 32 位整数跨 2 个寄存器, 实际字节排列有 4 种:

模式表示备注
ABCDBig-EndianModbus 原生, 多数厂商
CDABWord-SwapSchneider 等部分厂商
BADCByte-Swap极少见
DCBALittle-EndianIntel 本机

连接配置里可指定默认字节序, 也可以在地址配置时按字段覆盖:

{
"byteOrder": "ABCD",
"defaultFloatOrder": "CDAB"
}

数据类型映射

Modbus 表示PLC 类型寄存器数说明
1 CoilBOOL-只读 1 位
1 RegisterWORD / INT1 (16 bit)有无符号看 PLC 声明
2 RegistersDWORD / DINT2 (32 bit)字序看配置
2 RegistersREAL2 (32 bit IEEE 754)字序看配置
4 RegistersLINT / LREAL4 (64 bit)字序看配置
N RegistersSTRINGN (2N 字符)ASCII / UTF-8

读写优化

地址合并

如果要读 40001, 40003, 40005 三个独立位置, 适配器会自动合并为一次 Read 40001 Count=5 的请求, 然后拆出需要的三个值。合并阈值由"合并间隙"参数控制 (默认 ≤ 8 个寄存器)。

轮询优先级

var groups = new[] {
new ModbusPollGroup { Name = "高频控制", Priority = High, PollMs = 50 },
new ModbusPollGroup { Name = "中频监控", Priority = Medium, PollMs = 500 },
new ModbusPollGroup { Name = "低频参数", Priority = Low, PollMs = 5000 }
};

调度器保证高优先级组在设定周期内完成。低优先级组让位。

常见异常码

异常码含义典型原因
01Illegal Function从站不支持该功能码
02Illegal Data Address寄存器地址越界
03Illegal Data Value数据格式不合法
04Slave Device Failure从站内部错误
05Acknowledge请求已接收但未完成 (长任务)
06Slave Device Busy从站忙, 稍后重试
08Memory Parity Error内存奇偶校验错
0AGateway Path Unavailable网关不通
0BGateway Target No Response后端从站无响应

配置案例

案例 1: 读取变频器实际频率

变频器地址 40100 为实际频率 (单位 0.01 Hz):

var data = await adapter.ReadAsync(slaveId: 3, "40100", 1);
short raw = BitConverter.ToInt16(data.Reverse().ToArray(), 0); // 大端
float freqHz = raw * 0.01f;

案例 2: 设定变频器目标频率

目标频率寄存器 40010, 写入前需把 50Hz 换算为 5000:

short raw = (short)(50.0f * 100);
await adapter.WriteAsync(slaveId: 3, "40010", BitConverter.GetBytes(raw).Reverse().ToArray());

案例 3: 读多个温控仪表

10 个温控仪表, 每个有 PV (过程值) 和 SV (设定值), 分别在 4000140002:

for (int id = 1; id <= 10; id++) {
var data = await adapter.ReadAsync(slaveId: id, "40001", 2);
short pv = BitConverter.ToInt16(data.Take(2).Reverse().ToArray(), 0);
short sv = BitConverter.ToInt16(data.Skip(2).Take(2).Reverse().ToArray(), 0);
}

建议改用主动轮询组, 避免在循环里 await。

排错

症状原因处理
CRC 错波特率/校验位不匹配核对串口参数
超时从站 ID 错 / 线缆断Ping 从站 ID
TCP 连接拒绝端口未开 / IP 错Telnet 502
数值是原值的 100 倍或 0.01字节序或 scale 错试 CDAB 或除/乘 100
功能码 01 异常从站固件只支持部分功能码看手册
偶发 0x06 (Busy)从站处理慢加重试 / 降低轮询频率
多从站串扰总线终端电阻缺失RS-485 两端加 120 ohm