SCL 阀门控制:状态机、超时监视与错误码
单/双电控阀门的状态机:开阀、关阀、开/关到位反馈、三个超时定时器和 7 种错误码。
综合
S7-1200
S7-1500
状态机
阀门
TON_TIME
错误处理
1 块 / 144 行
可仿真
TIA 已验证
一句话: 用 0..3 表示关闭/正在开/已开/正在关,三个 TON_TIME 监视过渡态,超时置 1..7 错误码,到位反馈后清定时器。
导入到我的 TIA 项目
通过 TiaLink 把本示例的 1 个 SCL 块作为“外部源”导入你当前打开的 TIA Portal 项目:自动生成块、编译,并清理外部源。导入后不会自动保存;FB 示例导入后需要你在 OB 里调用 。
导入到我的 TIA 项目
TiaLink 设置
目标 PLC
自动(第一个 PLC)
阀门控制是“状态机 + 反馈 + 超时 + 错误处理”的典型组合:开阀命令下去后,阀门不会瞬间到位;如果长时间没有到位反馈,就要报错;开/关反馈同时为 1,也要报错。
0 关闭、1 正在开、2 已开、3 正在关;三个 TON_TIME 分别监视开阀、关阀和未知状态。
概述
FB_ValveControl 用 State 表示 0=关闭、1=正在开、2=已开、3=正在关。它支持:
单电控阀(TypeOfValve=0):失电即关,程序用 NOT OpenCmd 生成关阀命令;
双电控阀(TypeOfValve=1):开/关命令分开;
三个 TON_TIME 分别监视开阀、关阀和“无反馈”超时;
7 种错误码和对应的英文错误文本。
原工程用 UDT udt_valveStruct 把状态、反馈、输出、定时器和错误文本打包传递;本页改成 FB 接口 + 静态定时器,更适合单个阀门实例和逐扫描仿真。
你将学到
用 CASE 写一个带反馈的阀门状态机;
单电控/双电控的差异;
三个超时定时器如何每周期调用、条件不满足时自动复位;
错误码的优先级和错误文本映射。
本页目录
接口与变量
下表由构建器从 SCL 源码的接口声明自动生成;页面上展示的代码就是导入 TIA 的同一份源码。
FB_ValveControl FB
区块 名称 类型 说明 输入 OpenCmdBool输入 CloseCmdBool输入 FbOpenedBool输入 FbClosedBool输入 TypeOfValveInt输入 OpenTimeTime输入 CloseTimeTime输入 UnknownTimeTime输出 YOpenBool输出 YCloseBool输出 StateInt输出 ErrorStateUInt输出 ErrorTextSTRING[60]静态 TimerOpenTON_TIME静态 TimerCloseTON_TIME静态 TimerUnknownTON_TIME静态 StateIntInt静态 ErrorStateIntUInt临时 CloseEffectiveBool常量 STATE_CLOSEDInt常量 STATE_OPENINGInt常量 STATE_OPENEDInt常量 STATE_CLOSINGInt
程序结构
导入顺序以下表为准:被依赖的块/类型先导入;本页已按依赖排好。
FB_ValveControl FB 阀门状态机与超时监视 · 144 行 · 6 段
FB_ValveControl FB
阀门状态机与超时监视 · 144 行 · 源文件 blocks/FB_ValveControl.scl
查看 / 复制完整块源码
FB_ValveControl.scl 复制
1 FUNCTION_BLOCK "FB_ValveControl"
2 { S7_Optimized_Access := 'TRUE' }
3 VERSION : 0.1
4 VAR_INPUT
5 OpenCmd : Bool ;
6 CloseCmd : Bool ;
7 FbOpened : Bool ;
8 FbClosed : Bool ;
9 TypeOfValve : Int ;
10 OpenTime : Time := T#3S ;
11 CloseTime : Time := T#3S ;
12 UnknownTime : Time := T#3S ;
13 END_VAR
14
15 VAR_OUTPUT
16 YOpen : Bool ;
17 YClose : Bool ;
18 State : Int := 0 ;
19 ErrorState : UInt := 0 ;
20 ErrorText : String [ 60 ] := '' ;
21 END_VAR
22
23 VAR
24 TimerOpen : TON_TIME ;
25 TimerClose : TON_TIME ;
26 TimerUnknown : TON_TIME ;
27 StateInt : Int := 0 ;
28 ErrorStateInt : UInt := 0 ;
29 END_VAR
30
31 VAR_TEMP
32 CloseEffective : Bool ;
33 END_VAR
34
35 VAR CONSTANT
36 STATE_CLOSED : Int := 0 ;
37 STATE_OPENING : Int := 1 ;
38 STATE_OPENED : Int := 2 ;
39 STATE_CLOSING : Int := 3 ;
40 END_VAR
41
42 BEGIN
43 REGION 阀门类型处理
44 #CloseEffective := #CloseCmd ;
45 IF #TypeOfValve = 0 THEN
46
47 #CloseEffective := NOT #OpenCmd ;
48 END_IF ;
49 END_REGION
50
51 REGION 状态机
52 CASE #StateInt OF
53 0 :
54 IF #OpenCmd AND NOT #CloseEffective THEN
55 #StateInt := #STATE_OPENING ;
56 END_IF ;
57 1 :
58 IF #FbOpened THEN
59 #StateInt := #STATE_OPENED ;
60 ELSIF #CloseEffective THEN
61 #StateInt := #STATE_CLOSING ;
62 END_IF ;
63 2 :
64 IF #CloseEffective AND NOT #OpenCmd THEN
65 #StateInt := #STATE_CLOSING ;
66 END_IF ;
67 3 :
68 IF #FbClosed THEN
69 #StateInt := #STATE_CLOSED ;
70 ELSIF #OpenCmd THEN
71 #StateInt := #STATE_OPENING ;
72 END_IF ;
73 ELSE
74 #StateInt := #STATE_CLOSED ;
75 END_CASE ;
76 END_REGION
77
78 REGION 超时监视
79
80 #TimerOpen ( IN := ( #StateInt = #STATE_OPENING ) AND NOT #FbOpened ,
81 PT := #OpenTime ) ;
82 #TimerClose ( IN := ( #StateInt = #STATE_CLOSING ) AND NOT #FbClosed ,
83 PT := #CloseTime ) ;
84 #TimerUnknown ( IN := ( ( #StateInt = #STATE_CLOSED ) OR ( #StateInt = #STATE_OPENED ) )
85 AND NOT #FbOpened AND NOT #FbClosed ,
86 PT := #UnknownTime ) ;
87 END_REGION
88
89 REGION 输出
90 #State := #StateInt ;
91 IF #StateInt = #STATE_CLOSED OR #StateInt = #STATE_CLOSING THEN
92 #YOpen := FALSE ;
93 #YClose := TRUE ;
94 ELSE
95 #YOpen := TRUE ;
96 #YClose := FALSE ;
97 END_IF ;
98 END_REGION
99
100 REGION 错误处理
101 IF #TimerOpen . Q AND #OpenTime <> T#0MS THEN
102 #ErrorStateInt := 1 ;
103 ELSIF #TimerClose . Q AND #CloseTime <> T#0MS THEN
104 #ErrorStateInt := 2 ;
105 ELSIF #CloseEffective AND #OpenCmd THEN
106 #ErrorStateInt := 3 ;
107 ELSIF #TimerUnknown . Q AND #UnknownTime <> T#0MS THEN
108 #ErrorStateInt := 4 ;
109 ELSIF #FbClosed AND #FbOpened THEN
110 #ErrorStateInt := 5 ;
111 ELSIF #StateInt = #STATE_CLOSED AND #FbOpened THEN
112 #ErrorStateInt := 6 ;
113 ELSIF #StateInt = #STATE_OPENED AND #FbClosed THEN
114 #ErrorStateInt := 7 ;
115 ELSE
116 #ErrorStateInt := 0 ;
117 END_IF ;
118 END_REGION
119
120 REGION 错误文本
121 CASE #ErrorStateInt OF
122 0 :
123 #ErrorText := 'No error' ;
124 1 :
125 #ErrorText := 'Valve not opening' ;
126 2 :
127 #ErrorText := 'Valve not closing' ;
128 3 :
129 #ErrorText := 'Open and close input at the same time' ;
130 4 :
131 #ErrorText := 'Valve status unknown' ;
132 5 :
133 #ErrorText := 'Open and Close Feedback at the same time' ;
134 6 :
135 #ErrorText := 'Valve is closed, but open feedback is active' ;
136 7 :
137 #ErrorText := 'Valve is opened, but closed feedback is active' ;
138 ELSE
139 #ErrorText := 'Unknown error' ;
140 END_CASE ;
141 #ErrorState := #ErrorStateInt ;
142 END_REGION
143 END_FUNCTION_BLOCK
144
FB_ValveControl / 阀门类型处理 — 单电控自动取反
FB_ValveControl.scl · 阀门类型处理 复制
43 REGION 阀门类型处理
44 #CloseEffective := #CloseCmd ;
45 IF #TypeOfValve = 0 THEN
46
47 #CloseEffective := NOT #OpenCmd ;
48 END_IF ;
49 END_REGION
目的 :把“开/关命令”统一成内部使用的 CloseEffective。
条件 → 动作 :TypeOfValve=0(单电控)→ CloseEffective := NOT OpenCmd;否则 CloseEffective := CloseCmd。
为什么 :单电控阀只有一个线圈,失电就关;如果直接使用 CloseCmd,会与“失电即关”的物理行为不一致。
注意 :CloseEffective 是 VAR_TEMP,每个周期重新计算;不要把它写成静态变量。
FB_ValveControl / 状态机 — 关闭、正在开、已开、正在关
FB_ValveControl.scl · 状态机 复制
51 REGION 状态机
52 CASE #StateInt OF
53 0 :
54 IF #OpenCmd AND NOT #CloseEffective THEN
55 #StateInt := #STATE_OPENING ;
56 END_IF ;
57 1 :
58 IF #FbOpened THEN
59 #StateInt := #STATE_OPENED ;
60 ELSIF #CloseEffective THEN
61 #StateInt := #STATE_CLOSING ;
62 END_IF ;
63 2 :
64 IF #CloseEffective AND NOT #OpenCmd THEN
65 #StateInt := #STATE_CLOSING ;
66 END_IF ;
67 3 :
68 IF #FbClosed THEN
69 #StateInt := #STATE_CLOSED ;
70 ELSIF #OpenCmd THEN
71 #StateInt := #STATE_OPENING ;
72 END_IF ;
73 ELSE
74 #StateInt := #STATE_CLOSED ;
75 END_CASE ;
76 END_REGION
目的 :根据命令和反馈推进阀门状态。
条件 → 动作 :
当前状态
条件
下一状态
0 关闭
OpenCmd=1 且 CloseEffective=0
1 正在开
1 正在开
FbOpened=1
2 已开
1 正在开
CloseEffective=1
3 正在关
2 已开
CloseEffective=1 且 OpenCmd=0
3 正在关
3 正在关
FbClosed=1
0 关闭
3 正在关
OpenCmd=1
1 正在开
为什么 :状态机把“命令”和“反馈”分开处理,避免用一堆 IF 互相覆盖。
注意 :状态 1 和 3 是过渡态;如果反馈一直不来,靠超时定时器报错,而不是在状态机里死等。本页把状态放在静态 StateInt、错误码放在 ErrorStateInt,输出 State/ErrorState 在末尾镜像;这是为了消除 TIA 对“输出变量可能未初始化”的警告(真机编译已从 13 条警告降到 0/0)。
FB_ValveControl / 超时监视 — 三个定时器每周期都调用
FB_ValveControl.scl · 超时监视 复制
78 REGION 超时监视
79
80 #TimerOpen ( IN := ( #StateInt = #STATE_OPENING ) AND NOT #FbOpened ,
81 PT := #OpenTime ) ;
82 #TimerClose ( IN := ( #StateInt = #STATE_CLOSING ) AND NOT #FbClosed ,
83 PT := #CloseTime ) ;
84 #TimerUnknown ( IN := ( ( #StateInt = #STATE_CLOSED ) OR ( #StateInt = #STATE_OPENED ) )
85 AND NOT #FbOpened AND NOT #FbClosed ,
86 PT := #UnknownTime ) ;
87 END_REGION
目的 :检测“该开没开”“该关没关”“没有任何反馈”三种异常。
条件 → 动作 :
TimerOpen:State=1 且 FbOpened=0,超时 OpenTime;
TimerClose:State=3 且 FbClosed=0,超时 CloseTime;
TimerUnknown:State=0/2 且两个反馈都为 0,超时 UnknownTime。
为什么 :定时器每周期都调用,条件不满足时 IN=0 会自动复位;如果把定时器调用放在 CASE 分支里,状态切换后可能残留旧状态。
注意 :OpenTime=T#0MS 表示关闭该监视;错误处理里会检查这个条件。
FB_ValveControl / 输出 — 开/关线圈互锁
FB_ValveControl.scl · 输出 复制
89 REGION 输出
90 #State := #StateInt ;
91 IF #StateInt = #STATE_CLOSED OR #StateInt = #STATE_CLOSING THEN
92 #YOpen := FALSE ;
93 #YClose := TRUE ;
94 ELSE
95 #YOpen := TRUE ;
96 #YClose := FALSE ;
97 END_IF ;
98 END_REGION
目的 :根据状态驱动开阀/关阀输出。
条件 → 动作 :State=0/3 → YClose=1、YOpen=0;State=1/2 → YOpen=1、YClose=0。
为什么 :单电控阀只有一个线圈,双电控阀有两个线圈;无论哪种,开/关输出都不应该同时为 1。
注意 :本示例用软件互锁;安全相关场合应加硬件互锁和反馈监视。
FB_ValveControl / 错误处理 — 7 种错误码的优先级
FB_ValveControl.scl · 错误处理 复制
100 REGION 错误处理
101 IF #TimerOpen . Q AND #OpenTime <> T#0MS THEN
102 #ErrorStateInt := 1 ;
103 ELSIF #TimerClose . Q AND #CloseTime <> T#0MS THEN
104 #ErrorStateInt := 2 ;
105 ELSIF #CloseEffective AND #OpenCmd THEN
106 #ErrorStateInt := 3 ;
107 ELSIF #TimerUnknown . Q AND #UnknownTime <> T#0MS THEN
108 #ErrorStateInt := 4 ;
109 ELSIF #FbClosed AND #FbOpened THEN
110 #ErrorStateInt := 5 ;
111 ELSIF #StateInt = #STATE_CLOSED AND #FbOpened THEN
112 #ErrorStateInt := 6 ;
113 ELSIF #StateInt = #STATE_OPENED AND #FbClosed THEN
114 #ErrorStateInt := 7 ;
115 ELSE
116 #ErrorStateInt := 0 ;
117 END_IF ;
118 END_REGION
目的 :把定时器、命令冲突和反馈异常映射成唯一错误码。
条件 → 动作 :按 IF/ELSIF 顺序判断:开阀超时 → 1;关阀超时 → 2;开关命令同时为 1 → 3;未知状态超时 → 4;开/关反馈同时为 1 → 5;已关但有开反馈 → 6;已开但有关反馈 → 7;否则 0。
为什么 :错误码需要唯一,优先级决定“同时满足多个条件时先报哪个”;这里把“超时”放在“反馈冲突”之前。
注意 :错误码是 UInt,HMI 上要显示 ErrorText,不要只显示数字。
FB_ValveControl / 错误文本 — 给 HMI 的可读说明
FB_ValveControl.scl · 错误文本 复制
120 REGION 错误文本
121 CASE #ErrorStateInt OF
122 0 :
123 #ErrorText := 'No error' ;
124 1 :
125 #ErrorText := 'Valve not opening' ;
126 2 :
127 #ErrorText := 'Valve not closing' ;
128 3 :
129 #ErrorText := 'Open and close input at the same time' ;
130 4 :
131 #ErrorText := 'Valve status unknown' ;
132 5 :
133 #ErrorText := 'Open and Close Feedback at the same time' ;
134 6 :
135 #ErrorText := 'Valve is closed, but open feedback is active' ;
136 7 :
137 #ErrorText := 'Valve is opened, but closed feedback is active' ;
138 ELSE
139 #ErrorText := 'Unknown error' ;
140 END_CASE ;
141 #ErrorState := #ErrorStateInt ;
142 END_REGION
目的 :把错误码翻译成英文短句。
条件 → 动作 :CASE ErrorState OF 0..7 赋不同字符串,ELSE 赋 Unknown error。
为什么 :现场人员看到 “Valve not opening” 比看到 “1” 更快定位问题。
注意 :String[60] 要足够长;如果改成中文,注意 TIA 字符串长度按字符计。
仿真验证:让 Agent 写场景并执行(不需要博途)
推荐:让 Agent 自己设计场景
不需要你手写 scenario。把下面的提示词复制给已连接 PLC 云仿真 MCP 的 AI 助手(Claude Code / Cursor / VS Code 等),它会阅读源码、自己设计正常/边界/异常场景,并调用 scl_run_scenario 执行;新用户有 100 次免费调用。
复制提示词(包含完整 SCL 源码和任务说明)。
粘贴给已配置 sim MCP 的 Agent,让它先 scl_validate,再执行自己写的 scl_run_scenario。
看 Agent 返回的 PASS/FAIL、输入/期望/实际;需要精确复现时,再对照下方本站回归基线。
AGENT PROMPT 复制提示词
你已连接 PLC 云仿真 MCP(sim-cloud)……
任务:阅读下面的 SCL 示例,自己设计 3 个场景并用 scl_run_scenario 执行:
1) 正常路径
2) 边界/临界值
3) 异常、互锁或超时
每个场景请给出输入、期望、实际结果和 PASS/FAIL,并说明场景设计理由。
不要照抄现成场景。
示例:SCL 阀门控制:状态机、超时监视与错误码
// 复制后包含完整 SCL 源码和 MCP 参数说明
页面上只显示提示词开头;复制的内容包含全部块源码和 MCP 参数说明。
查看本站回归基线(固定场景,不是让你手写)
下面是本站构建时跑过的固定场景和预期输出,用来做回归对比或精确复现;不是要求你手写 scenario 。推荐先让 Agent 自己写场景,再和这份基线对照。
EXPECTED 复制结果
PASS 单电控阀开到位再关到位 (4 step(s), 4 scan(s))
PASS 开阀 1 秒无反馈:报超时 (1 step(s), 11 scan(s))
PASS 开/关反馈同时为 1:报错 5 (1 step(s), 1 scan(s))
RESULT: ALL PASS (16 scan(s))
BASELINE JSON 复制基线请求
{
"sources": [
{
"name": "blocks/FB_ValveControl.scl",
"source": "FUNCTION_BLOCK \"FB_ValveControl\"\n{ S7_Optimized_Access := 'TRUE' }\nVERSION : 0.1\n VAR_INPUT\n OpenCmd : Bool; // 开阀命令\n CloseCmd : Bool; // 关阀命令(单电控阀由程序自动取反)\n FbOpened : Bool; // 开到位反馈\n FbClosed : Bool; // 关到位反馈\n TypeOfValve : Int; // 0=单电控 1=双电控\n OpenTime : Time := T#3S; // 开阀超时,T#0MS 关闭监视\n CloseTime : Time := T#3S; // 关阀超时\n UnknownTime : Time := T#3S; // 未知状态超时\n END_VAR\n\n VAR_OUTPUT\n YOpen : Bool; // 开阀输出\n YClose : Bool; // 关阀输出\n State : Int := 0; // 0=关闭 1=正在开 2=已开 3=正在关\n ErrorState : UInt := 0; // 0=无错误,1..7 见错误文本\n ErrorText : String[60] := ''; // 当前错误说明\n END_VAR\n\n VAR\n TimerOpen : TON_TIME; // 开阀超时监视\n TimerClose : TON_TIME; // 关阀超时监视\n TimerUnknown : TON_TIME; // 未知状态监视\n StateInt : Int := 0; // 内部状态(避免输出未初始化的编译告警)\n ErrorStateInt : UInt := 0; // 内部错误码\n END_VAR\n\n VAR_TEMP\n CloseEffective : Bool; // 实际参与逻辑的关阀命令\n END_VAR\n\n VAR CONSTANT\n STATE_CLOSED : Int := 0;\n STATE_OPENING : Int := 1;\n STATE_OPENED : Int := 2;\n STATE_CLOSING : Int := 3;\n END_VAR\n\nBEGIN\n REGION 阀门类型处理\n #CloseEffective := #CloseCmd;\n IF #TypeOfValve = 0 THEN\n // 单电控阀:失电即关,所以“开命令取反”就是关命令。\n #CloseEffective := NOT #OpenCmd;\n END_IF;\n END_REGION\n\n REGION 状态机\n CASE #StateInt OF\n 0:\n IF #OpenCmd AND NOT #CloseEffective THEN\n #StateInt := #STATE_OPENING;\n END_IF;\n 1:\n IF #FbOpened THEN\n #StateInt := #STATE_OPENED;\n ELSIF #CloseEffective THEN\n #StateInt := #STATE_CLOSING;\n END_IF;\n 2:\n IF #CloseEffective AND NOT #OpenCmd THEN\n #StateInt := #STATE_CLOSING;\n END_IF;\n 3:\n IF #FbClosed THEN\n #StateInt := #STATE_CLOSED;\n ELSIF #OpenCmd THEN\n #StateInt := #STATE_OPENING;\n END_IF;\n ELSE\n #StateInt := #STATE_CLOSED;\n END_CASE;\n END_REGION\n\n REGION 超时监视\n // 三个定时器每周期都调用,条件不满足时自动复位。\n #TimerOpen(IN := (#StateInt = #STATE_OPENING) AND NOT #FbOpened,\n PT := #OpenTime);\n #TimerClose(IN := (#StateInt = #STATE_CLOSING) AND NOT #FbClosed,\n PT := #CloseTime);\n #TimerUnknown(IN := ((#StateInt = #STATE_CLOSED) OR (#StateInt = #STATE_OPENED))\n AND NOT #FbOpened AND NOT #FbClosed,\n PT := #UnknownTime);\n END_REGION\n\n REGION 输出\n #State := #StateInt;\n IF #StateInt = #STATE_CLOSED OR #StateInt = #STATE_CLOSING THEN\n #YOpen := FALSE;\n #YClose := TRUE;\n ELSE\n #YOpen := TRUE;\n #YClose := FALSE;\n END_IF;\n END_REGION\n\n REGION 错误处理\n IF #TimerOpen.Q AND #OpenTime <> T#0MS THEN\n #ErrorStateInt := 1; // 开阀超时\n ELSIF #TimerClose.Q AND #CloseTime <> T#0MS THEN\n #ErrorStateInt := 2; // 关阀超时\n ELSIF #CloseEffective AND #OpenCmd THEN\n #ErrorStateInt := 3; // 开关命令同时为 1\n ELSIF #TimerUnknown.Q AND #UnknownTime <> T#0MS THEN\n #ErrorStateInt := 4; // 无反馈,状态未知\n ELSIF #FbClosed AND #FbOpened THEN\n #ErrorStateInt := 5; // 开/关反馈同时为 1\n ELSIF #StateInt = #STATE_CLOSED AND #FbOpened THEN\n #ErrorStateInt := 6; // 已关但开反馈为 1\n ELSIF #StateInt = #STATE_OPENED AND #FbClosed THEN\n #ErrorStateInt := 7; // 已开但关反馈为 1\n ELSE\n #ErrorStateInt := 0;\n END_IF;\n END_REGION\n\n REGION 错误文本\n CASE #ErrorStateInt OF\n 0:\n #ErrorText := 'No error';\n 1:\n #ErrorText := 'Valve not opening';\n 2:\n #ErrorText := 'Valve not closing';\n 3:\n #ErrorText := 'Open and close input at the same time';\n 4:\n #ErrorText := 'Valve status unknown';\n 5:\n #ErrorText := 'Open and Close Feedback at the same time';\n 6:\n #ErrorText := 'Valve is closed, but open feedback is active';\n 7:\n #ErrorText := 'Valve is opened, but closed feedback is active';\n ELSE\n #ErrorText := 'Unknown error';\n END_CASE;\n #ErrorState := #ErrorStateInt;\n END_REGION\nEND_FUNCTION_BLOCK\n"
}
],
"scenario": {
"docs": [
"blocks/FB_ValveControl.scl"
],
"block": "FB_ValveControl",
"tickMs": 100,
"always": [
"!(YOpen && YClose)"
],
"scenarios": [
{
"name": "单电控阀开到位再关到位",
"steps": [
{
"set": {
"OpenCmd": true
},
"expect": {
"State": 1,
"YOpen": true,
"YClose": false,
"ErrorState": 0
}
},
{
"set": {
"FbOpened": true
},
"expect": {
"State": 2
}
},
{
"set": {
"OpenCmd": false,
"FbOpened": false
},
"expect": {
"State": 3,
"YOpen": false,
"YClose": true
}
},
{
"set": {
"FbClosed": true
},
"expect": {
"State": 0,
"YClose": true
}
}
]
},
{
"name": "开阀 1 秒无反馈:报超时",
"steps": [
{
"set": {
"OpenCmd": true,
"OpenTime": "T#1S"
},
"ticks": 11,
"expect": {
"State": 1,
"ErrorState": 1,
"ErrorText": "'Valve not opening'"
}
}
]
},
{
"name": "开/关反馈同时为 1:报错 5",
"steps": [
{
"set": {
"FbOpened": true,
"FbClosed": true
},
"expect": {
"ErrorState": 5
}
}
]
}
]
}
}
打开 PLC 云仿真
关键 SCL 语法
写法
含义
CASE ... OF ... ELSE ... END_CASE
状态机/错误码分支
TON_TIME
IEC 定时器实例的数据类型
#TimerOpen(IN := ..., PT := ...)
调用定时器实例
TimerOpen.Q
定时器到点输出
String[60]
最大 60 字符的字符串
常见坑
把定时器调用放在 CASE 分支里 :状态切换后定时器可能不复位;每周期都调用。
单电控阀同时给开/关命令 :CloseEffective = NOT OpenCmd 后不会同时为 1;如果业务需要“关优先”,要另外处理。
忘记处理“无反馈” :阀门卡住时,状态机会停在过渡态;TimerUnknown 负责这种异常。
错误码优先级不清 :多个错误同时满足时,先报哪个要写进需求;本页的顺序只是一个合理默认。
OpenTime=T#0MS 仍报超时 :错误处理里必须检查 <> T#0MS。
变体与扩展
增加 Maintenance 模式,手动强制开/关,并屏蔽超时。
增加 OpenLimit/CloseLimit 的硬件反馈去抖,避免反馈抖动导致状态来回跳。
把三个定时器改成 TON 数组,支持多阀门共用同一个 FB 类型。
把错误码映射成中文报警文本,接入 HMI 报警系统。
常见问题
SCL 阀门控制状态机怎么写? 用 0..3 表示关闭、正在开、已开、正在关;OpenCmd/CloseCmd 触发状态迁移,FbOpened/FbClosed 确认到位;每个过渡态挂一个 TON_TIME,超时置错误码。
阀门超时报警怎么实现? 进入过渡态时启动对应 TON(OpenTime/CloseTime/UnknownTime),Q 为 TRUE 就写 ErrorState(1..7)和 ErrorText;收到到位反馈或 Reset 时清定时器和错误。OpenTime=T#0MS 表示关闭该监视。
验证状态
项目 结果 日期 证据 本地仿真场景 PASS(16 scans) 2026-11-01 页面下方 `scl_run_scenario` 请求可复现 PLC 云仿真 PASS 2026-11-01 `scl_run_scenario`(托管 MCP) TIA 导入编译 PASS(0 error / 0 warning) 2026-11-01 本地 SCL 仿真与 PLC 云仿真(scl_run_scenario)场景通过;TIA Portal V19 + TiaLink 1.4.0.0 批量导入 PLC_2,编译 0 错误 / 0 警告,已保存。
本地仿真引擎验证语法、结构和逐扫描行为;TIA 编译验证在 Windows + TIA Portal + TiaLink 上完成。两者不能互相替代。