跳到主要内容

通讯协议教程

从零开始学会 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 TCPDarraLinkIDE → Service变量读写、订阅推送、远程部署、热更新
18443 HTTPSRemoteAccessServerIDE → 远程 Service远程管理 API
18823 HTTP/WSHMI Web浏览器 → HMIHMI 页面 + 实时数据推送
18852 UDPDiscovery同广播域设备发现设备扫描兜底
4840 TCPOPC UASCADA/MES → PLC标准 OPC UA 数据访问
502 TCPModbus TCP上位机 → PLCModbus 通讯

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 TokenJWT/OAuth 令牌认证HTTP API
用户名/密码基础认证OPC UA、MQTT
X.509 证书双向证书验证OPC UA
防火墙策略白名单放行固定端口所有协议

生产环境最低要求:启用加密 + 认证,禁止匿名访问。


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 地址错 / 防火墙拦 18821telnet 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。你需要做的只是:

  1. 确认 PDO 映射符合你的需求
  2. 如果有特殊参数,手动添加 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 文件

  1. 从松下官网下载 MINAS A6B ESI XML
  2. IDE 中:EtherCAT → ESI 管理 → 导入
  3. 搜索 "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 灯
切不到 OPPDO 映射失败查 AL_STATUS_CODE
WKC 不匹配帧丢失 / 从站掉线查端口错误计数
DC 不同步参考时钟选错改参考时钟从站
周期抖动大CPU 隔离不足增加隔离核心数
SDO 超时从站忙 / 索引不对确认对象字典支持

4. Modbus TCP/RTU

4.1 Modbus 协议原理

Modbus 是工业通讯的"通用语"——几乎每个设备都支持。它分两种:

模式传输层速度距离
Modbus TCP以太网 TCP 502 端口100m(以太网)
Modbus RTURS-485 串口1200m

主从模型

主站(Darra PLC)                 从站(变频器/温控器/仪表)
│ │
│ ── 请求(功能码 + 地址 + 数据)──>│
│ <── 响应(功能码 + 数据)───────│

4.2 功能码

功能码名称读/写数据类型
01Read CoilsBOOL(线圈)
02Read Discrete InputsBOOL(只读输入)
03Read Holding RegistersWORD/DWORD/REAL
04Read Input RegistersWORD/DWORD/REAL
05Write Single Coil单个 BOOL
06Write Single Register单个 WORD
15Write Multiple Coils批量 BOOL
16Write Multiple Registers批量 WORD
23Read/Write Multiple Registers读写批量 WORD

4.3 地址映射

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(最常用)

最常用的是 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❌ 仅调试
Basic256Sha256AES-256SHA-256✅ 推荐
Aes128_Sha256_RsaOaepAES-128SHA-256✅ 兼容性好
Aes256_Sha256_RsaPssAES-256SHA-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 侧准备

  1. 确认 OPC UA 服务已启用(端口 4840)
  2. 配置用户账号(IDE → 通讯 → OPC UA → 用户管理)
  3. 导入 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 SmartRack=0, Slot=0直连
S7-300Rack=0, Slot=2MPI/PROFINET
S7-400Rack=0, Slot=3机架号+槽号
S7-1200Rack=0, Slot=0/1需关优化块访问
S7-1500Rack=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)

  1. DB 块属性 → 取消勾选"优化的块访问"
  2. CPU 属性 → 防护与安全 → 允许 PUT/GET 通讯
  3. 下载配置到 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 错核对配置
错误 0x81PUT/GET 未开启TIA 里开启
读回 0DB 被"优化块访问"保护取消优化
错误 0x0ADB 未下载到 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 数据寄存器DD0~D65535WORD
M 内部继电器MM0~M65535BIT
X 输入XX0~X1FFFBIT(八进制)
Y 输出YY0~Y1FFFBIT(八进制)
R 文件寄存器RR0~R65535WORD
ZR 扩展文件寄存器ZRZR0~ZR65535WORD

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/statusPLC 状态
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/stateIO 状态
GET/api/alarms报警列表
POST/api/alarms/acknowledge确认报警
GET/api/trend/{variable}趋势数据

9.2 认证方式

方式Header说明
仅调试
BasicAuthorization: Basic base64(user:pass)需 HTTPS
BearerAuthorization: Bearer <token>推荐
API KeyX-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路径拼写错核对端点路径
401Token 过期刷新 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 灯亮?目视
2IP 可达:能 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 数据错位诊断

症状:读回来的数值看起来不对

可能原因

  1. 字节序(最常见):大端/小端不匹配
  2. 数据类型:INT 当 REAL 读,或反过来
  3. 地址偏移:Modbus 从 0 开始 vs 从 1 开始
  4. 缩放因子:原始值 5000 代表 50.00
  5. 位掩码:多个信号挤在一个寄存器里

排查方法

// 把原始字节打出来看看
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-NetConnectionPowerShell 端口测试Test-NetConnection -Port 502
netstat查看本机端口占用`netstat -an
Wireshark抓包分析协议帧过滤 tcp.port == 502
tcpdumpLinux 抓包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_*.logIDE 操作日志编译、下载、配置问题
service_*.logService 运行日志通讯、运行时问题
debug_info.txtEtherCAT 调试日志总线问题
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 0x81PUT/GET 未开启S7
S7 0x0ADB 不存在S7
MC 0x4021软元件名不支持MC
MC 0x4050请求量超限MC
OPC UA 0x80440000证书不信任OPC UA
OPC UA 0x80730000无权限OPC UA
EtherCAT AL 0x001BPDO 映射错误EtherCAT
HTTP 401未认证HTTP API
HTTP 429请求过频HTTP API

本教程覆盖了 Darra PLC 所有主流通讯协议。每个协议都遵循 原理 → 配置 → 示例 → 排错 的结构,方便快速上手。

更详细的协议文档请参考对应子页面: