SCL 软定时器:启动、暂停、恢复、到点
不用 TON 实例,自己维护一个 0/1/2/3 状态机:启动、暂停、恢复、到点、复位,适合做多定时器库。
进阶
S7-1200
S7-1500
定时器
状态机
Time
T_ADD
1 块 / 68 行
可仿真
TIA 已验证
一句话:用 0/1/2/3 四态状态机 + T_ADD 累加 CycleTime:运行态比较 Elapsed >= SetTime,暂停态不累加,到点转 3。
导入到我的 TIA 项目
通过 TiaLink 把本示例的 1 个 SCL 块作为“外部源”导入你当前打开的 TIA Portal 项目:自动生成块、编译,并清理外部源。导入后不会自动保存;FB 示例导入后需要你在 OB 里调用。
这个示例不用 TON 实例,而是自己维护一个定时器状态机。它的价值不在“替代 TON”,而在于展示:当你需要 600 个定时器、需要暂停/恢复、需要把定时器状态存进数组或 DB 时,软定时器比大量 IEC 定时器实例更灵活。
0 空闲、1 运行、2 暂停、3 到点;暂停时保持 Elapsed,恢复后继续累加。
概述
FB_SoftTimer 用 State 表示 0=空闲、1=运行、2=暂停、3=到达。每个扫描周期把 CycleTime 累加到 Elapsed,当 Elapsed >= SetTime 时进入“到达”状态;Pause 在运行中暂停,Reset 随时清零。
原工程 Timer.scl 是一个 600 路定时器库:用 UDT 保存每个定时器的状态、时间戳、设定值和已计时时间,用 TimerHandling 分批扫描;本页把它提炼成一个单定时器 FB,并把“系统时钟差”改成显式的 CycleTime 输入。
你将学到
- 用
CASE 写一个 0/1/2/3 状态机;
- 为什么软定时器需要显式的
CycleTime;
- 暂停/恢复时如何保持
Elapsed;
- 如何用场景验证“运行 3 秒 → 暂停 → 恢复 2 秒到点”。
接口与变量
下表由构建器从 SCL 源码的接口声明自动生成;页面上展示的代码就是导入 TIA 的同一份源码。
FB_SoftTimer FB
| 区块 | 名称 | 类型 | 说明 |
|---|
| 输入 | Start | Bool | |
| 输入 | Pause | Bool | |
| 输入 | Reset | Bool | |
| 输入 | CycleTime | Time | |
| 输入 | SetTime | Time | |
| 输出 | Q | Bool | |
| 输出 | Running | Bool | |
| 输出 | ET | Time | |
| 输出 | State | Int | |
| 静态 | Elapsed | Time | |
| 静态 | StateInt | Int | |
程序结构
导入顺序以下表为准:被依赖的块/类型先导入;本页已按依赖排好。
- FB_SoftTimer FB 软定时器状态机 · 68 行 · 3 段
FB_SoftTimer FB
软定时器状态机 · 68 行 · 源文件 blocks/FB_SoftTimer.scl
查看 / 复制完整块源码
FB_SoftTimer.scl
1FUNCTION_BLOCK "FB_SoftTimer"
2{ S7_Optimized_Access := 'TRUE' }
3VERSION : 0.1
4 VAR_INPUT
5 Start : Bool;
6 Pause : Bool;
7 Reset : Bool;
8 CycleTime : Time;
9 SetTime : Time := T#5S;
10 END_VAR
11
12 VAR_OUTPUT
13 Q : Bool;
14 Running : Bool;
15 ET : Time;
16 State : Int := 0;
17 END_VAR
18
19 VAR
20 Elapsed : Time;
21 StateInt : Int := 0;
22 END_VAR
23
24BEGIN
25 REGION 复位
26 IF #Reset THEN
27 #StateInt := 0;
28 #Elapsed := T#0MS;
29 END_IF;
30 END_REGION
31
32 REGION 状态机
33 IF NOT #Reset THEN
34 CASE #StateInt OF
35 0:
36 #Elapsed := T#0MS;
37 IF #Start THEN
38 #StateInt := 1;
39 END_IF;
40 1:
41 IF #Pause THEN
42 #StateInt := 2;
43 ELSE
44 #Elapsed := T_ADD(IN1 := #Elapsed, IN2 := #CycleTime);
45 IF #Elapsed >= #SetTime THEN
46 #StateInt := 3;
47 END_IF;
48 END_IF;
49 2:
50 IF NOT #Pause THEN
51 #StateInt := 1;
52 END_IF;
53 3:
54 ;
55 ELSE
56 #StateInt := 0;
57 END_CASE;
58 END_IF;
59 END_REGION
60
61 REGION 输出
62 #Q := #StateInt = 3;
63 #Running := #StateInt = 1;
64 #ET := #Elapsed;
65 #State := #StateInt;
66 END_REGION
67END_FUNCTION_BLOCK
68
FB_SoftTimer / 复位 — 回到空闲
FB_SoftTimer.scl · 复位
25 REGION 复位
26 IF #Reset THEN
27 #StateInt := 0;
28 #Elapsed := T#0MS;
29 END_IF;
30 END_REGION
目的:把定时器清零,回到状态 0。
条件 → 动作:Reset=1 → State=0,Elapsed=0。
为什么:复位优先级高于启动/暂停,保证任何状态下都能重新开始。
注意:复位是电平有效;如果 Reset 一直为 1,定时器不会运行。
FB_SoftTimer / 状态机 — 空闲、运行、暂停、到达
FB_SoftTimer.scl · 状态机
32 REGION 状态机
33 IF NOT #Reset THEN
34 CASE #StateInt OF
35 0:
36 #Elapsed := T#0MS;
37 IF #Start THEN
38 #StateInt := 1;
39 END_IF;
40 1:
41 IF #Pause THEN
42 #StateInt := 2;
43 ELSE
44 #Elapsed := T_ADD(IN1 := #Elapsed, IN2 := #CycleTime);
45 IF #Elapsed >= #SetTime THEN
46 #StateInt := 3;
47 END_IF;
48 END_IF;
49 2:
50 IF NOT #Pause THEN
51 #StateInt := 1;
52 END_IF;
53 3:
54 ;
55 ELSE
56 #StateInt := 0;
57 END_CASE;
58 END_IF;
59 END_REGION
目的:根据 Start、Pause、Reset 和 Elapsed 推进状态。
条件 → 动作:
| 当前状态 |
条件 |
下一状态 |
| 0 空闲 |
Start=1 |
1 运行 |
| 1 运行 |
Pause=1 |
2 暂停 |
| 1 运行 |
Elapsed >= SetTime |
3 到达 |
| 2 暂停 |
Pause=0 |
1 运行 |
| 3 到达 |
保持 |
3 到达(等 Reset) |
为什么:把“是否在计时”和“是否到点”拆成两个维度,逻辑比一堆 IF 更清晰。
注意:运行状态下每个周期都要累加 CycleTime;暂停状态下不能累加,否则暂停无效。本页把状态放在静态 StateInt 里,输出 State 每周期镜像 StateInt;这样 TIA 不会对“输出变量可能未初始化”报 13 条警告(真机编译已从 Warning 降到 0/0)。
FB_SoftTimer / 输出 — 把状态翻译成布尔和已计时时间
FB_SoftTimer.scl · 输出
61 REGION 输出
62 #Q := #StateInt = 3;
63 #Running := #StateInt = 1;
64 #ET := #Elapsed;
65 #State := #StateInt;
66 END_REGION
目的:对外提供 Q、Running、ET。
条件 → 动作:Q = (State=3);Running = (State=1);ET = Elapsed。
为什么:调用方通常只关心“到点了吗”“还在跑吗”“跑了多久”,不需要直接读状态码。
注意:Q 是电平信号,不是脉冲;如果只需要一个扫描周期的“到点脉冲”,要在调用方再做边沿检测。
仿真验证:让 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 运行 3 秒后暂停,恢复 2 秒到点 (4 step(s), 9 scan(s))
RESULT: ALL PASS (9 scan(s))
BASELINE JSON
{
"sources": [
{
"name": "blocks/FB_SoftTimer.scl",
"source": "FUNCTION_BLOCK \"FB_SoftTimer\"\n{ S7_Optimized_Access := 'TRUE' }\nVERSION : 0.1\n VAR_INPUT\n Start : Bool; // 启动计时(电平)\n Pause : Bool; // 暂停计时(电平)\n Reset : Bool; // 复位到空闲\n CycleTime : Time; // 本次扫描经过的时间,例如 T#10ms\n SetTime : Time := T#5S; // 设定时间\n END_VAR\n\n VAR_OUTPUT\n Q : Bool; // 计时到达\n Running : Bool; // 正在计时\n ET : Time; // 已计时时间\n State : Int := 0; // 0=空闲 1=运行 2=暂停 3=到达\n END_VAR\n\n VAR\n Elapsed : Time; // 已累计时间\n StateInt : Int := 0; // 内部状态(用静态变量避免输出未初始化的编译告警)\n END_VAR\n\nBEGIN\n REGION 复位\n IF #Reset THEN\n #StateInt := 0;\n #Elapsed := T#0MS;\n END_IF;\n END_REGION\n\n REGION 状态机\n IF NOT #Reset THEN\n CASE #StateInt OF\n 0: // 空闲:等待 Start\n #Elapsed := T#0MS;\n IF #Start THEN\n #StateInt := 1;\n END_IF;\n 1: // 运行:累加 CycleTime,到点转 3\n IF #Pause THEN\n #StateInt := 2;\n ELSE\n #Elapsed := T_ADD(IN1 := #Elapsed, IN2 := #CycleTime);\n IF #Elapsed >= #SetTime THEN\n #StateInt := 3;\n END_IF;\n END_IF;\n 2: // 暂停:保持 Elapsed,等待恢复\n IF NOT #Pause THEN\n #StateInt := 1;\n END_IF;\n 3: // 到达:保持到 Reset\n ;\n ELSE\n #StateInt := 0;\n END_CASE;\n END_IF;\n END_REGION\n\n REGION 输出\n #Q := #StateInt = 3;\n #Running := #StateInt = 1;\n #ET := #Elapsed;\n #State := #StateInt;\n END_REGION\nEND_FUNCTION_BLOCK\n"
}
],
"scenario": {
"docs": [
"blocks/FB_SoftTimer.scl"
],
"block": "FB_SoftTimer",
"tickMs": 100,
"seed": {
"CycleTime": "T#1S",
"SetTime": "T#5S"
},
"always": [
"!(Q && Running)"
],
"scenarios": [
{
"name": "运行 3 秒后暂停,恢复 2 秒到点",
"steps": [
{
"set": {
"Start": true
},
"ticks": 4,
"expect": {
"State": 1,
"Running": true,
"Q": false,
"ET": "T#3S"
}
},
{
"set": {
"Pause": true
},
"expect": {
"State": 2,
"Running": false,
"ET": "T#3S"
}
},
{
"set": {
"Pause": false
},
"ticks": 3,
"expect": {
"State": 3,
"Q": true,
"ET": "T#5S"
}
},
{
"set": {
"Reset": true
},
"expect": {
"State": 0,
"Q": false,
"ET": "T#0S"
}
}
]
}
]
}
}
打开 PLC 云仿真
关键 SCL 语法
| 写法 |
含义 |
CASE ... OF ... ELSE ... END_CASE |
多路分支 |
T_ADD(IN1 := a, IN2 := b) |
两个 Time 相加 |
T#5S / T#0MS |
时间字面量 |
State = 3 |
比较表达式 |
VAR |
扫描间保持的静态变量 |
常见坑
- 用
Elapsed := Elapsed + CycleTime:部分 SCL 编译器/仿真器不支持 Time + Time,用 T_ADD 更稳。
- 暂停时仍然累加:检查
Pause 分支是否真的跳过了累加。
SetTime=0:第一个运行周期就会到点;如果这不是预期,加一个最小设定值检查。
- 复位后
Start 仍为 1:状态机会立刻重新启动;需要“复位后等待新的启动沿”时,在复位分支里锁存一个 StartPrev。
- 多定时器共用一个 FB:每个定时器需要独立的实例 DB;不要用同一个实例反复调用不同定时器。
变体与扩展
- 把单定时器扩展成
Array[0..N] of Struct,用 TimerNr 选择,就是原工程的 600 路定时器库。
- 把
CycleTime 接到 OB 的扫描周期或 RD_LOC_T 的时间差,就可以在真实 PLC 上使用。
- 增加
Preset 输入,支持运行中修改设定值;到点判断会自动使用新值。
常见问题
不用 TON,SCL 怎么自己写定时器?
用 0/1/2/3 四态状态机:空闲、运行、暂停、到点;运行态用 T_ADD 累加 CycleTime,Elapsed >= SetTime 时转到到点;暂停态保持 Elapsed 不变。
暂停/恢复怎么实现?
暂停不是把 Start 清掉,而是进入独立的状态 2:不累加、不清零,等 Pause 释放后回到状态 1 继续累加。这样恢复后不会从头计时。
验证状态
| 项目 | 结果 | 日期 | 证据 |
|---|
| 本地仿真场景 | PASS(9 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 上完成。两者不能互相替代。