跳到主要内容

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 (设定值), 分别在 40001 和 40002:

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