在线调试
DarraRT PLC 提供非侵入式在线调试: 在程序不停止、不暂停、不降速的前提下, 通过在线监视 + 强制赋值 + 趋势图 + 报警 + 日志 + 任务扫描统计 + 交叉引用组合完成问题定位和逻辑验证。
PLC 产线实时控制不允许程序暂停。伺服抱闸、安全联锁、闭环控制、通讯心跳在程序暂停的瞬间就会触发故障甚至设备损坏。 因此 DarraRT PLC 不提供传统代码级断点 / 单步 / F9 / F10 / F11 / 暂停程序 / 挂起程序 等手段。 异常处理链路是: 停 PLC → Q 区清零 → 日志报错 → 报警弹出 — 直接停机, 不暂停。 所有调试通过观察运行中的变量 + 人为激励变量 + 记录变化轨迹完成。
调试模式对比
| 模式 | 部署环境 | 特性 | 典型场景 |
|---|---|---|---|
| 离线仿真 | IDE 内嵌仿真 Runtime | 变量监视 / 强制 / 趋势 / 报警 全支持 | 开发期逻辑验证 |
| 在线监视 | IDE 连接真实 Runtime | 变量监视 / 强制 (需权限) / 趋势 / 报警 / 任务统计 | 现场联机调试 |
| Deploy 诊断 | Runtime 独立, IDE 只读连 | 变量监视 (只读) / 趋势 / 报警历史 | 生产监控与故障回溯 |
六大调试工具
┌────────────┬────────────┬────────────┬────────────┬────────────┬────────────┐
│ 在线监视 │ 强制赋值 │ 趋势图 │ 报警管理 │ 日志 │ 任务统计 │
│ Watch │ Force │ Trend │ Alarm │ Log │ Profiler │
├────────────┼────────────┼────────────┼────────────┼────────────┼────────────┤
│ 看当前值 │ 注入激励 │ 看时序轨迹 │ 看异常事件 │ 看文字证据 │ 看性能瓶颈 │
└────────────┴────────────┴────────────┴────────────┴────────────┴────────────┘
详见: online-monitoring / online-force-write / online-trend / online-alarm
配套工具:
- 交叉引用 (Cross Reference): 变量 where-used 查找, 安全重命名
- 诊断仪表板 (Diagnostics): 一页式的 CPU/内存/IO/通讯状态
- 代码性能分析 (Profiler): 热点 FB, 详见 性能分析

问题定位流程 (无断点方法)
以"电机不动作"为例演示完整定位链:
第 1 步: 先看报警
Ribbon → PLC → 报警面板, 按时间倒序看最新报警:

- 如有红色急停 / 故障报警, 根因已被 PLC 逻辑捕获 → 直接按报警描述处理
- 如果没有报警, 继续下一步
第 2 步: 在线监视关键变量
从输出端反向回溯, 右键变量 → 添加到监视:
问题: %QX0.0 (电机使能输出) = FALSE
↑
fbMotor.bRunning = FALSE ← 看 FB 内部标志
↑
逻辑: (bStart OR bRunning) AND NOT bStop
↑
bStart = FALSE, bStop = FALSE, bRunning = FALSE
↑
%IX0.0 (启动按钮) = FALSE? ← 物理信号进来了吗
3 秒之内就能判断是操作员没按按钮, 还是按钮接线断了, 还是逻辑错误。
第 3 步: 强制激励验证
若怀疑"启动按钮输入信号没进来但按钮其实是好的", 右键 bStart_HMI → 写入 TRUE:
- 一次性激励, 下一扫描周期被程序覆盖 → 观察电机是否启动
- 如果强制后电机动了 → 问题在物理输入链路
- 如果强制后电机仍不动 → 问题在逻辑或输出
详见 强制与写入。
第 4 步: 趋势图看时序
如果变量在某些时刻正常、某些时刻异常, 用趋势图抓取时序:
- 新建趋势, 加入
bStart,bStop,bRunning,%QX0.0 - 采样周期 10ms
- 等待问题复现
- 回放波形, 看哪个信号先变 → 根因就在最先变化的那个信号
第 5 步: 交叉引用找写入点
发现 bRunning 突然变 FALSE 但找不到原因?
- 右键
bRunning→ 交叉引用 - 弹窗列出所有读写位置: 写入 3 处, 读取 12 处
- 逐个写入点检查 → 找到那个出乎意料的复位逻辑
第 6 步: 任务统计找性能问题
如果 PLC 动作慢、丢帧、IO 不同步, 看任务统计面板:
- Ribbon → 诊断 → 任务统计
- 查每个 OB 的 最小 / 平均 / 最大 / 抖动 扫描时间
OB1.MaxCycleTime接近OB1.WatchdogTime→ 主循环负载过高OB30.Jitter > 2ms→ 周期中断被高优先级任务长时间抢占- 详见 PLC 诊断 和 性能分析
第 7 步: 日志翻证据
如果问题已过去, 只能靠历史日志回溯:
| 日志位置 | 内容 |
|---|---|
| IDE 日志 | %AppData%/DarraPLC/logs/ide_*.log |
| Service 日志 | <Service.exe 同目录>/logs/service_*.log |
| Runtime 日志 | <Runtime.exe 同目录>/logs/runtime_*.log |
| 内核驱动日志 | DbgView.exe 监听 DarraRT_PLC.sys |
日志格式:
[2026-04-18 14:32:05.123] [ERROR] [MotorFB] 启动失败 code=E0103 pos=1523
at FB_Motor.Execute() in FB_Motor.cs:line 85
at Main.OB1() in Main.cs:line 42
查看堆栈第一行定位代码位置, 而不是只看错误消息。
异常处理链路 (生产模式)
与开发调试不同, 生产模式下异常处理必须立即停机、清零输出、报警, 禁止暂停:
┌─── 运行时异常触发 ──────────────────────────┐
│ │
│ 除零 / 越界 / 超时 / IO 错误 / 通讯异常 │
│ ↓ │
│ OB121 / OB122 / OB80 触发 │
│ ↓ │
│ 1. 停 PLC (STOP) │
│ 2. Q 区全部清零 (安全输出) │
│ 3. 写入日志 (包含异常类型+堆栈+变量) │
│ 4. 触发报警 (弹窗 + 邮件 + 短信) │
│ ↓ │
│ 等待人工确认并排查根因 │
│ │
└─────────────────────────────────────────────┘
在 OB121 / OB122 / OB80 中实现:
PROGRAM OB80 // 看门狗超时
VAR_INPUT
tActCycle : TIME;
iErrorOB : INT;
END_VAR
// 1. 立即停 PLC (由系统自动完成)
// 2. Q 区清零
MEMSET(%QB0, 0, 4096);
// 3. 日志记录
LogError('OB80_WatchdogTimeout',
'OB=', INT_TO_STRING(iErrorOB),
'cycle=', TIME_TO_STRING(tActCycle));
// 4. 触发报警
bAlarm_Watchdog := TRUE;
END_PROGRAM
监视表保存与复用
调试场景可以沉淀为监视表模板, 复用到类似项目:
- 配好一组关键变量的监视视图 (含触发条件、颜色、格式化)
- 监视面板 → 导出 XML
- 下次类似项目 → 导入 XML → 一键还原监视
- 项目级监视表随
.darraproj一起存档, 不用重建
编译错误诊断
IDE 编译错误显示在输出窗口 → 错误列表:
| 错误码 | 含义 | 处理 |
|---|---|---|
| E1001 | 变量未声明 | 在 VAR 区补充 |
| E1002 | 类型不匹配 | 加显式转换函数 (INT_TO_REAL) |
| E1003 | 数组越界 | 检查索引范围 |
| E1004 | 地址冲突 | 调整 AT %Mxxx 分配 |
| E1005 | 除零常量 | 检查分母为 0 的常量表达式 |
| W2001 | 循环无出口 | 加 EXIT 或条件退出 |
| W2002 | 变量未使用 | 删除或添加 {attribute 'no_warn'} |
双击错误行自动跳转到代码位置。
运行时异常定位方法
因为没有断点, 运行时异常只能靠事后的日志 + 变量快照回溯。最佳实践:
-
每个 FUNCTION_BLOCK 在关键分支埋日志
IF rTemp > 80.0 THEN
LogInfo('TempHigh', 'T=', REAL_TO_STRING(rTemp));
bFan := TRUE;
END_IF; -
OB121 统一捕获数学异常: 除零 / 溢出 / NaN 都路由到 OB121, 打印异常码 + 触发 POU
-
数组访问前显式 guard:
IF iIndex >= 0 AND iIndex < 100 THEN
rResult := arValues[iIndex];
ELSE
LogError('IndexOOB', 'idx=', INT_TO_STRING(iIndex));
END_IF; -
通讯层所有超时都写日志, 不吞异常:
Modbus.ReadHoldingRegisters失败必须日志 + 重试, 禁止catch { }空 catch
综合调试流程总览
┌──────────────┐
│ 1. 报警面板 │ ← 先看有没有系统给的线索
└──────┬───────┘
│ (无报警)
┌──────┴───────┐
│ 2. 在线监视 │ ← 从输出往输入回溯, 看哪个变量不对
└──────┬───────┘
│
┌──────┴───────┐
│ 3. 强制激励 │ ← 隔离物理输入 vs 逻辑错误
└──────┬───────┘
│ (时序问题)
┌──────┴───────┐
│ 4. 趋势回放 │ ← 看谁先变, 根因在最先变化的信号
└──────┬───────┘
│ (找不到写入点)
┌──────┴───────┐
│ 5. 交叉引用 │ ← 列出所有写入位置
└──────┬───────┘
│ (性能问题)
┌──────┴───────┐
│ 6. 任务统计 │ ← 看 OB 抖动 / 看门狗
└──────┬───────┘
│ (问题已过去)
┌──────┴───────┐
│ 7. 日志回溯 │ ← 搜文字证据
└──────────────┘
最佳实践
- 上线前先把 OB121/122/80 写全, 生产时才有证据
- 关键变量在 HMI 上显示, 操作员第一时间看到异常
- 每个 FB 暴露诊断属性:
fb.sDiagMsg/fb.iErrorCode便于在线监视直接看 - 用命名常量替代 magic number, 日志打印才有意义
- 测试阶段开启趋势图 24 小时录制, 问题复现后立刻回放波形
- 定期审计监视表: 项目后期删掉调试残留的 force 和 watch