通讯协议教程
从零开始学会 Darra PLC 的通讯配置。每个协议都包含:原理说明 → 配置方法 → 代码示例 → 故障排除。 适合刚接触工业通讯的工程师,也适合需要快速查阅的老手。
1. 通讯概述
1.1 Darra 通讯架构长什么样?
Darra PLC 的通讯分三层:
┌─────────────────────────────────────────────────────┐
│ 用户层(IDE / HMI / SCADA / 云) │
├─────────────────────────────────────────────────────┤
│ 通讯服务层 │
│ DarraLink │ RemoteAccess │ OPC UA │ MQTT │
├─────────────────────────────────────────────────────┤
│ 协议适配层 │
│ EtherCAT │ Modbus │ S7 │ MC │ 串口 │ 数据库 │
├─────────────────────────────────────────────────────┤
│ 现场设备 │
│ 伺服驱动器 │ 变频器 │ 传感器 │ 仪表 │ 上位机 │
└─────────────────────────────────────────────────────┘
控制面:走 HSL(High-Speed Communication Library),跑在 Windows Darra.PLC.Service 里,负责 Modbus、S7、MC 这些传统协议。
实时面:走 EtherCAT / DarraSync,跑在内核态驱动里,负责伺服控制和分布式 IO,延迟微秒级。
简单说:控制面"慢但灵活",实时面"快但固定"。
1.2 固定端口一览(不许改)
以下端口是产品常量,UI 不显示、不可编辑:
| 端口 | 协议 | 谁连谁 | 干啥用 |
|---|---|---|---|
| 18821 TCP | DarraLink | IDE → Service | 变量读写、订阅推送、远程部署、热更新 |
| 18443 HTTPS | RemoteAccessServer | IDE → 远程 Service | 远程管理 API |
| 18823 HTTP/WS | HMI Web | 浏览器 → HMI | HMI 页面 + 实时数据推送 |
| 18852 UDP | Discovery | 同广播域设备发现 | 设备扫描兜底 |
| 4840 TCP | OPC UA | SCADA/MES → PLC | 标准 OPC UA 数据访问 |
| 502 TCP | Modbus TCP | 上位机 → PLC | Modbus 通讯 |
1.3 通讯方式:请求-响应 vs 订阅-推送
| 方式 | 谁主动 | 延迟 | 典型协议 |
|---|---|---|---|
| 请求-响应 | 客户端发请求,服务器回响应 | 一次往返 | Modbus、S7、MC、HTTP |
| 订阅-推送 | 客户端订阅,服务器变化时推送 | 最快 | OPC UA 订阅、MQTT、WebSocket |
什么时候选哪个?
- 需要实时控制(如伺服位置):用 EtherCAT(硬实时)
- 需要周期性读数据(如每 100ms 读温度):用请求-响应(Modbus)
- 需要事件通知(如报警触发):用订阅-推送(OPC UA / MQTT)
- 需要跨网络上云:用 MQTT(物联网标准)
1.4 通讯安全
| 安全措施 | 说明 | 适用协议 |
|---|---|---|
| TLS 加密 | 传输层加密,防窃听 | MQTT、OPC UA、HTTPS |
| Bearer Token | JWT/OAuth 令牌认证 | HTTP API |
| 用户名/密码 | 基础认证 | OPC UA、MQTT |
| X.509 证书 | 双向证书验证 | OPC UA |
| 防火墙策略 | 白名单放行固定端口 | 所有协议 |
生产环境最低要求:启用加密 + 认证,禁止匿名访问。
2. DarraLink 协议
2.1 概述
DarraLink 是 Darra 自有的二进制协议,跑在 TCP 18821 端口上,是 IDE 与 Service 之间的主通道。
它能干啥?
- 在线监控 PLC 变量(实时读写)
- 订阅变量变化(推送模式)
- 远程部署项目(下载 .dplc 到现场机)
- 热更新(不停机更新 PLC 程序)
- 诊断信息查询
2.2 连接建立
IDE(工程师电脑) Service(现场工控机)
│ │
│ 1. TCP 连接 18821 │
│─────────────────────────────>│
│ 2. 认证握手 │
│<────────────────────────────>│
│ 3. 心跳(每 5s) │
│<────────────────────────────>│
│ 4. 数据通讯 │
│<────────────────────────────>│
心跳机制:
- IDE 每 5 秒发一次心跳包
- 连续 3 次心跳无响应 → 判定断线 → 自动重连
- 重连策略:指数退避 1s → 2s → 4s → ... → 最大 60s
2.3 变量读写示例
读变量(SCL 中调用):
VAR
DarraLinkClient : DarraLinkClient;
Value : REAL;
Status : DINT;
END_VAR
// 连接
DarraLinkClient.Connect(
Host := '192.168.1.100',
Port := 18821,
Timeout := T#3s
);
// 读取变量 MD100
DarraLinkClient.ReadVariable(
Name := 'MD100',
Value := Value,
Status := Status
);
// 写入变量 MW500
DarraLinkClient.WriteVariable(
Name := 'MW500',
Value := 1500,
Status := Status
);
2.4 订阅推送
VAR
Subscription : DarraLinkSubscription;
END_VAR
// 订阅变量变化
Subscription.Subscribe(
Variables := ['MD100', 'MD104', 'MX0.0'],
Interval := T#500ms, // 采样间隔
OnChange := TRUE // 仅变化时推送
);
// 回调处理
IF Subscription.Updated THEN
// Subscription.Values[0] 是最新的 MD100
ProcessValue(Subscription.Values[0]);
END_IF
2.5 错误处理与重连
VAR
Link : DarraLinkClient;
ReconnectCount : INT := 0;
END_VAR
IF NOT Link.Connected THEN
ReconnectCount := ReconnectCount + 1;
Link.Reconnect();
// 连续 5 次失败就报警
IF ReconnectCount > 5 THEN
Alarm_DarraLinkDown := TRUE;
END_IF
ELSE
ReconnectCount := 0;
Alarm_DarraLinkDown := FALSE;
END_IF
2.6 排错
| 症状 | 原因 | 处理 |
|---|---|---|
| 连接超时 | IP 地址错 / 防火墙拦 18821 | telnet 192.168.1.100 18821 测试 |
| 认证失败 | Token 过期 / 不匹配 | 重新部署项目,刷新 Token |
| 频繁断连 | 网络不稳定 | 检查网线、交换机 |
| 变量读回 0 | 变量名拼写错 | 核对变量表的大小写 |
3. EtherCAT 主站
3.1 EtherCAT 协议原理
EtherCAT(Ethernet for Control Automation Technology)是一种实时工业以太网协议。
核心思想:数据帧"飞过"每个从站时,从站在帧经过时直接读写数据,不经过软件协议栈。
主站 ──[帧]──→ 从站1 ──→ 从站2 ──→ ... ──→ 从站N
↓ ↓
在帧经过时读/写 在帧经过时读/写
为什么这么快?
- 从站硬件(ASIC/FPGA)在帧经过的瞬间处理数据
- 不经过 CPU、不经过软件协议栈
- 周期可以做到 50µs ~ 1ms
3.2 主站配置
第一步:网卡绑定
EtherCAT 需要一张专用网卡,推荐 Intel I210/I211/I350。
# 安装 EtherCAT 驱动(管理员权限)
install_ecat.ps1 -DevSign -Force
# 确认网卡已被接管
netcfg -q DarraRT_Eth
注意:被 DarraRT_Eth 接管的网卡不能同时用于上网。建议双网卡:一张上网,一张 EtherCAT。
第二步:ESI 文件加载
ESI(EtherCAT Slave Information)是从站的"身份证"XML 文件。从厂商网站下载后,导入 IDE:
项目树 → EtherCAT → ESI 管理 → 导入
导入后,IDE 就知道从站支持哪些参数、哪些 PDO。
第三步:PDO 映射
PDO(Process Data Object)是周期性交换的数据。每个从站告诉你"我想发啥、我想收啥"。
从站发(TxPDO):
0x6041:00 Statusword 16 bit
0x6064:00 Actual Position 32 bit
0x6077:00 Torque Actual 16 bit
从站收(RxPDO):
0x6040:00 Controlword 16 bit
0x607A:00 Target Position 32 bit
0x60FF:00 Target Velocity 32 bit
IDE 自动根据 ESI 配置默认 PDO。你需要做的只是:
- 确认 PDO 映射符合你的需求
- 如果有特殊参数,手动添加 SDO 配置
3.3 从站状态机
EtherCAT 从站有四个状态:
INIT → PRE-OP → SAFE-OP → OP
↑ ↑ ↑ ↑
└───────┴────────┴───────┘
(失败时回到前一个状态)
| 状态 | 能干啥 | 啥不能干 |
|---|---|---|
| INIT | 只读寄存器 | 不能通讯 |
| PRE-OP | 邮箱通讯(SDO 配置参数) | PDO 不能用 |
| SAFE-OP | 邮箱 + 读 PDO | 不能写输出 |
| OP | 全部功能 | — |
典型启动流程:
上电 → INIT(自动)→ 主站配置参数 → PRE-OP → 配置 PDO 映射 → SAFE-OP → 下发 DC 时钟 → OP
3.4 分布式时钟(DC)
DC 让所有从站的 SYNC0 信号在纳秒级对齐,是多轴同步的基础。
主站 → 广播参考时钟 → 所有从站校时
主站 → 计算传播延迟 → 下发到每个从站
每个从站 → 按本地时钟生成 SYNC0/SYNC1
配置参数:
- 参考时钟从站:选第一个支持 DC 的从站(通常靠近主站)
- SYNC0 周期:与 PDO 周期一致(典型 1ms)
- SYNC0 相位:默认 0,必要时偏移(如 500µs)
3.5 CoE 邮箱通讯(SDO 读写)
SDO 用于参数配置和非实时数据,不走 PDO:
// 读参数:从站 3 的 Actual Position(0x6064:00)
int pos = master.SdoRead<int>(slaveIndex: 3, index: 0x6064, subIndex: 0);
// 写参数:从站 3 的 Profile Velocity(0x6081:00)
master.SdoWrite(slaveIndex: 3, index: 0x6081, subIndex: 0, value: 100000);
重要:SDO 不是实时的(典型延迟 1~50ms)。启动阶段配参数用 SDO,运行时高频数据走 PDO。
3.6 过程数据(PDO 映射)
PDO 数据在 PLC 程序中直接映射到变量:
PLC 变量 PDO 映射 从站寄存器
Axis1.ActualPos ← 0x6064:00 ← 伺服实际位置
Axis1.TargetPos → 0x607A:00 → 伺服目标位置
Axis1.Status ← 0x6041:00 ← 伺服状态字
Axis1.Control → 0x6040:00 → 伺服控制字
在 SCL 中控制伺服:
VAR
Axis1 : AXIS_REF;
MoveAbs : MC_MoveAbsolute;
END_VAR
// 使能伺服
Axis1.ControlWord := 16#0006; // Shutdown
Axis1.ControlWord := 16#0007; // Switch On
Axis1.ControlWord := 16#000F; // Enable Operation
// 绝对定位
MoveAbs(
Axis := Axis1,
Position := 100000, // 单位 counts
Velocity := 50000,
Acceleration := 100000,
Deceleration := 100000,
Execute := TRUE
);
IF MoveAbs.Done THEN
// 到达目标位置
END_IF
3.7 从站诊断
VAR
Diag : EtherCATDiagnostics;
SlaveStatus : ARRAY[0..63] OF SLAVE_STATUS;
i : INT;
END_VAR
// 读取所有从站状态
Diag.ReadAllSlaveStatus(SlaveStatus := SlaveStatus);
FOR i := 0 TO 63 DO
IF SlaveStatus[i].Online THEN
// 从站 i 在线
IF SlaveStatus[i].State <> ESM_STATE_OP THEN
// 不在 OP 状态,记录原因
LogError('Slave ' + INT_TO_STRING(i) +
' state: ' + INT_TO_HEX(SlaveStatus[i].StateCode));
END_IF
END_IF
END_FOR
3.8 热插拔与冗余
热插拔:EtherCAT 支持运行时插入/拔出从站。主站检测到拓扑变化后自动重新配置。
{
"ethercat": {
"hotConnect": true,
"coldReconnectTimeout": 10000,
"maxSlaves": 64
}
}
线缆冗余(双网卡):
主站 NIC1 ── 从站1 ── 从站2 ── ... ── 从站N ──┐
│
主站 NIC2 ──────────(备用路径)───────────────┘
任意一根网线断,主站自动反转方向,业务不中断。
3.9 配置示例:松下 MINAS A6B 伺服
硬件连接
Darra PLC (Intel I210) ── CAT6 网线 ── MINAS A6B (X2A EtherCAT 口)
步骤 1:导入 ESI 文件
- 从松下官网下载 MINAS A6B ESI XML
- IDE 中:
EtherCAT → ESI 管理 → 导入 - 搜索 "Panasonic MINAS A6B"
步骤 2:配置 PDO
TxPDO(从站发):
- 0x6041:00 Statusword(16 bit)
- 0x6064:00 Actual Position(32 bit)
- 0x6077:00 Torque Actual(16 bit)
- 0x6061:00 Modes of Operation Display(8 bit)
RxPDO(主站发):
- 0x6040:00 Controlword(16 bit)
- 0x607A:00 Target Position(32 bit)
- 0x60FF:00 Target Velocity(32 bit)
- 0x6060:00 Modes of Operation(8 bit)
步骤 3:配置 DC 时钟
SYNC0 周期:1000 µs(1ms)
参考时钟:第一个从站(MINAS A6B)
步骤 4:SCL 控制代码
PROGRAM Main
VAR
// 轴变量
Axis1 : AXIS_REF;
// MC 功能块
Home : MC_Home;
MoveAbs : MC_MoveAbsolute;
MoveVel : MC_MoveVelocity;
Power : MC_Power;
// 状态
IsHomed : BOOL := FALSE;
TargetPos : DINT := 50000;
Speed : DINT := 30000;
END_VAR
// 主程序
Power(
Axis := Axis1,
Enable := TRUE,
Status => Axis1.PowerStatus,
Error => Axis1.PowerError
);
IF Power.Status AND NOT IsHomed THEN
Home(
Axis := Axis1,
Execute := TRUE,
Done => IsHomed
);
END_IF
IF IsHomed THEN
MoveAbs(
Axis := Axis1,
Position := TargetPos,
Velocity := Speed,
Execute := TRUE,
Done => MoveDone
);
END_IF
// 错误处理
IF Axis1.PowerError OR MoveAbs.Error THEN
// 停止输出,报警
Axis1.ControlWord := 16#0006; // Shutdown
Alarm_ServoFault := TRUE;
END_IF
3.10 排错
| 症状 | 原因 | 处理 |
|---|---|---|
| 扫描不到从站 | 网卡未被 DarraRT_Eth 接管 | 设备管理器检查驱动 |
| 卡在 INIT | 线序错 / 从站没通电 | 检查 LED 灯 |
| 切不到 OP | PDO 映射失败 | 查 AL_STATUS_CODE |
| WKC 不匹配 | 帧丢失 / 从站掉线 | 查端口错误计数 |
| DC 不同步 | 参考时钟选错 | 改参考时钟从站 |
| 周期抖动大 | CPU 隔离不足 | 增加隔离核心数 |
| SDO 超时 | 从站忙 / 索引不对 | 确认对象字典支持 |
4. Modbus TCP/RTU
4.1 Modbus 协议原理
Modbus 是工业通讯的"通用语"——几乎每个设备都支持。它分两种:
| 模式 | 传输层 | 速度 | 距离 |
|---|---|---|---|
| Modbus TCP | 以太网 TCP 502 端口 | 快 | 100m(以太网) |
| Modbus RTU | RS-485 串口 | 慢 | 1200m |
主从模型:
主站(Darra PLC) 从站(变频器/温控器/仪表)
│ │
│ ── 请求(功能码 + 地址 + 数据)──>│
│ <── 响应(功能码 + 数据)───────│
4.2 功能码
| 功能码 | 名称 | 读/写 | 数据类型 |
|---|---|---|---|
| 01 | Read Coils | 读 | BOOL(线圈) |
| 02 | Read Discrete Inputs | 读 | BOOL(只读输入) |
| 03 | Read Holding Registers | 读 | WORD/DWORD/REAL |
| 04 | Read Input Registers | 读 | WORD/DWORD/REAL |
| 05 | Write Single Coil | 写 | 单个 BOOL |
| 06 | Write Single Register | 写 | 单个 WORD |
| 15 | Write Multiple Coils | 写 | 批量 BOOL |
| 16 | Write Multiple Registers | 写 | 批量 WORD |
| 23 | Read/Write Multiple Registers | 读写 | 批量 WORD |
4.3 地址映射
Modbus 地址与 PLC 变量通过 PLCDataServer 建立映射:
| Modbus 地址范围 | 功能码 | PLC 地址 | 数据类型 |
|---|---|---|---|
| 00001 ~ 09999 | 01/05/15 | MX0.0 ~ MX1249.7 | BOOL(线圈) |
| 10001 ~ 19999 | 02 | IX0.0 ~ IX1249.7 | BOOL(只读输入) |
| 30001 ~ 39999 | 04 | IW0 ~ IW9998 | WORD(只读) |
| 40001 ~ 49999 | 03/06/16 | MW0 ~ MW9998 | WORD(最常用) |
最常用的是 40001 开始的保持寄存器,读写都行。
4.4 Modbus TCP 客户端
配置
{
"protocol": "ModbusTCP",
"host": "192.168.1.100",
"port": 502,
"slaveId": 1,
"timeout": 3000,
"keepAlive": true
}
代码示例
// 创建 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);
// 写单个寄存器 40001
await adapter.WriteAsync("40001", BitConverter.GetBytes((short)1000));
// 断开
await adapter.DisconnectAsync();
SCL 中调用
VAR
MB : ModbusTCP_Client;
TempValue : REAL;
Status : DINT;
END_VAR
// 配置连接
MB.Configure(
Host := '192.168.1.100',
Port := 502,
SlaveID := 1,
Timeout := T#3s
);
// 连接
MB.Connect();
// 读 40001(保持寄存器,实际是地址 0)
MB.ReadHoldingRegisters(
StartAddr := 0,
Quantity := 2, // REAL 占 2 个寄存器
Data := TempValue,
Status := Status
);
// 写 MW500 到 40001
MB.WriteSingleRegister(
Address := 0,
Value := MW500
);
4.5 Modbus RTU 串口通信
配置
{
"protocol": "ModbusRTU",
"serialPort": "COM3",
"baudRate": 9600,
"dataBits": 8,
"parity": "Even",
"stopBits": 1,
"slaveId": 1,
"timeout": 1000
}
硬件连接
Darra PLC(RS-485 口) 从站设备
A (+) ───────────────── A (+)
B (-) ───────────────── B (-)
GND ─────────────────── GND
RS-485 布线注意:
- 总线两端各加 120 ohm 终端电阻
- 最多 32 个节点(标准驱动器)
- 用菊花链拓扑,不要星形
4.6 从站模式
Darra PLC 也可以作为 Modbus 从站,让上位机来读写:
var server = new ModbusTcpServer();
server.Start(port: 502);
server.RegisterHoldingRegisters(startAddress: 0, count: 1000);
// 外部客户端读 40001~41000 时,自动从 PLC 的 MW0~MW999 返回
PLC 不用写代码,外部客户端即可通过 Modbus 访问变量。
4.7 字节序问题
Modbus 默认大端序(Big-Endian)。32 位浮点数(REAL)跨 2 个寄存器,字节排列有 4 种:
| 模式 | 说明 | 厂商 |
|---|---|---|
| ABCD | 大端序(默认) | 大多数厂商 |
| CDAB | 字交换 | Schneider 等 |
| BADC | 字节交换 | 极少 |
| DCBA | 小端序 | Intel 本机 |
如果读回来的数值是预期的 100 倍或 0.01 倍,八成是字节序不对。
4.8 示例:读取变频器数据
场景:读取三菱变频器的当前频率(寄存器 40100,单位 0.01Hz)
VAR
MB : ModbusTCP_Client;
RawData : WORD;
FrequencyHz : REAL;
END_VAR
// 读 40100(实际地址 99)
MB.ReadHoldingRegisters(
StartAddr := 99,
Quantity := 1,
Data := RawData
);
// 换算:原始值 5000 = 50.00 Hz
FrequencyHz := WORD_TO_REAL(RawData) / 100.0;
设定目标频率(50Hz = 5000):
MB.WriteSingleRegister(
Address := 9, // 40010 的目标频率地址
Value := 5000 // 50.00 Hz
);
4.9 排错
| 症状 | 原因 | 处理 |
|---|---|---|
| 连接超时 | IP 错 / 端口 502 被拦 | telnet 192.168.1.100 502 测试 |
| CRC 校验错 | 串口参数不匹配 | 核对波特率、校验位 |
| 异常码 02 | 寄存器地址越界 | 确认从站支持的地址范围 |
| 异常码 03 | 数据值不合法 | 检查写入值格式 |
| 数值是 100 倍 | 字节序不对 | 试 CDAB 模式 |
| 偶发 0x06 Busy | 从站处理慢 | 降低轮询频率 |
| RS-485 串扰 | 终端电阻缺失 | 两端加 120 ohm |
5. OPC UA
5.1 OPC UA 协议概述
OPC UA(Unified Architecture)是工业 4.0 的"普通话"——标准化的数据访问接口。
核心概念:
| 概念 | 说明 | 类比 |
|---|---|---|
| 地址空间 | 所有数据的树形结构 | 像文件系统 |
| 节点 | 地址空间里的每个"东西" | 像文件/文件夹 |
| 变量节点 | 存放具体数值 | 像文件内容 |
| 方法节点 | 可调用的功能 | 像可执行程序 |
| 订阅 | 数据变化时自动推送 | 像文件监听 |
5.2 地址空间
PLC 数据自动映射为 OPC UA 节点:
Objects/
├── Server/ 服务器诊断
├── Darra/
│ ├── PLC/
│ │ ├── Variables/ PLC 变量
│ │ │ ├── MW0 ns=2;s=MW0
│ │ │ ├── MD100 ns=2;s=MD100
│ │ │ └── ...
│ │ ├── IO/ IO 点位
│ │ ├── Programs/ PLC 程序状态
│ │ └── Alarms/ 报警事件源
│ └── Robot/
│ ├── Joint[0..5]/ 关节状态
│ └── TCP/ 工具中心点
5.3 变量读/写/订阅
读取变量
var client = OpcUaClientFactory.Create("opc.tcp://192.168.1.100:4840");
await client.ConnectAsync();
// 读取单个变量
var value = await client.ReadAsync("ns=2;s=MD100");
// 批量读取
var batchResult = await client.ReadBatchAsync(
new[] { "ns=2;s=MD100", "ns=2;s=MD104", "ns=2;s=MX0.0" }
);
写入变量
await client.WriteAsync("ns=2;s=MW500", 1500);
订阅变化
await client.SubscribeAsync(
nodeId: "ns=2;s=MD100",
samplingInterval: TimeSpan.FromMilliseconds(250),
callback: (id, val) => {
Console.WriteLine($"{id} = {val}");
}
);
5.4 服务器配置
{
"opcua": {
"enabled": true,
"port": 4840,
"serverName": "Darra PLC OPC UA Server",
"securityPolicy": "Basic256Sha256",
"maxSessions": 50,
"maxSubscriptions": 200,
"publishInterval": 1000,
"historyEnabled": true,
"historyMaxDays": 30
}
}
5.5 安全模式
| 安全策略 | 加密 | 签名 | 推荐度 |
|---|---|---|---|
| None | 无 | 无 | ❌ 仅调试 |
| Basic256Sha256 | AES-256 | SHA-256 | ✅ 推荐 |
| Aes128_Sha256_RsaOaep | AES-128 | SHA-256 | ✅ 兼容性好 |
| Aes256_Sha256_RsaPss | AES-256 | SHA-256+PSS | 🔒 最高安全 |
5.6 用户认证
| 身份类型 | 实现 | 适用场景 |
|---|---|---|
| 匿名 | 无 | 仅内网调试 |
| 用户名/密码 | 明文或加密 | 中等安全 |
| X.509 证书 | 证书+私钥 | 企业级 |
5.7 高可用(Part 4 HA)
OPC UA 支持 6 种冗余模式,实现服务器主备切换:
| 冗余模式 | 说明 | 切换时间 |
|---|---|---|
| 冷备 | 备机不运行,手动切换 | 分钟级 |
| 温备 | 备机运行但不提供服务 | 秒级 |
| 热备 | 备机同步数据,自动切换 | < 1s |
| 透明 | 多服务器负载均衡 | 零中断 |
| 镜像 | 完全同步,任意切换 | 零中断 |
| 混合 | 以上组合 | 按需 |
配置示例:
{
"opcua": {
"ha": {
"enabled": true,
"mode": "HotStandby",
"peerServers": [
"opc.tcp://192.168.1.100:4840",
"opc.tcp://192.168.1.101:4840"
],
"heartbeatInterval": 1000,
"failoverDelay": 5000
}
}
}
5.8 示例:连接 SCADA 系统
场景:SCADA 系统通过 OPC UA 读取 Darra PLC 的变量
SCADA 侧配置:
端点:opc.tcp://192.168.1.100:4840
安全策略:Basic256Sha256
认证:用户名/密码(operator / ****)
Darra PLC 侧准备:
- 确认 OPC UA 服务已启用(端口 4840)
- 配置用户账号(IDE → 通讯 → OPC UA → 用户管理)
- 导入 SCADA 的客户端证书(首次连接时)
测试工具:
- UaExpert(Unified Automation,免费)
- Prosys OPC UA Browser(免费)
5.9 排错
| 症状 | 原因 | 处理 |
|---|---|---|
| 连不上 | 端口 4840 被拦 | 防火墙放行 |
| 0x80440000 BadIdentityTokenInvalid | 账号密码错 | 重新输入 |
| 订阅不推送 | KeepAlive 太小 | 增加 KeepAlive 间隔 |
| 大量变量 CPU 高 | Sampling 太密 | 增大采样间隔 |
| 历史数据为空 | 未启用或超保留期 | 检查 historyEnabled |
| 证书错误 | 未信任客户端证书 | 手工信任 |
6. MQTT
6.1 MQTT 协议原理
MQTT 是发布/订阅模式的物联网协议,适合 PLC 上云场景。
┌──────────┐
PLC 发布数据 ───────────>│ MQTT │<── SCADA 订阅
│ Broker │<── 云平台订阅
手机 APP 订阅 <──────────│(EMQX等) │─── 时序数据库订阅
└──────────┘
核心概念:
- Topic(主题):数据的"标签",如
darra/plc/001/temperature - Publish(发布):发送数据到某个 Topic
- Subscribe(订阅):接收某个 Topic 的数据
- QoS(服务质量):0=最多一次,1=至少一次,2=恰好一次
6.2 客户端配置
{
"protocol": "MQTT",
"broker": "tcp://192.168.1.200:1883",
"clientId": "DarraPLC_001",
"username": "plc_user",
"password": "********",
"keepAlive": 60,
"cleanSession": false,
"tls": {
"enabled": false
},
"lastWill": {
"topic": "darra/plc/001/status",
"payload": "{\"online\": false}",
"qos": 1,
"retain": true
}
}
6.3 发布 PLC 变量
SCL 中发布数据到云端:
VAR
MqttClient : MQTT_Client;
Payload : STRING(256);
END_VAR
// 构建 JSON 数据
Payload := '{"temperature":' + REAL_TO_STRING(MD100) +
',"pressure":' + REAL_TO_STRING(MD104) +
',"running":' + BOOL_TO_STRING(MX0.0) + '}';
// 发布到 Topic
MqttClient.Publish(
Topic := 'darra/plc/001/telemetry',
Payload := Payload,
QoS := MQTT_QOS_0,
Retain := FALSE
);
6.4 订阅外部数据
VAR
MqttClient : MQTT_Client;
ReceivedTopic : STRING(128);
ReceivedPayload : STRING(256);
END_VAR
// 订阅云端指令
MqttClient.Subscribe(
Topic := 'darra/plc/001/cmd/#',
QoS := MQTT_QOS_1
);
// 处理收到的消息
IF MqttClient.MessageReceived THEN
ReceivedTopic := MqttClient.Topic;
ReceivedPayload := MqttClient.Payload;
// 解析指令
IF ReceivedTopic = 'darra/plc/001/cmd/start' THEN
StartProduction();
ELSIF ReceivedTopic = 'darra/plc/001/cmd/stop' THEN
StopProduction();
END_IF
END_IF
6.5 遗嘱消息(Last Will)
遗嘱消息是 PLC 异常断线时,Broker 代发的"死亡通知":
{
"lastWill": {
"topic": "darra/plc/001/status",
"payload": "{\"online\":false,\"device\":\"DarraPLC_001\"}",
"qos": 1,
"retain": true
}
}
同时,PLC 正常运行时定期发送"在线"消息:
// 每 10 秒发一次在线心跳
IF Every10Seconds THEN
MqttClient.Publish(
Topic := 'darra/plc/001/status',
Payload := '{"online":true,"device":"DarraPLC_001"}',
QoS := MQTT_QOS_1,
Retain := TRUE
);
END_IF
上位系统通过订阅
darra/plc/001/status就知道 PLC 是否在线。
6.6 TLS 加密
{
"broker": "ssl://mqtt.example.com:8883",
"tls": {
"enabled": true,
"caFile": "certs/ca.crt",
"certFile": "certs/client.crt",
"keyFile": "certs/client.key",
"minVersion": "TLS1.2"
}
}
6.7 示例:PLC 数据上云
场景:PLC 每 5 秒发温度 + 压力数据到云平台 EMQX Broker
Darra PLC 侧:
PROGRAM MQTT_Upload
VAR
MQTT : MQTT_Client;
Telemetry : STRING(512);
LastSend : TIME;
END_VAR
// 每 5 秒发一次
IF T_PLC_ELAPSED - LastSend >= T#5s THEN
LastSend := T_PLC_ELAPSED;
// 组装 JSON
Telemetry := '{' +
'"ts":"' + FORMAT_DATE(NOW(), 'YYYY-MM-DDTHH:MM:SS') + '",' +
'"temp":' + REAL_TO_STRING(MD100) + ',' +
'"pressure":' + REAL_TO_STRING(MD104) + ',' +
'"flow":' + REAL_TO_STRING(MD108) +
'}';
MQTT.Publish(
Topic := 'factory/line01/plc01/data',
Payload := Telemetry,
QoS := MQTT_QOS_0
);
END_IF
云平台侧(EMQX 规则引擎):
SQL: SELECT * FROM "factory/+/+/data"
动作:写入 InfluxDB(时序数据库)
6.8 排错
| 症状 | 原因 | 处理 |
|---|---|---|
| 连接被拒绝 Code=5 | 用户名密码错 | 检查 Broker 侧账号 |
| 连接正常但无消息 | Topic 拼写错(区分大小写) | 核对 Topic |
| 消息重复 | QoS 1 重试导致 | 客户端幂等处理 |
| TLS 握手失败 | CA 证书不匹配 | 检查证书链 |
| 断线后恢复慢 | CleanSession=false 时恢复订阅 | 等待重连完成 |
7. Siemens S7 协议
7.1 S7 协议概述
S7 是西门子 PLC 的通讯协议,跑在 TCP 102 端口上。
应用层: S7 PDU
会话层: COTP (ISO 8073)
传输层: TPKT (RFC 1006)
网络层: TCP (Port 102)
7.2 支持型号
| 系列 | 机架/槽号 | 注意事项 |
|---|---|---|
| S7-200 Smart | Rack=0, Slot=0 | 直连 |
| S7-300 | Rack=0, Slot=2 | MPI/PROFINET |
| S7-400 | Rack=0, Slot=3 | 机架号+槽号 |
| S7-1200 | Rack=0, Slot=0/1 | 需关优化块访问 |
| S7-1500 | Rack=0, Slot=0/1 | 需关优化块访问 + 开 PUT/GET |
7.3 连接配置
{
"protocol": "S7",
"host": "192.168.1.10",
"port": 102,
"rack": 0,
"slot": 1,
"plcType": "S7-1500",
"timeout": 3000
}
7.4 读取 DB 块
var adapter = PLCDriverFactory.Create(ProtocolId.SiemensS7);
await adapter.ConnectAsync(new ConnectionConfig
{
Host = "192.168.1.10",
Port = 102,
Rack = 0,
Slot = 1
});
// 读 DB1.DBW0(INT16)
var data = await adapter.ReadAsync("DB1.DBW0", 2);
short value = BitConverter.ToInt16(data.Reverse().ToArray(), 0);
// 读 DB1.DBD4(REAL)
var realData = await adapter.ReadAsync("DB1.DBD4", 4);
float realValue = BitConverter.ToSingle(realData.Reverse().ToArray(), 0);
7.5 写入 DB 块
// 写 DB1.DBW10 = 1500
await adapter.WriteAsync("DB1.DBW10",
BitConverter.GetBytes((short)1500).Reverse().ToArray());
// 写 DB1.DBD4 = 3.14
await adapter.WriteAsync("DB1.DBD4",
BitConverter.GetBytes(3.14f).Reverse().ToArray());
注意:S7 是大端序,
Reverse()是必须的。
7.6 批量读写优化
var batch = adapter.CreateBatch();
batch.Add("DB1.DBW0", 2); // INT
batch.Add("DB1.DBD4", 4); // REAL
batch.Add("M10.0", 1); // BOOL
var results = await batch.ReadAsync();
// 一次往返搞定,比三次单独请求快 3 倍
7.7 示例:与 S7-1200 交换数据
S7-1200 侧准备(TIA Portal)
- DB 块属性 → 取消勾选"优化的块访问"
- CPU 属性 → 防护与安全 → 允许 PUT/GET 通讯
- 下载配置到 CPU
Darra PLC 侧
VAR
S7 : S7_Client;
RecipeData : ARRAY[0..9] OF INT;
i : INT;
END_VAR
// 连接 S7-1200
S7.Connect(
Host := '192.168.1.10',
Rack := 0,
Slot := 0
);
// 批量读取 DB1.DBW0~DBW18(10 个 INT)
FOR i := 0 TO 9 DO
S7.ReadWord(
DB := 1,
Offset := i * 2,
Value := RecipeData[i]
);
END_FOR
// 把读到的配方数据写到本地变量
MW1000 := RecipeData[0]; // 速度
MW1002 := RecipeData[1]; // 加速度
MW1004 := RecipeData[2]; // 位置
7.8 排错
| 症状 | 原因 | 处理 |
|---|---|---|
| 连接超时 | IP/Rack/Slot 错 | 核对配置 |
| 错误 0x81 | PUT/GET 未开启 | TIA 里开启 |
| 读回 0 | DB 被"优化块访问"保护 | 取消优化 |
| 错误 0x0A | DB 未下载到 CPU | 下载 DB |
| 低速 100ms+ | PDU 小 | 启用批量读 |
8. Mitsubishi MC 协议
8.1 MC 协议概述
MC(MELSEC Communication)是三菱 PLC 的通讯协议,支持 3E/4E 帧格式。
| 模式 | 特点 | 推荐 |
|---|---|---|
| 3E 帧 | 单次请求 | 简单场景 |
| 4E 帧 | 带序列号,支持多连接并发 | ✅ 推荐生产用 |
8.2 连接配置
{
"protocol": "MitsubishiMC",
"host": "192.168.1.20",
"port": 5000,
"frameType": "4E",
"dataFormat": "Binary",
"networkNumber": 0,
"stationNumber": 0,
"timeout": 3000
}
8.3 软元件访问
| 软元件 | 标识 | 地址范围 | 数据类型 |
|---|---|---|---|
| D 数据寄存器 | D | D0~D65535 | WORD |
| M 内部继电器 | M | M0~M65535 | BIT |
| X 输入 | X | X0~X1FFF | BIT(八进制) |
| Y 输出 | Y | Y0~Y1FFF | BIT(八进制) |
| R 文件寄存器 | R | R0~R65535 | WORD |
| ZR 扩展文件寄存器 | ZR | ZR0~ZR65535 | WORD |
X/Y 是八进制地址,X10 实际是第 8 个输入(八进制 10 = 十进制 8)。
8.4 批量读取/写入
var adapter = PLCDriverFactory.Create(ProtocolId.MitsubishiMC);
await adapter.ConnectAsync(new ConnectionConfig
{
Host = "192.168.1.20",
Port = 5000,
FrameType = MCFrameType.Frame4E
});
// 读 D0~D9(10 个 WORD)
var data = await adapter.ReadAsync("D0", 10);
// 写 M100 置位
await adapter.WriteAsync("M100", new byte[] { 0x01 });
// 批量写 D100~D104
short[] values = { 100, 200, 300, 400, 500 };
await adapter.WriteAsync("D100",
values.SelectMany(BitConverter.GetBytes).ToArray());
8.5 示例:与 FX5U 通讯
VAR
MC : MC_Client;
DataBuffer : ARRAY[0..49] OF INT;
i : INT;
END_VAR
// 连接 FX5U
MC.Connect(
Host := '192.168.1.20',
Port := 5000,
FrameType := MC_4E_FRAME
);
// 读取 D100~D149(50 个数据寄存器)
MC.ReadWords(
Device := 'D',
StartAddr := 100,
Count := 50,
Data := DataBuffer
);
// 处理数据
FOR i := 0 TO 49 DO
MW2000[i] := DataBuffer[i];
END_FOR
8.6 排错
| 症状 | 原因 | 处理 |
|---|---|---|
| 连接被拒 | GX Works 里 MC 协议未开 | 检查 PLC 侧设置 |
| 0x4021 | 软元件名错或型号选错 | 核对 plcType |
| 0x4050 | 请求数据量超上限 | 分多次读 |
| ASCII/Binary 不匹配 | PLC 侧通讯数据代码设置 | 两侧一致 |
9. HTTP API
9.1 REST API 端点
Darra PLC 内置 HTTP REST API 服务(默认端口 8080),提供 17 个端点:
| 方法 | 端点 | 说明 |
|---|---|---|
| GET | /api/system/info | 系统信息 |
| GET | /api/system/status | 运行状态 |
| GET | /api/plc/status | PLC 状态 |
| POST | /api/plc/start | 启动 PLC |
| POST | /api/plc/stop | 停止 PLC |
| GET | /api/variables | 变量列表 |
| GET | /api/variables/{name} | 读单个变量 |
| PUT | /api/variables/{name} | 写单个变量 |
| POST | /api/variables/batch | 批量读写 |
| GET | /api/io/state | IO 状态 |
| GET | /api/alarms | 报警列表 |
| POST | /api/alarms/acknowledge | 确认报警 |
| GET | /api/trend/{variable} | 趋势数据 |
9.2 认证方式
| 方式 | Header | 说明 |
|---|---|---|
| 无 | — | 仅调试 |
| Basic | Authorization: Basic base64(user:pass) | 需 HTTPS |
| Bearer | Authorization: Bearer <token> | 推荐 |
| API Key | X-Api-Key: <key> | 固定密钥 |
9.3 读取变量
# 读取 MD100
curl -H "Authorization: Bearer eyJhbGciOi..." \
http://192.168.1.100:8080/api/variables/MD100
{
"success": true,
"data": {
"name": "MD100",
"value": 85.3,
"type": "REAL",
"quality": "Good",
"timestamp": "2026-04-07T10:30:00.000Z"
}
}
9.4 写入变量
# 写入 MW500 = 1500
curl -X PUT \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJhbGciOi..." \
-d '{"value": 1500}' \
http://192.168.1.100:8080/api/variables/MW500
9.5 示例:从 Web 应用控制 PLC
场景:Web 页面点击"启动"按钮,PLC 开始生产
前端代码(JavaScript):
async function startProduction() {
const response = await fetch('http://192.168.1.100:8080/api/plc/start', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
}
});
const result = await response.json();
if (result.success) {
updateStatus('PLC 已启动');
} else {
updateStatus('启动失败: ' + result.error.message);
}
}
async function readTemperature() {
const response = await fetch(
'http://192.168.1.100:8080/api/variables/MD100',
{ headers: { 'Authorization': 'Bearer ' + token } }
);
const data = await response.json();
document.getElementById('temp-display').innerText =
data.data.value.toFixed(1) + ' °C';
}
// 每 2 秒刷新温度显示
setInterval(readTemperature, 2000);
9.6 排错
| 症状 | 原因 | 处理 |
|---|---|---|
| 404 | 路径拼写错 | 核对端点路径 |
| 401 | Token 过期 | 刷新 Token |
| 403 | 权限不足 | 检查用户角色 |
| 429 | 调太频繁了 | 降低调用频率 |
| CORS 错 | 跨域未配置 | 加 CORS 头 |
10. 数据库通讯
10.1 核心定位
PLC 不是数据库。数据库的典型用法是 从数据库读数据进 PLC(如配方、工单), 不是把 PLC 当数据库写频道。高频写会拖慢 PLC 扫描周期。
支持数据库:
- SQLite(默认,本地)
- SQL Server
- MySQL / MariaDB
- PostgreSQL / TimescaleDB
10.2 数据库映射配置
项目树 → 通讯 → 右键 → 添加 → 数据库连接
{
"protocol": "SQLite",
"databasePath": "C:\\Darra\\Data\\plc_data.db",
"walMode": true,
"pollIntervalMs": 1000
}
10.3 写入数据(历史归档)
使用 DB_INSERT FB,仅限低频场景(班次结束、故障归档):
VAR
Insert : DB_INSERT;
END_VAR
// 班次结束时写一次(不是每周期!)
IF ShiftEnd THEN
Insert(
ConnectionName := 'db_recipe',
SQL := 'INSERT INTO shift_summary(ts, station, qty, ok) ' +
'VALUES(:ts, :st, :q, :ok)',
Execute := TRUE
);
Insert.BindDateTime('ts', NOW());
Insert.BindString('st', 'Station01');
Insert.BindInt('q', ShiftTotalQty);
Insert.BindBool('ok', ShiftResult);
END_IF
10.4 读取数据(配方加载)
VAR
Select : DB_SELECT;
Rows : ARRAY[0..99] OF DB_ROW;
Count : INT;
i : INT;
END_VAR
// 读取当前活动配方
Select(
ConnectionName := 'db_recipe',
SQL := 'SELECT speed, accel, pos FROM recipes WHERE active = 1',
Execute := TRUE,
ResultArray := Rows,
MaxRows := 100
);
IF Select.Done THEN
Count := Select.RowCount;
// 把配方参数写入 PLC 变量
FOR i := 0 TO Count - 1 DO
MW1000[i] := Rows[i].Cell[0].AsInt; // speed
MW1002[i] := Rows[i].Cell[1].AsInt; // accel
MW1004[i] := Rows[i].Cell[2].AsInt; // pos
END_FOR
END_IF
10.5 异步数据管道(高频归档)
高频变量归档走异步管道,不阻塞 PLC 扫描:
PLC 变量 → RingBuffer(内存) → 异步 Writer 线程 → 数据库
配置方式:
项目树 → 数据服务 → Historian → 新建采集任务
{
"historian": {
"enabled": true,
"storageType": "TimescaleDB",
"connectionString": "Host=192.168.1.50;Database=historian;...",
"bufferSize": 10000,
"flushIntervalMs": 1000,
"variables": [
{ "name": "MD100", "interval": 100 },
{ "name": "MD104", "interval": 100 },
{ "name": "MX0.0", "interval": 500, "onChange": true }
]
}
}
10.6 示例:生产数据存储到数据库
场景:每班结束时,把产量数据写入 SQL Server
PROGRAM ShiftSummary
VAR
InsertShift : DB_INSERT;
ShiftEnd : BOOL;
ShiftTotalQty : INT;
ShiftOkQty : INT;
ShiftNgQty : INT;
END_VAR
// 班次结束触发(由 HMI 或定时器触发)
IF ShiftEnd THEN
InsertShift(
ConnectionName := 'db_production',
SQL := 'INSERT INTO dbo.shift_report(shift_date, line_no, ' +
'total_qty, ok_qty, ng_qty, create_time) ' +
'VALUES(:dt, :line, :total, :ok, :ng, GETDATE())',
Execute := TRUE
);
InsertShift.BindDate('dt', TODAY());
InsertShift.BindInt('line', 1);
InsertShift.BindInt('total', ShiftTotalQty);
InsertShift.BindInt('ok', ShiftOkQty);
InsertShift.BindInt('ng', ShiftNgQty);
IF InsertShift.Done THEN
// 写入成功,清零班次计数
ShiftTotalQty := 0;
ShiftOkQty := 0;
ShiftNgQty := 0;
ELSE
// 写入失败,记录错误
Alarm_DBWriteFailed := TRUE;
END_IF
END_IF
10.7 排错
| 症状 | 原因 | 处理 |
|---|---|---|
| 连接超时 | 防火墙 / 路由不通 | telnet 测试端口 |
| 认证失败 | 密码过期 | 数据库侧确认 |
| 插入失败"数值超范围" | 类型不匹配 | 检查列类型 |
| 唯一约束冲突 | 主键重复 | 改用 UPSERT |
| 查询慢 | 无索引 | 加索引 |
| 时区错 | 服务器/PLC 时区不同 | 统一 UTC 存储 |
11. 串口通讯
11.1 串口配置
{
"protocol": "SerialPort",
"portName": "COM3",
"baudRate": 9600,
"dataBits": 8,
"parity": "None",
"stopBits": 1,
"handshake": "None",
"readTimeout": 1000,
"writeTimeout": 1000,
"encoding": "ASCII"
}
常用波特率:2400、4800、9600、19200、38400、57600、115200
11.2 ASCII 协议
适合简单文本协议的设备(如称重仪表):
{
"framing": {
"type": "StartStop",
"start": [0x02],
"end": [0x0D]
},
"parser": [
{ "name": "weight", "offset": 0, "length": 6, "type": "ASCII" },
{ "name": "unit", "offset": 6, "length": 2, "type": "ASCII" }
]
}
帧示例:
02 30 30 31 32 35 30 4B 47 0D → "001250KG"
11.3 Modbus RTU(串口)
详见第 4 章 Modbus RTU 部分。串口参数:
{
"protocol": "ModbusRTU",
"serialPort": "COM3",
"baudRate": 9600,
"dataBits": 8,
"parity": "Even",
"stopBits": 1,
"slaveId": 1
}
11.4 自定义协议
对于非标设备,使用自定义协议解析器:
{
"framing": {
"type": "LengthPrefixed",
"header": [0xAA, 0x55],
"lengthOffset": 2,
"lengthSize": 1
},
"parser": [
{ "name": "cmd", "offset": 3, "length": 1, "type": "UInt8" },
{ "name": "value", "offset": 4, "length": 4, "type": "Int32BE" }
],
"validator": {
"type": "CRC16",
"offset": 8,
"polynomial": "Modbus"
}
}
11.5 示例:读取称重仪表数据
场景:串口连接称重仪表,每 500ms 读取当前重量
硬件连接:
Darra PLC(COM3 RS-232)── DB9 串口线 ── 称重仪表
配置:
{
"protocol": "SerialPort",
"portName": "COM3",
"baudRate": 9600,
"dataBits": 7,
"parity": "Even",
"stopBits": 1
}
SCL 代码:
VAR
Serial : SerialPort_Client;
Request : ARRAY[0..7] OF BYTE; // 读取命令
Response : ARRAY[0..31] OF BYTE;
WeightValue : REAL;
i : INT;
END_VAR
// 构建读取命令
Request[0] := 16#01; // 设备地址
Request[1] := 16#03; // 功能码(读寄存器)
Request[2] := 16#00; // 起始地址高
Request[3] := 16#00; // 起始地址低
Request[4] := 16#00; // 寄存器数高
Request[5] := 16#02; // 寄存器数低(读 2 个 = 32 bit)
// CRC16 自动计算
// 每 500ms 发送请求
IF T_PLC_ELAPSED - LastRead >= T#500ms THEN
LastRead := T_PLC_ELAPSED;
Serial.Send(Request);
Serial.Receive(Response, Timeout := T#500ms);
// 解析重量值(4 字节 IEEE 754 浮点)
WeightValue := BYTE_TO_REAL(Response[3..6]);
// 存入 PLC 变量
MD1000 := WeightValue;
END_IF
11.6 排错
| 症状 | 原因 | 处理 |
|---|---|---|
| 打不开串口 | COM 号被占 | 设备管理器检查 |
| 收到乱码 | 波特率/校验位不匹配 | 核对串口参数 |
| 偶发丢字节 | 波特率太高 | 降波特率 |
| RS-485 回显 | 发送时采到自己信号 | 靠时序忽略 |
| USB 串口断开后 COM 号变 | USB 枚举顺序变化 | 设固定 COM 号 |
12. 故障排除(通用)
12.1 连接失败排查清单
按顺序检查,不要跳步:
| # | 检查项 | 工具/命令 |
|---|---|---|
| 1 | 物理层:网线插好?Link 灯亮? | 目视 |
| 2 | IP 可达:能 ping 通吗? | ping 192.168.1.100 |
| 3 | 端口开放:端口通吗? | Test-NetConnection -Port 502 |
| 4 | 协议层:抓包看看 | Wireshark |
| 5 | 应用层:用调试工具测试 | Modbus Poll / UaExpert |
| 6 | 日志层:看 Service 日志 | logs/service_*.log |
12.2 通讯超时处理
通用策略:指数退避重试
首次失败 → 等 1s 重试
再次失败 → 等 2s 重试
再次失败 → 等 4s 重试
再次失败 → 等 8s 重试
...(最大 60s,不再增加)
成功 → 重置为 1s
SCL 实现:
VAR
RetryDelay : TIME := T#1s;
MaxDelay : TIME := T#60s;
RetryCount : INT := 0;
END_VAR
IF NOT Connection.Connected AND RetryTimer.Q THEN
RetryCount := RetryCount + 1;
Connection.Reconnect();
// 指数退避
RetryDelay := RetryDelay * 2;
IF RetryDelay > MaxDelay THEN
RetryDelay := MaxDelay;
END_IF
// 连续 10 次失败报警
IF RetryCount >= 10 THEN
Alarm_CommFault := TRUE;
END_IF
ELSE
// 连接成功,重置
RetryCount := 0;
RetryDelay := T#1s;
Alarm_CommFault := FALSE;
END_IF
12.3 数据错位诊断
症状:读回来的数值看起来不对
可能原因:
- 字节序(最常见):大端/小端不匹配
- 数据类型:INT 当 REAL 读,或反过来
- 地址偏移:Modbus 从 0 开始 vs 从 1 开始
- 缩放因子:原始值 5000 代表 50.00
- 位掩码:多个信号挤在一个寄存器里
排查方法:
// 把原始字节打出来看看
var raw = await adapter.ReadAsync("40001", 4);
Console.WriteLine(BitConverter.ToString(raw));
// 输出: 42-47-0A-3D
// 如果是 REAL 32,这值 ≈ 49.82
// 如果是 DINT,这值 = 1112341565
12.4 网络工具速查
| 工具 | 用途 | 示例 |
|---|---|---|
| ping | 测试 IP 可达 | ping 192.168.1.100 |
| telnet | 测试端口开放 | telnet 192.168.1.100 502 |
| Test-NetConnection | PowerShell 端口测试 | Test-NetConnection -Port 502 |
| netstat | 查看本机端口占用 | `netstat -an |
| Wireshark | 抓包分析协议帧 | 过滤 tcp.port == 502 |
| tcpdump | Linux 抓包 | tcpdump -i eth0 port 502 |
快速诊断三步走:
# 1. 通不通?
ping 192.168.1.100
# 2. 端口开没开?
telnet 192.168.1.100 502
# 3. 服务在不在?
curl -k https://127.0.0.1:18443/api/status
12.5 日志分析
日志位置:%AppData%/DarraPLC/logs/
| 日志文件 | 内容 | 适用场景 |
|---|---|---|
ide_*.log | IDE 操作日志 | 编译、下载、配置问题 |
service_*.log | Service 运行日志 | 通讯、运行时问题 |
debug_info.txt | EtherCAT 调试日志 | 总线问题 |
DbgView | 内核驱动日志 | 驱动问题 |
常用 grep 命令:
# 查看最新错误
grep -i "error\|fail\|timeout" service_2026*.log
# 查看通讯连接/断线记录
grep -i "connect\|disconnect\|reconnect" service_2026*.log
# 查看 EtherCAT 从站状态
grep -i "slave\|wkc\|op\|init" debug_info.txt
12.6 常见错误码速查
| 错误码 | 含义 | 常见于 |
|---|---|---|
| Modbus 01 | 不支持的功能码 | Modbus |
| Modbus 02 | 地址越界 | Modbus |
| Modbus 03 | 数据值不合法 | Modbus |
| Modbus 04 | 从站内部故障 | Modbus |
| S7 0x81 | PUT/GET 未开启 | S7 |
| S7 0x0A | DB 不存在 | S7 |
| MC 0x4021 | 软元件名不支持 | MC |
| MC 0x4050 | 请求量超限 | MC |
| OPC UA 0x80440000 | 证书不信任 | OPC UA |
| OPC UA 0x80730000 | 无权限 | OPC UA |
| EtherCAT AL 0x001B | PDO 映射错误 | EtherCAT |
| HTTP 401 | 未认证 | HTTP API |
| HTTP 429 | 请求过频 | HTTP API |
本教程覆盖了 Darra PLC 所有主流通讯协议。每个协议都遵循 原理 → 配置 → 示例 → 排错 的结构,方便快速上手。
更详细的协议文档请参考对应子页面: