运行时集成
Vision Studio 视觉程序以异步 Job 方式集成到 PLC 运行时中,通过 RuntimeResourcePolicy 管理资源,通过 PLC 变量交换结果数据。
异步执行模型
Job 生命周期
┌──────────┐ 触发 ┌──────────┐ 调度 ┌──────────┐
│ PLC 扫描 │ ───────→ │ Job 队列 │ ───────→ │ 视觉执行器 │
│ 周期 │ │ (FIFO) │ │ (异步) │
└──────────┘ └──────────┘ └──────┬───┘
│
┌──────────────────────────────┘
│
▼
┌────────────────┐
│ 相机采集 │
│ 图像预处理 │
│ 模型推理 │
│ 后处理 │
│ 结果回写 │
└────────┬───────┘
│
▼
┌────────────────┐
│ PLC 变量状态 │
│ VIS_JOB_DONE │
│ VIS_RESULT_* │
└────────────────┘
Job 状态机
┌──────────┐
│ IDLE │ ←── PLC 就绪
└────┬─────┘
│ 触发 (VIS_JOB_START ↑)
▼
┌──────────┐
│ QUEUED │ ←── 排队等待资源
└────┬─────┘
│ 资源就绪
▼
┌──────────┐
│ RUNNING │ ←── 执行中
└────┬─────┘
│
┌────────┼────────┐
▼ ▼ ▼
┌────────┐┌────────┐┌──────────┐
│DONE ││FAULT ││TIMEOUT │
│(成功) ││(故障) ││(超时) │
└────────┘└────────┘└──────────┘
异步执行的优势
| 特性 | 同步执行 | 异步 Job |
|---|---|---|
| 扫描周期影响 | 阻塞,周期延长 | 无影响 |
| 推理延迟 | 等待推理完成 | 并行执行 |
| 多任务并发 | 不支持 | 支持多 Job 并发 |
| 故障处理 | 可能影响 PLC 运行 | 独立故障隔离 |
| 资源管理 | 无 | RuntimeResourcePolicy 控制 |
资源管理 (RuntimeResourcePolicy)
RuntimeResourcePolicy 管理视觉运行时使用的计算资源,确保视觉推理不影响 PLC 核心任务的实时性。
策略配置
{
"vision": {
"runtime_resource_policy": {
"gpu": {
"enabled": true,
"device_id": 0,
"memory_limit_mb": 1024,
"fallback_to_cpu": true
},
"cpu": {
"max_cores": 2,
"affinity": [4, 5],
"priority": "below_normal",
"thread_pool_size": 4
},
"concurrency": {
"max_concurrent_jobs": 2,
"queue_size": 10,
"job_timeout_ms": 30000
},
"memory": {
"max_image_memory_mb": 512,
"max_model_cache_mb": 1024,
"swap_to_disk": false
},
"scheduling": {
"policy": "round_robin",
"preemption": false,
"watchdog_interval_ms": 1000
}
}
}
}
策略参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
gpu.enabled | BOOL | false | 启用 GPU 推理 |
gpu.device_id | DWORD | 0 | GPU 设备 ID |
gpu.memory_limit_mb | DWORD | 1024 | GPU 显存上限 |
gpu.fallback_to_cpu | BOOL | true | 显存不足时回退 CPU |
cpu.max_cores | DWORD | 2 | 最大 CPU 核心数 |
cpu.affinity | DWORD[] | [] | CPU 亲和性绑定 |
cpu.priority | ENUM | below_normal | 线程优先级 |
concurrency.max_concurrent_jobs | DWORD | 2 | 最大并发 Job 数 |
concurrency.queue_size | DWORD | 10 | 排队最大长度 |
concurrency.job_timeout_ms | DWORD | 30000 | Job 超时时间 |
memory.max_image_memory_mb | DWORD | 512 | 图像缓存上限 |
memory.max_model_cache_mb | DWORD | 1024 | 模型缓存上限 |
scheduling.policy | ENUM | round_robin | 调度策略 |
scheduling.watchdog_interval_ms | DWORD | 1000 | 看门狗间隔 |
资源配置建议
| 场景 | GPU 显存 | CPU 核心 | 并发 Job | 说明 |
|---|---|---|---|---|
| 单相机 640×640 检测 | 512 MB | 1 | 1 | 入门配置 |
| 双相机 1280×1024 检测 | 2 GB | 2 | 2 | 标准配置 |
| 四相机 2448×2048 检测 | 4 GB | 4 | 4 | 高性能配置 |
| 边缘设备 (无 GPU) | — | 2 | 1 | CPU 推理 |
结果回写到 PLC
变量映射规范
视觉 Job 执行完成后,结果自动写入 PLC 变量区:
系统变量 (所有视觉 Job 共享):
| 变量名 | 类型 | 说明 |
|---|---|---|
VIS_JOB_BUSY | BOOL | 有 Job 正在执行 |
VIS_JOB_DONE | BOOL | 最近一次 Job 完成 |
VIS_JOB_ERROR | BOOL | 最近一次 Job 异常 |
VIS_JOB_ERROR_ID | DWORD | 错误码 |
VIS_JOB_ID | DWORD | 当前 Job 序号 |
VIS_JOB_ELAPSED | REAL | 执行耗时 (ms) |
结果变量 (按 Job 配置):
| 变量名 | 类型 | 说明 |
|---|---|---|
VIS_RESULT_COUNT | INT | 检测目标数 |
VIS_RESULT_01_CLASS | STRING[32] | 目标 1 类别 |
VIS_RESULT_01_CONF | REAL | 目标 1 置信度 |
VIS_RESULT_01_X | REAL | 目标 1 X |
VIS_RESULT_01_Y | REAL | 目标 1 Y |
VIS_RESULT_01_W | REAL | 目标 1 宽度 |
VIS_RESULT_01_H | REAL | 目标 1 高度 |
VIS_RESULT_01_ANGLE | REAL | 目标 1 角度 |
VIS_RESULT_02_* | ... | 目标 2 (最多 256 个) |
结果回写流程
1. PLC 设置 VIS_JOB_START = TRUE
2. 运行时检测到上升沿,创建 Job 加入队列
3. Job 开始执行,VIS_JOB_BUSY = TRUE
4. 采集 → 预处理 → 推理 → 后处理
5. 推理完成,结果写入 VIS_RESULT_* 变量
6. VIS_JOB_DONE = TRUE, VIS_JOB_BUSY = FALSE
7. PLC 在下一个扫描周期读到 VIS_JOB_DONE
8. PLC 读取 VIS_RESULT_* 变量
9. PLC 将 VIS_JOB_DONE 清零,准备下一次触发
PLC 侧代码示例 (SCL)
PROGRAM VisionIntegration
VAR
// 触发视觉 Job
trigger_counter : INT := 0;
trigger_pulse : BOOL;
trigger_prev : BOOL;
// 结果接收
has_result : BOOL;
result_count : INT;
i : INT;
END_VAR
// 上升沿检测
trigger_pulse := trigger_signal AND NOT trigger_prev;
trigger_prev := trigger_signal;
// 触发视觉 Job
IF trigger_pulse THEN
VIS_JOB_START := TRUE;
trigger_counter := trigger_counter + 1;
ELSE
VIS_JOB_START := FALSE;
END_IF;
// 读取结果
IF VIS_JOB_DONE AND NOT VIS_JOB_BUSY THEN
// 检查是否出错
IF VIS_JOB_ERROR THEN
// 错误处理
LOG_ERROR("视觉 Job 失败, 错误码: ", VIS_JOB_ERROR_ID);
system_state := STATE_VISION_FAULT;
ELSE
// 读取检测结果
result_count := VIS_RESULT_COUNT;
IF result_count > 0 THEN
// 处理检测结果
FOR i := 1 TO result_count DO
// 根据结果控制逻辑
// ...
END_FOR;
system_state := STATE_PRODUCT_OK;
ELSE
system_state := STATE_PRODUCT_NG;
END_IF;
END_IF;
// 清除完成标志
VIS_JOB_DONE := FALSE;
END_IF;
// 超时保护
IF VIS_JOB_BUSY AND (TIME_SINCE_TRIGGER > T#30S) THEN
VIS_JOB_ABORT := TRUE;
LOG_WARN("视觉 Job 超时, 已中止");
END_IF;
END_PROGRAM
故障处理与超时
故障分类
| 故障类型 | 错误码 | 可恢复 | 处理方式 |
|---|---|---|---|
| 相机断开 | 0x1001 | 是 | 自动重连,最多 3 次 |
| 模型加载失败 | 0x2001 | 是 | 回退到上一个版本 |
| 推理异常 | 0x3001 | 否 | 记录日志,报错到 PLC |
| 显存不足 | 0x4001 | 是 | 自动回退到 CPU |
| 内存不足 | 0x4002 | 是 | 清理缓存后重试 |
| 超时 | 0x5001 | 是 | 输出默认值,下次重试 |
| 许可限制 | 0x6001 | 否 | 提示用户升级许可 |
故障处理策略
{
"vision": {
"fault_handling": {
"auto_retry": {
"enabled": true,
"max_retries": 3,
"retry_interval_ms": 1000,
"backoff_multiplier": 2.0
},
"camera_reconnect": {
"enabled": true,
"max_attempts": 3,
"interval_ms": 2000
},
"model_fallback": {
"enabled": true,
"fallback_version": "previous"
},
"timeout": {
"default_value_output": true,
"default_count": 0,
"log_timeout": true
},
"fatal": {
"disable_vision": true,
"notify_plc": true,
"notify_operator": true
}
}
}
}
自动恢复流程
检测到故障 ──→ 判断可恢复性
│ │
│ 可恢复 │ 不可恢复
▼ ▼
重试计数 记录日志
│ 通知 PLC
▼ 通知操作员
重试操作 │
│ ▼
├── 成功 → 恢复正常 禁用视觉功能
│
└── 失败 → 判断是否达到最大重试次数
│
┌────────┼────────┐
▼ ▼ ▼
相机故障 模型故障 其他故障
重连相机 回退版本 禁用功能
超时处理
| 超时类型 | 默认值 | 配置位置 | 超时后动作 |
|---|---|---|---|
| 相机采集超时 | 5000 ms | 相机配置 | 输出默认值,触发 OnTimeout |
| 模型推理超时 | 30000 ms | RuntimeResourcePolicy | 输出默认值,触发 OnTimeout |
| Job 总超时 | 30000 ms | RuntimeResourcePolicy | 中止 Job,触发 OnTimeout |
| 事件处理超时 | 1000 ms | 事件配置 | 跳过事件,记录日志 |
性能监控
运行时指标
视觉运行时暴露以下指标,可在 IDE 或 HMI 面板中查看:
| 指标 | 说明 | 单位 |
|---|---|---|
| GPU 利用率 | GPU 计算核心占用率 | % |
| GPU 显存使用 | 显存已用量 | MB |
| CPU 使用率 | 视觉进程 CPU 占用 | % |
| 推理延迟 | 平均推理时间 | ms |
| 采集延迟 | 平均采集时间 | ms |
| Job 排队长度 | 当前等待队列长度 | 个数 |
| Job 吞吐量 | 每分钟完成 Job 数 | 个/min |
| 成功/失败率 | 最近 100 次 Job 成功率 | % |
诊断日志
视觉运行时的诊断日志位于 %AppData%/DarraPLC/logs/vision_*.log:
[2026-07-20 14:30:00.123] [INFO] [VisionJob] Job #1287 开始执行 (程序: surface_inspect)
[2026-07-20 14:30:00.124] [INFO] [Acquisition] 相机 USB_CAM_1 采集开始, 曝光 5000µs
[2026-07-20 14:30:00.128] [INFO] [Acquisition] 采集完成, 1280×1024, 耗时 4ms
[2026-07-20 14:30:00.129] [INFO] [Preprocessing] 预处理开始 (灰度→高斯滤波→缩放)
[2026-07-20 14:30:00.132] [INFO] [Preprocessing] 预处理完成, 耗时 3ms
[2026-07-20 14:30:00.133] [INFO] [Inference] 推理开始 (模型: surface_defect v3, GPU)
[2026-07-20 14:30:00.141] [INFO] [Inference] 推理完成, 耗时 8ms, 检测到 2 个目标
[2026-07-20 14:30:00.142] [INFO] [Postprocessing] 后处理完成, 耗时 1ms
[2026-07-20 14:30:00.143] [INFO] [ResultWrite] 结果写入 PLC 变量, 2 个目标
[2026-07-20 14:30:00.143] [INFO] [VisionJob] Job #1287 完成, 总耗时 20ms
安全与隔离
进程隔离
视觉运行时在与 PLC 核心不同的进程中运行:
- PLC 运行时:Windows Service 实时线程,最高优先级
- 视觉运行时:独立进程,
below_normal优先级 - 故障隔离:视觉进程崩溃不影响 PLC 运行时
资源隔离
- CPU 隔离:通过
cpu.affinity绑定特定核心,不抢占 PLC 核心 - GPU 隔离:显存上限防止 GPU 显存溢出
- 内存隔离:
max_image_memory_mb防止图像缓存撑爆内存 - 时间隔离:
job_timeout_ms防止视觉任务无限占用