OB 组织块调度
组织块 (Organization Block, OB) 是 PLC 程序的执行入口。Darra PLC 支持 12 种 OB 类型,由 OB 调度器按优先级进行抢占式调度。
OB 类型总览
| OB | 名称 | 触发方式 | 优先级 | 典型用途 |
|---|---|---|---|---|
| OB1 | 主循环 | 每个扫描周期 | 1 (最低) | 主程序逻辑 |
| OB10 | 异步事件 | 事件触发 | 8 | 通讯回调、报警处理 |
| OB20 | 延时中断 | 一次性延时触发 | 10 | 延时后执行操作 |
| OB30 | 周期中断 | 固定时间间隔 | 12 | 闭环控制 (PID) |
| OB35 | 快速周期中断 | 短间隔 (≥1ms) | 14 | 高速计数、位置环 |
| OB40 | 硬件中断 | IO 边沿触发 | 16 | 紧急响应 |
| OB80 | 时间错误 | 扫描超时 | 20 | 过载诊断 |
| OB82 | IO 访问错误 | IO 读写失败 | 18 | IO 故障处理 |
| OB100 | 启动 | PLC 冷启动 | — | 初始化 |
| OB101 | 热重启 | PLC 热重启 | — | 恢复状态 |
| OB121 | 编程错误 | 运行时异常 | 22 | 除零/溢出处理 |
| OB122 | IO 错误 | 模块故障 | 22 | 模块离线处理 |
OB1 — 主循环
OB1 是 PLC 的主程序,每个扫描周期执行一次:
PROGRAM OB1
VAR
fbMotor : FB_MotorControl;
fbConveyor : FB_ConveyorControl;
END_VAR
// 每个扫描周期执行
fbMotor(bStart := %IX0.0, bStop := %IX0.1);
fbConveyor(bEnable := fbMotor.bRunning);
%QX0.0 := fbMotor.bRunning;
%QX0.1 := fbConveyor.bActive;
END_PROGRAM
OB1 的执行周期即 PLC 的扫描周期。默认 50ms,可配置范围 1ms ~ 10000ms。实际周期 = IO 输入读取 + OB1 执行 + IO 输出写入 + 高优先级 OB 插入时间。
OB10 — 异步事件
由特定事件触发执行,适合处理非周期性任务:
PROGRAM OB10
VAR
iEventId : INT;
END_VAR
// 事件分发
CASE iEventId OF
1: // 通讯连接事件
HandleConnection();
2: // 报警触发事件
HandleAlarm();
3: // 配方切换事件
LoadRecipe(iRecipeNo);
END_CASE;
END_PROGRAM
OB30 — 周期中断
以固定时间间隔执行,不受 OB1 扫描周期影响。适合需要精确时间基准的控制算法:
PROGRAM OB30 // 配置: 10ms 周期
VAR
fbPID_Temp : FB_PID;
rActTemp : REAL;
rSetTemp : REAL;
rOutput : REAL;
END_VAR
// 每 10ms 精确执行
rActTemp := INT_TO_REAL(%IW100) * 0.1; // 温度传感器
rSetTemp := %MD200; // HMI 设定值
fbPID_Temp(
rSetpoint := rSetTemp,
rFeedback := rActTemp,
rKp := 2.0,
rKi := 0.5,
rKd := 0.1
);
rOutput := fbPID_Temp.rOutput;
%QW200 := REAL_TO_INT(rOutput * 100.0); // 输出到执行器
END_PROGRAM
OB30 的执行间隔在项目配置中设定。多个 OB30 实例可配置不同间隔(如 OB30=10ms、OB31=20ms)。实时模式下间隔精度可达微秒级。
OB35 — 快速周期中断
高优先级的快速中断,适合运动控制和高速计数:
PROGRAM OB35 // 配置: 1ms 周期
VAR
diEncoder : DINT;
diLastEncoder : DINT;
rVelocity : REAL;
END_VAR
// 每 1ms 执行 — 高速位置环
diEncoder := %ID0; // 编码器反馈
rVelocity := DINT_TO_REAL(diEncoder - diLastEncoder) / 0.001; // 速度估算
diLastEncoder := diEncoder;
END_PROGRAM
OB40 — 硬件中断
由物理 IO 信号的边沿触发,用于紧急事件响应:
PROGRAM OB40
VAR
iChannel : INT; // 触发通道号
END_VAR
// 硬件中断: 急停按钮
IF iChannel = 0 THEN
// 立即停止所有运动
MC_Stop_All();
bEmergencyStop := TRUE;
END_IF;
END_PROGRAM
OB80 — 时间错误
当 OB1 扫描周期超过配置的最大时间时触发:
PROGRAM OB80
VAR
tActCycle : TIME; // 实际扫描时间
tMaxCycle : TIME; // 配置的最大周期
END_VAR
// 记录超时事件
iOverrunCount := iOverrunCount + 1;
// 连续超时 → 降级处理
IF iOverrunCount > 10 THEN
bReducedMode := TRUE; // 进入降级模式
END_IF;
END_PROGRAM
OB100 — 启动 (冷启动)
PLC 冷启动时执行一次,用于初始化:
PROGRAM OB100
// 初始化全局变量
giMachineState := 0;
grSetTemperature := 25.0;
// 清零计数器
giPartCount := 0;
giErrorCount := 0;
// 初始化通讯
InitModbusConnection();
InitEtherCATMaster();
END_PROGRAM
OB121 / OB122 — 错误处理
PROGRAM OB121 // 编程错误
VAR
iErrorCode : INT;
iBlockId : INT;
END_VAR
// 处理运行时错误 (除零、数组越界等)
CASE iErrorCode OF
1: // 除零错误
LogError('除零错误', iBlockId);
2: // 数组越界
LogError('数组越界', iBlockId);
3: // 数值溢出
LogError('数值溢出', iBlockId);
END_CASE;
END_PROGRAM
优先级调度机制
OB 调度器采用基于优先级的抢占式调度:
优先级: OB121/122 > OB80 > OB40 > OB35 > OB30 > OB10 > OB1
执行示例:
┌─OB1────────┐ ┌─OB1(继续)──┐
│ │←OB30→│ │
│ ┌OB40┐ │ │ │
│ │ │ │ │ │
──┴────┴────┴──┴─────┴────────────┴──→ 时间
- 高优先级 OB 可以抢占正在执行的低优先级 OB
- 被抢占的 OB 在高优先级 OB 完成后恢复执行
- 同一优先级的 OB 按触发时间顺序排队
OB30/OB35 的执行时间必须远小于其配置周期,否则会导致 OB80 时间错误。建议保持至少 50% 的周期裕量。使用性能面板监控各 OB 的实际执行时间。
配置方法
在项目配置中设定各 OB 的参数:
| 参数 | 说明 | 默认值 | 范围 |
|---|---|---|---|
OB1.CycleTime | 主循环周期 | 50ms | 1ms ~ 10s |
OB30.Interval | 周期中断间隔 | 10ms | 1ms ~ 1s |
OB35.Interval | 快速中断间隔 | 1ms | 125μs ~ 100ms |
OB1.MaxCycleTime | 最大允许扫描时间 | 200ms | > CycleTime |
OB1.WatchdogTime | 看门狗超时 | 500ms | > MaxCycleTime |
OB20 — 延时中断 (Time-Delayed)
SRT_DINT 指令启动一个一次性延时触发器, 到达时间后调用 OB20:
// 用户代码触发延时
SRT_DINT(OB_NR := 20, DTIME := T#5s, SIGN := 16#ABCD);
// 5 秒后系统调用 OB20
PROGRAM OB20
VAR_INPUT
iSign : WORD; // SRT_DINT 传入的标识
END_VAR
// 处理 5 秒后要做的动作
CASE iSign OF
16#ABCD:
CloseCoolantValve();
16#DEF0:
TriggerAlarm();
END_CASE;
END_PROGRAM
OB82 — IO 访问错误
当 IO 模块离线、读/写失败时触发:
PROGRAM OB82
VAR_INPUT
iFaultingModule : INT; // 故障模块编号
iErrorCode : INT; // 错误类型
END_VAR
// 记录 IO 故障
LogError('IO_Fault',
'Module=', INT_TO_STRING(iFaultingModule),
'Code=', INT_TO_STRING(iErrorCode));
// 触发报警
bAlarm_IO := TRUE;
// 如果是安全关键 IO, 停 PLC
IF iFaultingModule IN [1, 2, 3] THEN // 安全 IO 列表
StopPLC();
END_IF;
END_PROGRAM
任务扫描统计 (Task Profiler)
Darra PLC 实时统计每个 OB 的执行时间, 通过 S 区和 TaskProfiler 对象访问:
统计指标
| 指标 | 说明 |
|---|---|
MinCycleTime | 历史最短扫描时间 |
AvgCycleTime | EWMA 指数加权平均 (alpha=1/8, 短期波动敏感) |
MaxCycleTime | 历史最长扫描时间 |
CurrCycleTime | 最近一次执行耗时 |
Jitter | 周期抖动 (Max - Min 或 标准差) |
OverrunCount | 超过看门狗的次数 |
TotalRuns | 累计执行次数 |
TotalTime | 累计执行时间 |
CpuPercent | 占 CPU 百分比 (= TotalTime / WallClock) |
访问示例
VAR
profiler : TaskProfiler;
stStats : ST_TaskStats;
END_VAR
// 读取 OB1 统计
stStats := profiler.GetStats(OB_ID := 1);
// 输出到 HMI
%MD500 := TIME_TO_REAL(stStats.MaxCycleTime); // HMI 显示
%MD504 := TIME_TO_REAL(stStats.AvgCycleTime);
%MW508 := INT_TO_INT(stStats.OverrunCount);
// 抖动过大报警
IF TIME_TO_MS(stStats.Jitter) > 5 THEN
LogWarn('OB1 jitter too high: ', TIME_TO_STRING(stStats.Jitter));
END_IF;
诊断仪表板
在 IDE 的 诊断 → 任务统计 面板可视化查看所有 OB 的性能数据:
┌────────┬─────────┬─────────┬─────────┬─────────┬──────────┐
│ OB │ Min │ Avg │ Max │ Jitter │ Overruns │
├────────┼─────────┼─────────┼─────────┼─────────┼──────────┤
│ OB1 │ 2.1ms │ 8.5ms │ 45ms │ 3ms │ 0 │
│ OB30 │ 850μs │ 1.2ms │ 2.8ms │ 0.5ms │ 0 │
│ OB35 │ 120μs │ 180μs │ 420μs │ 80μs │ 2 │
└────────┴─────────┴─────────┴─────────┴─────────┴──────────┘
周期抖动 (Jitter)
抖动是 PLC 实时性的核心指标, 反映周期性任务执行时刻的偏差:
理想: ┃ ┃ ┃ ┃ ┃
↑ ↑ ↑ ↑ ↑
0ms 10ms 20ms 30ms 40ms
实际: ┃ ┃ ┃ ┃ ┃
↑ ↑ ↑ ↑ ↑
0ms 9.2ms 20.8ms 28.9ms 41.1ms
←jitter→
抖动来源:
- 高优先级 OB 抢占
- 系统中断 (NDIS / USB / SMI)
- CPU 频率波动 (SpeedStep / Turbo Boost)
- Windows 内核调度 (用户态运行时)
DarraRT PLC 通过以下手段把抖动控制在微秒级:
- 核心隔离: DarraRT.sys 锁定 CPU 核, 禁止 Windows 调度
- SMI 检测: 识别 BIOS 干扰并告警
- TSC 高精度时基: 微秒级对齐
- DPC/ISR 屏蔽: 禁止 APIC 中断干扰
典型指标 (1ms OB35):
| 硬件 | 抖动 |
|---|---|
| 消费级 Intel i5 (无 RT 内核) | 50~200μs |
| DarraRT.sys + 核心隔离 | < 10μs |
| DarraRT.sys + Intel Xeon + RTSS | < 3μs |
排错表
| 问题 | 可能原因 | 解决 |
|---|---|---|
| OB1 扫描时间忽高忽低 | 被 OB30/35 抢占 | 查 Max - Min, 若大则减少高优先级负载 |
| OB80 频繁触发 | MaxCycleTime 太小或负载过重 | 加大 MaxCycleTime 或优化 OB1 代码 |
| OB35 抖动 > 100μs | 核心未隔离 | 在内核驱动中启用 isolate |
| OB121 触发 | 运行时异常 (除零/越界) | 加 guard, 检查数据 |
| OB100 每次都进 | 热启动条件不满足 | 检查 NVRAM 电池和 RETAIN 区 CRC |
最佳实践
- OB1 代码简洁: 耗时任务移到 OB30/35 周期中断
- OB35 < 1ms: 快速中断的代码必须极简
- 留 50% 周期裕量: MaxCycleTime ≥ 2 × CycleTime
- 看门狗: WatchdogTime ≥ 2 × MaxCycleTime
- OB100 初始化全部全局变量: 包括 fbXxx 的内部状态
- 所有错误 OB 都要写日志: OB80/82/121/122 不能只是跑一下就完事
- 在线监视 Profiler: 部署后 24 小时观察抖动趋势