ST中断服务程序:OB块中ST代码的编写规范与限制
在S7-1200/1500系列PLC编程中,组织块(OB)是响应系统事件的核心执行单元。其中,中断组织块(如OB40~OB47、OB60~OB64等) 用于处理硬件中断、时间中断、诊断中断等实时性要求高的任务。当使用结构化文本(Structured Text,ST)语言编写这些OB块时,必须严格遵循IEC 61131-3标准与西门子TIA Portal运行时环境的双重约束——稍有偏差,轻则导致中断丢失、逻辑错乱,重则引发CPU停机或过程失控。本文不讲概念,只列可立即执行的硬性规则与典型陷阱,覆盖从变量声明、语句结构到调试验证的全链路实操要点。
一、OB块选择与触发机制确认(前置动作)
中断OB的编号直接决定其触发条件和执行优先级。必须根据实际硬件信号类型和响应时效要求选择唯一匹配的OB,不可随意替换或复用。
-
确认中断源类型:
- 硬件中断(如数字量输入DI的上升沿)→ 固定使用
OB40(默认)或OB41~OB47(需在硬件组态中为每个DI通道单独分配)。 - 时间中断(周期性执行)→ 使用
OB30~OB38(推荐OB35,默认100ms周期;若需更短周期,必须启用“高精度定时器”并确认CPU支持)。 - 延迟中断(延时后触发)→ 使用
OB20(仅限S7-1500,S7-1200不支持)。 - 诊断中断(模块故障)→ 使用
OB82(诊断中断)、OB86(机架故障)、OB121(编程错误)。
- 硬件中断(如数字量输入DI的上升沿)→ 固定使用
-
检查硬件组态中的中断使能状态:
- 在TIA Portal中双击对应DI模块 → 进入“属性” → “常规” → “中断” → 勾选“启用过程中断”;
- 在“过程中断”选项卡中 → 设置“事件”为“上升沿”或“下降沿”;
- 点击“分配组织块”下拉菜单 → 明确指定为
OB40(或其他已创建的OB编号); - 若未完成此步,即使ST代码完全正确,中断也永远不会触发。
-
验证OB优先级与嵌套限制:
- 所有中断OB的优先级均高于主循环OB1(默认优先级1),但彼此之间存在固定顺序:
OB40(硬件中断) >OB60(同步中断) >OB35(时间中断) >OB82(诊断中断); - 同一优先级的多个OB不能同时运行:例如,两个DI通道均配置为
OB40,则第二个中断将在第一个OB40执行完毕后才进入; - 禁止在中断OB内调用更高优先级的OB(如在OB40中调用
CALL "OB60"),将导致CPU报错16#8004(优先级冲突)。
- 所有中断OB的优先级均高于主循环OB1(默认优先级1),但彼此之间存在固定顺序:
二、ST代码结构:四层强制分区与声明规则
S7-1200/1500的中断OB在ST语言下必须按以下顺序、且仅按此顺序书写代码段。任何错位、缺失或混用均会导致编译失败或运行异常。
-
全局变量声明区(仅允许VAR_GLOBAL / VAR_TEMP):
- 禁止声明VAR或VAR_IN_OUT变量:中断OB无固定输入/输出接口,所有数据交互必须通过全局DB块或M存储区;
- VAR_TEMP变量仅限本OB内部使用,且每次中断触发时自动初始化为0或FALSE(非保持型);
- 正确示例:
VAR_TEMP iCounter : INT := 0; // 每次中断重置为0 bPulseActive : BOOL := FALSE; // 非保持,无需INIT END_VAR - 错误示例(禁止):
VAR myVar : REAL; // 编译报错:中断OB不支持VAR区 END_VAR
-
局部常量与符号常量定义区(VAR CONSTANT):
- 仅允许定义编译期确定的常量,禁止调用函数或访问DB变量;
- 推荐用常量替代魔法数字,提升可读性与维护性;
- 示例:
VAR CONSTANT MAX_COUNT : INT := 1000; PULSE_WIDTH_MS : TIME := T#20MS; END_VAR
-
指令执行区(主体逻辑):
-
必须以顺序逻辑为主,严禁使用FOR/WHILE循环(见后文“执行时间限制”章节);
-
禁止调用非异步安全函数块(FB)或函数(FC):仅允许调用标有“异步安全”(Async-Safe)属性的块(如系统函数
MOVE,TON,CTU的ST版本); -
正确调用方式:
// ✅ 安全:系统定时器TON,已标记为异步安全 myTON(IN := bStartPulse, PT := PULSE_WIDTH_MS); bPulseActive := myTON.Q; // ❌ 危险:自定义FB若未勾选"异步安全",调用将导致CPU停机 // "MyCustomFB"(IN1 := iCounter); // 编译可能通过,运行时报错16#8010
-
-
错误处理区(可选,但强烈建议):
- 使用
IF ERROR THEN ... END_IF捕获ST语句级错误(如除零、数组越界); - 注意:此ERROR仅捕获当前语句,不覆盖OB整体异常(如内存溢出仍会停机);
- 示例:
iResult := iDividend / iDivisor; IF ERROR THEN iResult := 0; bDivError := TRUE; END_IF
- 使用
三、关键语法限制与避坑清单
以下为高频致错点,每一条均对应真实工程故障案例,必须逐条核对。
| 限制类型 | 具体规则 | 违反后果 | 实操对策 |
|---|---|---|---|
| 执行时间 | 单次OB执行时间 ≤ 1ms(S7-1200)或 ≤ 500μs(S7-1500高速模式);超时将触发 OB40 中断丢失并记录诊断缓冲区 |
CPU持续报 16#8001(OB执行超时),后续中断被丢弃 |
禁用所有循环语句;用查表法(ARRAY)替代FOR;用移位寄存器(SHL/SHR)替代计数累加;对长逻辑拆分为多级状态机,每次中断只推进1步 |
| 数据访问 | 禁止访问未优化的DB块(即DB块属性中“优化的块访问”未勾选);仅允许访问“优化的DB”或M存储区 | 运行时报错 16#800F(访问冲突),CPU停机 |
在DB块属性中 勾选“优化的块访问”;若需访问非优化DB,改用 AT 指令映射到临时变量后再操作 |
| 浮点运算 | 禁止在中断OB中执行复杂浮点计算(如SIN、COS、LN、EXP);仅允许 +, -, *, /, ABS 等基础运算 |
浮点协处理器占用时间不可控,极易超时 | 将复杂计算移至OB1主循环预计算并存入全局变量;中断OB中仅做查表或线性插值 |
| 字符串操作 | 完全禁止使用STRING、WSTRING类型及CONCAT、LEFT、MID等字符串函数 | 字符串动态内存分配引发不可预测延迟,必然超时 | 用固定长度的BYTE数组(ARRAY[0..19] OF BYTE)替代STRING;ASCII码值直接比较(如 aByte[0] = 16#41 代表'A') |
| 地址计算 | 禁止使用ADR()、PEEK、POKE指令;禁止指针运算(如 pVar^ := 100) |
地址非法访问触发硬件保护,CPU立即停机 | 所有数据地址通过符号名直接访问;需动态索引时,用 ARRAY[0..n] OF INT + 整数下标 |
四、典型安全编码模板(OB40硬件中断)
以下为经TIA Portal V18实测通过的OB40 ST代码模板,满足全部硬性约束,可直接复制修改:
// OB40 - 硬件中断服务程序(DI通道上升沿触发)
// 执行时间实测:< 350μs(S7-1500 CPU 1511-1PN)
VAR_TEMP
// 临时计数器(每次中断清零)
iPulseCount : INT := 0;
// 脉冲宽度计时器(TON实例)
tPulseTimer : TON;
// 状态标志(非保持)
bPulseDetected : BOOL := FALSE;
END_VAR
VAR CONSTANT
// 防抖时间(硬件滤波后仍需软件确认)
DEBOUNCE_TIME : TIME := T#20MS;
// 最大允许脉冲频率(防误触发)
MAX_PULSES_PER_SEC : INT := 50;
END_VAR
// 主逻辑:检测有效脉冲并计数
// 步骤1:启动防抖定时器(上升沿触发瞬间)
tPulseTimer(IN := TRUE, PT := DEBOUNCE_TIME);
// 步骤2:防抖完成后确认脉冲有效
IF tPulseTimer.Q THEN
// 步骤3:检查频率限制(使用全局DB中的计时器)
IF "DB_Global".iLastPulseTime <> 0 THEN
IF (TOD - "DB_Global".iLastPulseTime) >= T#20MS THEN
iPulseCount := "DB_Global".iTotalPulses + 1;
"DB_Global".iTotalPulses := iPulseCount;
"DB_Global".iLastPulseTime := TOD;
bPulseDetected := TRUE;
END_IF;
ELSE
// 首次触发
"DB_Global".iTotalPulses := 1;
"DB_Global".iLastPulseTime := TOD;
bPulseDetected := TRUE;
END_IF;
END_IF;
// 步骤4:错误处理(仅针对本OB内可能出错的语句)
IF ERROR THEN
"DB_Global".iOB40_ErrCnt := "DB_Global".iOB40_ErrCnt + 1;
END_IF
关键说明:
- 全部变量均位于
VAR_TEMP区,无VAR或VAR_IN_OUT; - 未使用任何循环、字符串、浮点函数、指针;
TOD(当前时间)为系统常量,读取无开销;"DB_Global"为已启用“优化的块访问”的全局DB;- 防抖与频率限制逻辑均采用时间差比较(
TOD - ...),避免循环等待。
五、调试与验证必做步骤
代码写完不等于可用。必须通过以下四项验证,缺一不可:
-
静态检查(编译前):
- 在TIA Portal中右键OB → “检查块” → 确认无
Warning 22(非异步安全调用)或Warning 15(变量声明违规); - 查看“块信息” → “属性” → 确认“语言”为
ST,“类型”为OB,“优先级”与硬件组态一致。
- 在TIA Portal中右键OB → “检查块” → 确认无
-
执行时间实测(在线监控):
- 下载程序后,在“监视表”中添加系统变量:
"OB40".Runtime(单位:μs); - 强制触发中断100次 → 记录最大值;
- 要求:最大值 < 80%阈值(S7-1500为400μs,S7-1200为800μs)。
- 下载程序后,在“监视表”中添加系统变量:
-
中断丢失测试:
- 用信号发生器向DI通道发送1kHz方波(占空比50%);
- 在OB40内对
"DB_Global".iTotalPulses每次加1; - 同时在OB1中每秒读取该值并计算实际频率;
- 合格标准:实测频率 ≥ 990Hz(允许1%丢失,超过即需优化)。
-
压力工况验证:
- 同时触发3个不同中断OB(如OB40、OB35、OB82);
- 监控各OB的
Runtime及诊断缓冲区; - 确认无
16#8001、16#8004、16#800F报错,且各OB执行时间未显著增长。
六、进阶技巧:跨OB数据同步安全方案
当需在中断OB与主循环OB1间共享数据(如传感器采样值),必须解决竞态问题。绝对禁止直接读写同一变量。
-
双缓冲机制(推荐,零风险):
- 创建全局DB,含两套相同结构的变量组:
BufferA和BufferB; - OB40只写
BufferA,OB1只读BufferB; - OB1末尾执行:
BufferB := BufferA;; - OB40开头执行:
BufferA.NewData := TRUE;; - OB1通过检查
BufferB.NewData判断数据新鲜度。
- 创建全局DB,含两套相同结构的变量组:
-
原子标志位法(轻量级):
- 使用单个
BOOL标志(如"DB_Sync".bDataReady); - OB40写完数据后执行:
"DB_Sync".bDataReady := TRUE;; - OB1检测到
TRUE后立即读取数据,并立刻置FALSE; - 关键:标志位操作必须为单条ST语句(如
:=),确保原子性。
- 使用单个
-
绝对禁止方案:
- 在OB40中直接修改OB1使用的中间变量(如
"DB_Mid".fValue); - 在OB1中用
IF "DB_Mid".fValue > 0 THEN ... END_IF判断——因读写非原子,可能导致fValue被部分更新,产生无效中间值。
- 在OB40中直接修改OB1使用的中间变量(如
七、常见报错速查表
| 错误代码 | 中文描述 | 根本原因 | 快速定位方法 |
|---|---|---|---|
16#8001 |
OB执行超时 | 循环、浮点函数、长数组拷贝 | 查看 OBx.Runtime 值;注释掉疑似语句分段测试 |
16#8004 |
优先级冲突 | 在低优先级OB中调用高优先级OB | 检查所有 CALL 语句的目标OB编号 |
16#800F |
数据访问冲突 | 访问非优化DB或地址越界 | 检查DB属性;确认所有数组下标在 [0..n] 内 |
16#8010 |
异步调用错误 | 调用了非异步安全FB/FC | 在块调用处右键 → “属性” → 查看“异步安全”复选框 |
16#8020 |
堆栈溢出 | VAR_TEMP声明过大(>2KB)或递归调用 | 减少临时变量;禁用所有递归逻辑 |

暂无评论,快来抢沙发吧!