跳到正文
Learn-SCL
SCL 示例库/SCL 安全使能开关:三位置、BCD 工位号与左右互锁

SCL 安全使能开关:三位置、BCD 工位号与左右互锁

三位置使能开关的释放锁存、4 位 BCD 工位号、左右点动边沿与互锁、16 路工位输出和 LED 指示。

综合 S7-1200 S7-1500 使能开关 BCD 数组 互锁 边沿检测 1 块 / 127 行 可仿真 TIA 已验证

一句话:三位置开关的上升沿 + 有人 + 急停正常才置 Release;松开开关、急停断开或工位号变化就撤销。BCD 四位权值 1/2/4/8 加偏移得到工位号,左右点动各取上升沿并互锁,只有当前工位号对应的那一路输出为 1。

导入到我的 TIA 项目

通过 TiaLink 把本示例的 1 个 SCL 块作为“外部源”导入你当前打开的 TIA Portal 项目:自动生成块、编译,并清理外部源。导入后不会自动保存;FB 示例导入后需要你在 OB 里调用。

TiaLink 设置

默认 http://localhost:8080。也可以在本页地址后加 ?tialink= 临时指定。

TiaLink 启动时若设置了 --api-token,这里要填相同的值;只保存在本机浏览器 localStorage,不会上传。

免责声明:本页是标准 SCL 的教学示例,不是经过安全认证的安全程序。真实设备的安全功能必须使用 F-CPU、F-block、安全 I/O,并完成风险评估和验证;不能用本页代码替代安全回路。

三位置使能开关(Zustimmtaster)是手动工位的常见操作件:按住中间位置才允许点动,松开就停。这个示例把它和工位号选择、左右点动、16 路输出组合在一起,重点讲“释放锁存”和“边沿 + 互锁”。

三位置使能开关、Release 锁存、工位号变化复位和左右点动输出的时序
开关上升沿置 Release;松开、急停断开或工位号变化撤销;左右点动只在 Release 有效时响应上升沿。

概述

FB_SafetyApproval 的输入分三组:

  • 安全条件:NotHaltOk(急停回路正常)、ThreePosSwitch(三位置开关压下)、ActivitySensor(工位有人);
  • 工位号:Bit0..Bit3(BCD 权值 1/2/4/8)+ BcdOffset;
  • 操作:SwitchLeft / SwitchRight(左右点动)、LedTakt(LED 闪烁节拍)。

输出包括 LedRed / LedGreen、OutLeft / OutRight、BcdNumber、LeftChannel / RightChannel,以及 OutputLeft[1..16] / OutputRight[1..16] 两组工位输出。

Release 是核心状态:三位置开关的上升沿 + 有人 + 急停正常才置位;松开开关、急停断开、或工位号变化都会撤销。左右点动各取上升沿,并且互相锁死:一边按着,另一边不能动。

原工程 zustimmtaster_zeus_ssp.scl 变量名是德语,单路输出循环只到 15;本页改成英文命名、1..16 全赋值,并把同仓库的 NotAusLeuchtring 作为变体小节。

你将学到

  • 用“上升沿置位、条件撤销”的方式实现使能释放锁存;
  • 4 位 BCD + 偏移计算工位号;
  • 左右点动的边沿检测与互锁;
  • 用 FOR 循环把工位号映射到 16 路数组输出;
  • 为什么 LED 和输出要放在释放逻辑之后,避免一个扫描周期的滞后。

接口与变量

下表由构建器从 SCL 源码的接口声明自动生成;页面上展示的代码就是导入 TIA 的同一份源码。

FB_SafetyApproval FB

区块名称类型说明
输入NotHaltOkBool
输入ThreePosSwitchBool
输入ActivitySensorBool
输入Bit0Bool
输入Bit1Bool
输入Bit2Bool
输入Bit3Bool
输入SwitchRightBool
输入SwitchLeftBool
输入LedTaktBool
输入BcdOffsetInt
输出LedRedBool
输出LedGreenBool
输出OutRightBool
输出OutLeftBool
输出BcdNumberInt
输出LeftChannelInt
输出RightChannelInt
输出OutputLeftARRAY[1..16] OF Bool
输出OutputRightARRAY[1..16] OF Bool
静态ReleaseBool
静态ThreePosPrevBool
静态LeftPrevBool
静态RightPrevBool
静态BcdPrevInt
静态BcdValueInt
静态OutLeftIntBool
静态OutRightIntBool
静态iInt
临时ThreePosRisingBool
临时LeftRisingBool
临时RightRisingBool
临时BcdChangedBool

程序结构

导入顺序以下表为准:被依赖的块/类型先导入;本页已按依赖排好。

  1. FB_SafetyApproval FB 使能释放、BCD 工位号、左右互锁与 16 路输出 · 127 行 · 5 段

FB_SafetyApproval FB

使能释放、BCD 工位号、左右互锁与 16 路输出 · 127 行 · 源文件 blocks/FB_SafetyApproval.scl

查看 / 复制完整块源码
FB_SafetyApproval.scl
1FUNCTION_BLOCK "FB_SafetyApproval"
2{ S7_Optimized_Access := 'TRUE' }
3VERSION : 0.1
4   VAR_INPUT
5      NotHaltOk : Bool;        // 急停回路正常(TRUE = 允许)
6      ThreePosSwitch : Bool;   // 三位置使能开关压下(中间位置)
7      ActivitySensor : Bool;   // 墙面支架检测到有人操作
8      Bit0 : Bool;             // BCD 位 0,权值 1
9      Bit1 : Bool;             // BCD 位 1,权值 2
10      Bit2 : Bool;             // BCD 位 2,权值 4
11      Bit3 : Bool;             // BCD 位 3,权值 8
12      SwitchRight : Bool;      // 右手点动开关
13      SwitchLeft : Bool;       // 左手点动开关
14      LedTakt : Bool;          // LED 闪烁节拍
15      BcdOffset : Int;         // BCD 偏移(工位号)
16   END_VAR
17 
18   VAR_OUTPUT
19      LedRed : Bool;           // 急停未复位时常亮
20      LedGreen : Bool;         // 允许操作时亮/闪
21      OutRight : Bool;         // 右输出
22      OutLeft : Bool;          // 左输出
23      BcdNumber : Int := 0;    // 当前 BCD 编号
24      LeftChannel : Int := 0;  // 当前选中的左工位号(0 = 无)
25      RightChannel : Int := 0; // 当前选中的右工位号(0 = 无)
26      OutputLeft : Array[1..16] of Bool;   // 左工位单路输出
27      OutputRight : Array[1..16] of Bool;  // 右工位单路输出
28   END_VAR
29 
30   VAR
31      Release : Bool := FALSE;   // 使能释放锁存
32      ThreePosPrev : Bool;       // 三位置开关上一周期
33      LeftPrev : Bool;           // 左开关上一周期
34      RightPrev : Bool;          // 右开关上一周期
35      BcdPrev : Int := 0;        // 上一周期 BCD 值
36      BcdValue : Int := 0;       // 本周期 BCD 值
37      OutLeftInt : Bool := FALSE;
38      OutRightInt : Bool := FALSE;
39      i : Int;
40   END_VAR
41 
42   VAR_TEMP
43      ThreePosRising : Bool;
44      LeftRising : Bool;
45      RightRising : Bool;
46      BcdChanged : Bool;
47   END_VAR
48BEGIN
49   REGION BCD 编号
50      // 4 位 BCD 输入加偏移 = 工位号;每一位的权值是 1/2/4/8。
51      #BcdValue := #BcdOffset;
52      IF #Bit0 THEN #BcdValue := #BcdValue + 1; END_IF;
53      IF #Bit1 THEN #BcdValue := #BcdValue + 2; END_IF;
54      IF #Bit2 THEN #BcdValue := #BcdValue + 4; END_IF;
55      IF #Bit3 THEN #BcdValue := #BcdValue + 8; END_IF;
56      #BcdChanged := #BcdValue <> #BcdPrev;
57      #BcdPrev := #BcdValue;
58      #BcdNumber := #BcdValue;
59   END_REGION
60 
61   REGION 使能释放
62      // 三位置开关的上升沿 + 有人 + 急停正常 + 编号没变,才允许操作。
63      #ThreePosRising := #ThreePosSwitch AND NOT #ThreePosPrev;
64      #ThreePosPrev := #ThreePosSwitch;
65 
66      // 工位号变化时,已经使能的输出必须先撤销;首次上电 BCD 变化不阻塞使能。
67      IF #BcdChanged AND #Release THEN
68         #Release := FALSE;
69      END_IF;
70 
71      IF #ThreePosRising AND #ActivitySensor AND #NotHaltOk THEN
72         #Release := TRUE;
73      END_IF;
74 
75      // 松开使能开关或急停断开,都必须重新按下使能开关。
76      IF NOT #ThreePosSwitch OR NOT #NotHaltOk THEN
77         #Release := FALSE;
78      END_IF;
79   END_REGION
80 
81   REGION LED 指示
82      // 急停不正常:红灯常亮;正常时绿灯按“未使能闪烁 / 已使能常亮”显示。
83      // 放在释放逻辑之后,使能生效的同一周期绿灯就常亮。
84      #LedRed := NOT #NotHaltOk;
85      #LedGreen := #NotHaltOk AND #ActivitySensor AND
86                   ((NOT #ThreePosSwitch AND #LedTakt) OR (#ThreePosSwitch AND #Release));
87   END_REGION
88 
89   REGION 左右点动输出
90      // 左/右都取上升沿,且互锁:另一边按着时不能动。
91      #LeftRising := #SwitchLeft AND NOT #LeftPrev;
92      #LeftPrev := #SwitchLeft;
93      IF #LeftRising AND #Release AND #NotHaltOk AND NOT #SwitchRight THEN
94         #OutLeftInt := TRUE;
95      ELSIF NOT #SwitchLeft OR NOT #Release THEN
96         #OutLeftInt := FALSE;
97      END_IF;
98 
99      #RightRising := #SwitchRight AND NOT #RightPrev;
100      #RightPrev := #SwitchRight;
101      IF #RightRising AND #Release AND #NotHaltOk AND NOT #SwitchLeft THEN
102         #OutRightInt := TRUE;
103      ELSIF NOT #SwitchRight OR NOT #Release THEN
104         #OutRightInt := FALSE;
105      END_IF;
106 
107      #OutLeft := #OutLeftInt;
108      #OutRight := #OutRightInt;
109   END_REGION
110 
111   REGION 单路输出
112      // 只有当前工位号对应的那一路输出跟随左/右总输出;1..16 全部赋值。
113      #LeftChannel := 0;
114      #RightChannel := 0;
115      FOR #i := 1 TO 16 DO
116         #OutputLeft[#i] := (#BcdNumber = #i) AND #OutLeft;
117         #OutputRight[#i] := (#BcdNumber = #i) AND #OutRight;
118         IF #OutputLeft[#i] THEN
119            #LeftChannel := #i;
120         END_IF;
121         IF #OutputRight[#i] THEN
122            #RightChannel := #i;
123         END_IF;
124      END_FOR;
125   END_REGION
126END_FUNCTION_BLOCK
127 

FB_SafetyApproval / BCD 编号 — 四位权值加偏移

FB_SafetyApproval.scl · BCD 编号
49   REGION BCD 编号
50      // 4 位 BCD 输入加偏移 = 工位号;每一位的权值是 1/2/4/8。
51      #BcdValue := #BcdOffset;
52      IF #Bit0 THEN #BcdValue := #BcdValue + 1; END_IF;
53      IF #Bit1 THEN #BcdValue := #BcdValue + 2; END_IF;
54      IF #Bit2 THEN #BcdValue := #BcdValue + 4; END_IF;
55      IF #Bit3 THEN #BcdValue := #BcdValue + 8; END_IF;
56      #BcdChanged := #BcdValue <> #BcdPrev;
57      #BcdPrev := #BcdValue;
58      #BcdNumber := #BcdValue;
59   END_REGION

目的:把 4 个布尔输入转换成 1..16 的工位号。

条件 → 动作:BcdValue := BcdOffset;Bit0 为 1 加 1,Bit1 加 2,Bit2 加 4,Bit3 加 8;BcdChanged := BcdValue <> BcdPrev;BcdNumber := BcdValue。

为什么:BCD 的本质是“每一位有固定权值”,用加法比查表更直观;BcdOffset 让同一个程序可以复用到不同工位段。

注意:BcdChanged 只在值变化的那个扫描周期为 1。工位号变化时要撤销 Release,操作员必须重新按下使能开关;否则在工位切换过程中可能带着旧的释放信号动作。

FB_SafetyApproval / 使能释放 — 上升沿置位,松手/急停/换工位撤销

FB_SafetyApproval.scl · 使能释放
61   REGION 使能释放
62      // 三位置开关的上升沿 + 有人 + 急停正常 + 编号没变,才允许操作。
63      #ThreePosRising := #ThreePosSwitch AND NOT #ThreePosPrev;
64      #ThreePosPrev := #ThreePosSwitch;
65 
66      // 工位号变化时,已经使能的输出必须先撤销;首次上电 BCD 变化不阻塞使能。
67      IF #BcdChanged AND #Release THEN
68         #Release := FALSE;
69      END_IF;
70 
71      IF #ThreePosRising AND #ActivitySensor AND #NotHaltOk THEN
72         #Release := TRUE;
73      END_IF;
74 
75      // 松开使能开关或急停断开,都必须重新按下使能开关。
76      IF NOT #ThreePosSwitch OR NOT #NotHaltOk THEN
77         #Release := FALSE;
78      END_IF;
79   END_REGION

目的:保证“按住才允许”,并且松手后必须重新按一次。

条件 → 动作:

  1. ThreePosRising := ThreePosSwitch AND NOT ThreePosPrev;每个周期更新 ThreePosPrev;
  2. 如果 BcdChanged AND Release,先撤销 Release;
  3. 如果 ThreePosRising AND ActivitySensor AND NotHaltOk,置 Release := TRUE;
  4. 如果 NOT ThreePosSwitch OR NOT NotHaltOk,撤销 Release。

为什么:第 2 步只撤销“已经使能”的旧状态,首次上电 BCD 从 0 变成非 0 时不会阻塞操作员第一次按开关。第 3 步用上升沿,保证一直按住开关不会在松手后自动恢复。

注意:原工程的顺序是先按 BCD_is_diff 复位、再用上升沿置位,所以“换工位 + 按开关”同一个扫描周期时,置位可能覆盖复位;本页把“换工位撤销”放在置位之前,优先保证换工位后必须重新按。ActivitySensor 丢失在原工程里不是复位条件;是否要加,取决于现场风险评估。

FB_SafetyApproval / LED 指示 — 红灯急停,绿灯闪烁/常亮

FB_SafetyApproval.scl · LED 指示
81   REGION LED 指示
82      // 急停不正常:红灯常亮;正常时绿灯按“未使能闪烁 / 已使能常亮”显示。
83      // 放在释放逻辑之后,使能生效的同一周期绿灯就常亮。
84      #LedRed := NOT #NotHaltOk;
85      #LedGreen := #NotHaltOk AND #ActivitySensor AND
86                   ((NOT #ThreePosSwitch AND #LedTakt) OR (#ThreePosSwitch AND #Release));
87   END_REGION

目的:给操作员一个明确的状态指示。

条件 → 动作:LedRed := NOT NotHaltOk;LedGreen := NotHaltOk AND ActivitySensor AND ((NOT ThreePosSwitch AND LedTakt) OR (ThreePosSwitch AND Release))。

为什么:急停不正常时红灯常亮;正常但未使能时绿灯闪烁;使能有效时绿灯常亮。

注意:LED 赋值必须放在释放逻辑之后。如果放在前面,按下开关的同一个扫描周期里 Release 还是旧值,绿灯会慢一个周期才亮——仿真里能明显看到,现场也会觉得“按了没反应”。

FB_SafetyApproval / 左右点动输出 — 上升沿 + 互锁

FB_SafetyApproval.scl · 左右点动输出
89   REGION 左右点动输出
90      // 左/右都取上升沿,且互锁:另一边按着时不能动。
91      #LeftRising := #SwitchLeft AND NOT #LeftPrev;
92      #LeftPrev := #SwitchLeft;
93      IF #LeftRising AND #Release AND #NotHaltOk AND NOT #SwitchRight THEN
94         #OutLeftInt := TRUE;
95      ELSIF NOT #SwitchLeft OR NOT #Release THEN
96         #OutLeftInt := FALSE;
97      END_IF;
98 
99      #RightRising := #SwitchRight AND NOT #RightPrev;
100      #RightPrev := #SwitchRight;
101      IF #RightRising AND #Release AND #NotHaltOk AND NOT #SwitchLeft THEN
102         #OutRightInt := TRUE;
103      ELSIF NOT #SwitchRight OR NOT #Release THEN
104         #OutRightInt := FALSE;
105      END_IF;
106 
107      #OutLeft := #OutLeftInt;
108      #OutRight := #OutRightInt;
109   END_REGION

目的:只在使能有效时响应左右点动的“按下瞬间”,并禁止同时动作。

条件 → 动作:

  • LeftRising := SwitchLeft AND NOT LeftPrev;按住不放只触发一次;
  • 左输出置位条件:LeftRising AND Release AND NotHaltOk AND NOT SwitchRight;
  • 左输出复位条件:NOT SwitchLeft OR NOT Release;
  • 右输出同理,互锁条件反过来。

为什么:点动是“按一下走一下”,用上升沿避免长按连续触发;互锁保证左右不会同时输出。

注意:OutLeftInt / OutRightInt 是静态变量,保证按住期间输出保持;最后再镜像到 OutLeft / OutRight,避免 TIA 对“输出可能未初始化”报警告。

FB_SafetyApproval / 单路输出 — 工位号对应哪一路

FB_SafetyApproval.scl · 单路输出
111   REGION 单路输出
112      // 只有当前工位号对应的那一路输出跟随左/右总输出;1..16 全部赋值。
113      #LeftChannel := 0;
114      #RightChannel := 0;
115      FOR #i := 1 TO 16 DO
116         #OutputLeft[#i] := (#BcdNumber = #i) AND #OutLeft;
117         #OutputRight[#i] := (#BcdNumber = #i) AND #OutRight;
118         IF #OutputLeft[#i] THEN
119            #LeftChannel := #i;
120         END_IF;
121         IF #OutputRight[#i] THEN
122            #RightChannel := #i;
123         END_IF;
124      END_FOR;
125   END_REGION

目的:在 16 路输出里只激活当前工位号对应的那一路。

条件 → 动作:FOR i := 1 TO 16,OutputLeft[i] := (BcdNumber = i) AND OutLeft;OutputRight[i] 同理;同时把命中的下标写到 LeftChannel / RightChannel。

为什么:HMI 和现场接线通常按工位号分配输出,用数组下标直接映射最直观;LeftChannel / RightChannel 给 HMI 一个标量,不需要读数组元素。

注意:原工程循环写的是 FOR i := 1 TO 15,但数组是 1..16,第 16 路永远不会赋值;本页改成 1..16。循环里必须每周期给 16 个元素都赋值,不能只写命中的那一路,否则旧值会残留。

仿真验证:让 Agent 写场景并执行(不需要博途)

推荐:让 Agent 自己设计场景

不需要你手写 scenario。把下面的提示词复制给已连接 PLC 云仿真 MCP 的 AI 助手(Claude Code / Cursor / VS Code 等),它会阅读源码、自己设计正常/边界/异常场景,并调用 scl_run_scenario 执行;新用户有 100 次免费调用。

  1. 复制提示词(包含完整 SCL 源码和任务说明)。
  2. 粘贴给已配置 sim MCP 的 Agent,让它先 scl_validate,再执行自己写的 scl_run_scenario。
  3. 看 Agent 返回的 PASS/FAIL、输入/期望/实际;需要精确复现时,再对照下方本站回归基线。
AGENT PROMPT
你已连接 PLC 云仿真 MCP(sim-cloud)……

任务:阅读下面的 SCL 示例,自己设计 3 个场景并用 scl_run_scenario 执行:
1) 正常路径
2) 边界/临界值
3) 异常、互锁或超时
每个场景请给出输入、期望、实际结果和 PASS/FAIL,并说明场景设计理由。
不要照抄现成场景。

示例:SCL 安全使能开关:三位置、BCD 工位号与左右互锁
// 复制后包含完整 SCL 源码和 MCP 参数说明

页面上只显示提示词开头;复制的内容包含全部块源码和 MCP 参数说明。

查看本站回归基线(固定场景,不是让你手写)

下面是本站构建时跑过的固定场景和预期输出,用来做回归对比或精确复现;不是要求你手写 scenario。推荐先让 Agent 自己写场景,再和这份基线对照。

EXPECTED
PASS  急停不正常:红灯亮,不允许操作 (1 step(s), 1 scan(s))
PASS  正常上电:绿灯闪烁,按下使能后常亮 (3 step(s), 3 scan(s))
PASS  工位 1 左点动:只有第 1 路输出 (3 step(s), 3 scan(s))
PASS  左右互锁:左手按住时右手无效 (6 step(s), 6 scan(s))
PASS  切换工位号:释放使能,必须重新按下 (7 step(s), 7 scan(s))
PASS  偏移 1 + BCD 15 = 工位 16:第 16 路输出 (3 step(s), 3 scan(s))
RESULT: ALL PASS (23 scan(s))
BASELINE JSON
{
  "sources": [
    {
      "name": "blocks/FB_SafetyApproval.scl",
      "source": "FUNCTION_BLOCK \"FB_SafetyApproval\"\n{ S7_Optimized_Access := 'TRUE' }\nVERSION : 0.1\n   VAR_INPUT\n      NotHaltOk : Bool;        // 急停回路正常(TRUE = 允许)\n      ThreePosSwitch : Bool;   // 三位置使能开关压下(中间位置)\n      ActivitySensor : Bool;   // 墙面支架检测到有人操作\n      Bit0 : Bool;             // BCD 位 0,权值 1\n      Bit1 : Bool;             // BCD 位 1,权值 2\n      Bit2 : Bool;             // BCD 位 2,权值 4\n      Bit3 : Bool;             // BCD 位 3,权值 8\n      SwitchRight : Bool;      // 右手点动开关\n      SwitchLeft : Bool;       // 左手点动开关\n      LedTakt : Bool;          // LED 闪烁节拍\n      BcdOffset : Int;         // BCD 偏移(工位号)\n   END_VAR\n\n   VAR_OUTPUT\n      LedRed : Bool;           // 急停未复位时常亮\n      LedGreen : Bool;         // 允许操作时亮/闪\n      OutRight : Bool;         // 右输出\n      OutLeft : Bool;          // 左输出\n      BcdNumber : Int := 0;    // 当前 BCD 编号\n      LeftChannel : Int := 0;  // 当前选中的左工位号(0 = 无)\n      RightChannel : Int := 0; // 当前选中的右工位号(0 = 无)\n      OutputLeft : Array[1..16] of Bool;   // 左工位单路输出\n      OutputRight : Array[1..16] of Bool;  // 右工位单路输出\n   END_VAR\n\n   VAR\n      Release : Bool := FALSE;   // 使能释放锁存\n      ThreePosPrev : Bool;       // 三位置开关上一周期\n      LeftPrev : Bool;           // 左开关上一周期\n      RightPrev : Bool;          // 右开关上一周期\n      BcdPrev : Int := 0;        // 上一周期 BCD 值\n      BcdValue : Int := 0;       // 本周期 BCD 值\n      OutLeftInt : Bool := FALSE;\n      OutRightInt : Bool := FALSE;\n      i : Int;\n   END_VAR\n\n   VAR_TEMP\n      ThreePosRising : Bool;\n      LeftRising : Bool;\n      RightRising : Bool;\n      BcdChanged : Bool;\n   END_VAR\nBEGIN\n   REGION BCD 编号\n      // 4 位 BCD 输入加偏移 = 工位号;每一位的权值是 1/2/4/8。\n      #BcdValue := #BcdOffset;\n      IF #Bit0 THEN #BcdValue := #BcdValue + 1; END_IF;\n      IF #Bit1 THEN #BcdValue := #BcdValue + 2; END_IF;\n      IF #Bit2 THEN #BcdValue := #BcdValue + 4; END_IF;\n      IF #Bit3 THEN #BcdValue := #BcdValue + 8; END_IF;\n      #BcdChanged := #BcdValue <> #BcdPrev;\n      #BcdPrev := #BcdValue;\n      #BcdNumber := #BcdValue;\n   END_REGION\n\n   REGION 使能释放\n      // 三位置开关的上升沿 + 有人 + 急停正常 + 编号没变,才允许操作。\n      #ThreePosRising := #ThreePosSwitch AND NOT #ThreePosPrev;\n      #ThreePosPrev := #ThreePosSwitch;\n\n      // 工位号变化时,已经使能的输出必须先撤销;首次上电 BCD 变化不阻塞使能。\n      IF #BcdChanged AND #Release THEN\n         #Release := FALSE;\n      END_IF;\n\n      IF #ThreePosRising AND #ActivitySensor AND #NotHaltOk THEN\n         #Release := TRUE;\n      END_IF;\n\n      // 松开使能开关或急停断开,都必须重新按下使能开关。\n      IF NOT #ThreePosSwitch OR NOT #NotHaltOk THEN\n         #Release := FALSE;\n      END_IF;\n   END_REGION\n\n   REGION LED 指示\n      // 急停不正常:红灯常亮;正常时绿灯按“未使能闪烁 / 已使能常亮”显示。\n      // 放在释放逻辑之后,使能生效的同一周期绿灯就常亮。\n      #LedRed := NOT #NotHaltOk;\n      #LedGreen := #NotHaltOk AND #ActivitySensor AND\n                   ((NOT #ThreePosSwitch AND #LedTakt) OR (#ThreePosSwitch AND #Release));\n   END_REGION\n\n   REGION 左右点动输出\n      // 左/右都取上升沿,且互锁:另一边按着时不能动。\n      #LeftRising := #SwitchLeft AND NOT #LeftPrev;\n      #LeftPrev := #SwitchLeft;\n      IF #LeftRising AND #Release AND #NotHaltOk AND NOT #SwitchRight THEN\n         #OutLeftInt := TRUE;\n      ELSIF NOT #SwitchLeft OR NOT #Release THEN\n         #OutLeftInt := FALSE;\n      END_IF;\n\n      #RightRising := #SwitchRight AND NOT #RightPrev;\n      #RightPrev := #SwitchRight;\n      IF #RightRising AND #Release AND #NotHaltOk AND NOT #SwitchLeft THEN\n         #OutRightInt := TRUE;\n      ELSIF NOT #SwitchRight OR NOT #Release THEN\n         #OutRightInt := FALSE;\n      END_IF;\n\n      #OutLeft := #OutLeftInt;\n      #OutRight := #OutRightInt;\n   END_REGION\n\n   REGION 单路输出\n      // 只有当前工位号对应的那一路输出跟随左/右总输出;1..16 全部赋值。\n      #LeftChannel := 0;\n      #RightChannel := 0;\n      FOR #i := 1 TO 16 DO\n         #OutputLeft[#i] := (#BcdNumber = #i) AND #OutLeft;\n         #OutputRight[#i] := (#BcdNumber = #i) AND #OutRight;\n         IF #OutputLeft[#i] THEN\n            #LeftChannel := #i;\n         END_IF;\n         IF #OutputRight[#i] THEN\n            #RightChannel := #i;\n         END_IF;\n      END_FOR;\n   END_REGION\nEND_FUNCTION_BLOCK\n"
    }
  ],
  "scenario": {
    "docs": [
      "FB_SafetyApproval.scl"
    ],
    "block": "FB_SafetyApproval",
    "tickMs": 10,
    "seed": {
      "NotHaltOk": true,
      "ThreePosSwitch": false,
      "ActivitySensor": true,
      "Bit0": true,
      "Bit1": false,
      "Bit2": false,
      "Bit3": false,
      "SwitchRight": false,
      "SwitchLeft": false,
      "LedTakt": true,
      "BcdOffset": 0
    },
    "always": [
      "!(OutLeft && OutRight)"
    ],
    "scenarios": [
      {
        "name": "急停不正常:红灯亮,不允许操作",
        "steps": [
          {
            "set": {
              "NotHaltOk": false,
              "ThreePosSwitch": true
            },
            "expect": {
              "LedRed": true,
              "LedGreen": false,
              "OutLeft": false,
              "OutRight": false
            }
          }
        ]
      },
      {
        "name": "正常上电:绿灯闪烁,按下使能后常亮",
        "steps": [
          {
            "set": {
              "NotHaltOk": true,
              "ThreePosSwitch": false
            },
            "expect": {
              "LedRed": false,
              "LedGreen": true
            }
          },
          {
            "set": {
              "ThreePosSwitch": true
            },
            "expect": {
              "LedGreen": true,
              "BcdNumber": 1
            }
          },
          {
            "set": {
              "ThreePosSwitch": true
            },
            "expect": {
              "LedGreen": true
            }
          }
        ]
      },
      {
        "name": "工位 1 左点动:只有第 1 路输出",
        "steps": [
          {
            "set": {
              "ThreePosSwitch": true
            }
          },
          {
            "set": {
              "SwitchLeft": true
            },
            "expect": {
              "OutLeft": true,
              "OutRight": false,
              "LeftChannel": 1,
              "RightChannel": 0
            }
          },
          {
            "set": {
              "SwitchLeft": false
            },
            "expect": {
              "OutLeft": false,
              "LeftChannel": 0
            }
          }
        ]
      },
      {
        "name": "左右互锁:左手按住时右手无效",
        "steps": [
          {
            "set": {
              "ThreePosSwitch": true
            }
          },
          {
            "set": {
              "SwitchLeft": true
            },
            "expect": {
              "OutLeft": true
            }
          },
          {
            "set": {
              "SwitchRight": true
            },
            "expect": {
              "OutLeft": true,
              "OutRight": false
            }
          },
          {
            "set": {
              "SwitchLeft": false
            },
            "expect": {
              "OutLeft": false
            }
          },
          {
            "set": {
              "SwitchRight": false
            }
          },
          {
            "set": {
              "SwitchRight": true
            },
            "expect": {
              "OutRight": true,
              "OutLeft": false
            }
          }
        ]
      },
      {
        "name": "切换工位号:释放使能,必须重新按下",
        "steps": [
          {
            "set": {
              "ThreePosSwitch": true
            }
          },
          {
            "set": {
              "SwitchLeft": true
            },
            "expect": {
              "OutLeft": true
            }
          },
          {
            "set": {
              "SwitchLeft": false,
              "Bit1": true
            },
            "expect": {
              "BcdNumber": 3,
              "OutLeft": false
            }
          },
          {
            "set": {
              "SwitchLeft": true
            },
            "expect": {
              "OutLeft": false
            }
          },
          {
            "set": {
              "SwitchLeft": false,
              "ThreePosSwitch": false
            }
          },
          {
            "set": {
              "ThreePosSwitch": true
            }
          },
          {
            "set": {
              "SwitchLeft": true
            },
            "expect": {
              "OutLeft": true,
              "LeftChannel": 3
            }
          }
        ]
      },
      {
        "name": "偏移 1 + BCD 15 = 工位 16:第 16 路输出",
        "steps": [
          {
            "set": {
              "BcdOffset": 1,
              "Bit0": true,
              "Bit1": true,
              "Bit2": true,
              "Bit3": true
            }
          },
          {
            "set": {
              "ThreePosSwitch": true
            },
            "expect": {
              "BcdNumber": 16
            }
          },
          {
            "set": {
              "SwitchLeft": true
            },
            "expect": {
              "OutLeft": true,
              "LeftChannel": 16
            }
          }
        ]
      }
    ]
  }
}

打开 PLC 云仿真

关键 SCL 语法

写法 含义
Array[1..16] of Bool 16 路输出数组,下标 1..16
FOR #i := 1 TO 16 DO ... END_FOR 遍历所有工位
AND NOT 上升沿:当前为 1、上周期为 0
#BcdValue := #BcdValue + 1 BCD 位权值累加
#OutputLeft[#i] := ... 数组下标赋值
REGION ... END_REGION 代码分段

常见坑

  • 松手后没有撤销 Release:使能开关变成“按过一次就一直有效”,失去松手即停的意义。
  • 只取 ThreePosSwitch 电平,不取上升沿:一直按住时,工位号变化后可能自动恢复使能;必须重新按。
  • 换工位不撤销 Release:操作员可以在带着释放信号的情况下切到另一个工位,输出会跟到新工位。
  • LED 放在释放逻辑之前:绿灯慢一个扫描周期,调试时容易误判。
  • 单路循环只到 15:数组第 16 路永远是旧值;循环上界必须和数组声明一致。
  • 用旧的 BCD_Result 做单路比较:工位号变化后输出可能还停在旧工位;统一用当前 BcdNumber。
  • 把教学代码当安全功能:本页没有 F 认证、没有安全诊断、没有双通道;真实安全回路必须用安全 PLC 和安全程序。

变体与扩展

同仓库的 NotAusLeuchtring/NotAusLeuchtring.scl 只有 6 行,讲解“急停灯环”时可以并入本节:

SCL
1FUNCTION "NotAusLeuchtring" : Void
2   VAR_INPUT
3      EStopActive : Bool;   // 急停按下
4      Blink : Bool;         // 正常时的闪烁节拍
5   END_VAR
6   VAR_OUTPUT
7      Lamp : Bool;
8   END_VAR
9BEGIN
10   IF #EStopActive THEN
11      #Lamp := TRUE;    // 急停时常亮
12   ELSE
13      #Lamp := #Blink;  // 正常时随节拍闪烁
14   END_IF;
15END_FUNCTION
  • 需要多工位时,把 BcdOffset 做成输入,每个工位段共用同一个 FB 实例。
  • 需要“使能有效但左右都不动”的提示时,增加 Ready := Release AND NotHaltOk 输出给 HMI。
  • 需要记录“哪个工位、哪个方向、什么时候动作”时,在左右输出上升沿处触发一个日志/报警块。
  • 真实安全项目里,使能开关应接到 F-DI,使用安全程序的标准使能块;本页只作为标准程序的操作逻辑教学。

常见问题

使能开关为什么必须重新按一次?

三位置开关松开就撤销 Release;急停断开或 BCD 工位号变化也会撤销。要重新使能必须是新的上升沿,不能靠一直按住开关自动恢复,这是使能开关“松手即停”的基本要求。

工位号 BCD 怎么算?

四位输入 Bit0..Bit3 的权值分别是 1/2/4/8,加上 BcdOffset 得到工位号;例如 Bit0+Bit1=3,再加偏移 1 就是工位 4。单路输出用 FOR 循环比较 BcdNumber 和数组下标。

验证状态

项目结果日期证据
本地仿真场景PASS(23 scans)2026-11-01页面下方 `scl_run_scenario` 请求可复现
PLC 云仿真PASS2026-11-01`scl_run_scenario`(托管 MCP)
TIA 导入编译PASS(0 error / 0 warning)2026-11-01本地 SCL 仿真与 PLC 云仿真(scl_run_scenario)23 扫描通过;TIA Portal V19 + TiaLink 1.4.0.0 导入 PLC_2,编译 0 错误 / 0 警告,已保存。

本地仿真引擎验证语法、结构和逐扫描行为;TIA 编译验证在 Windows + TIA Portal + TiaLink 上完成。两者不能互相替代。

来源与许可

本页示例改编自 github.com/lopez-dev/Siemens-SCL-Source-Files 的 Zustimmtaster/zustimmtaster_zeus_ssp.scl,原作者 Daniel Cáceres López,以 MIT 许可发布。中文注释、接口调整、仿真场景与页面讲解由 Learn-SCL 编写。

原文件用德语变量名,单路输出循环只到 15;本站改为英文命名、1..16 全赋值,并把同仓库的 NotAusLeuchtring 作为变体小节。

示例源码以 MIT 许可发布(上游仓库);中文讲解与页面内容版权归汇智网(Hubwiz)。官方参考:Siemens TIA Portal。

把这个示例带进你的工程

TiaLink

把 TIA Portal 接到你的智能体:导入、编译、读回核对、PLCSIM 仿真。本地运行,工程数据不出厂。

了解 TiaLink →

PLC 云仿真

不装博途,把 SCL 放到云端逐扫描执行:校验、仿真、场景验收。新用户 100 次免费。

打开云仿真 →

Learn-LAD

同一个团队的梯形图示例库:逐网络讲解、变量表、TiaLink 一键导入。

浏览 LAD 示例 →