跳到正文
Learn-SCL
SCL 示例库/SCL 软定时器:启动、暂停、恢复、到点

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 里调用。

TiaLink 设置

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

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

这个示例不用 TON 实例,而是自己维护一个定时器状态机。它的价值不在“替代 TON”,而在于展示:当你需要 600 个定时器、需要暂停/恢复、需要把定时器状态存进数组或 DB 时,软定时器比大量 IEC 定时器实例更灵活。

SCL 软定时器状态机:0 空闲 → 1 运行 → 2 暂停 → 1 恢复 → 3 到点
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

区块名称类型说明
输入StartBool
输入PauseBool
输入ResetBool
输入CycleTimeTime
输入SetTimeTime
输出QBool
输出RunningBool
输出ETTime
输出StateInt
静态ElapsedTime
静态StateIntInt

程序结构

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

  1. 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;    // 本次扫描经过的时间,例如 T#10ms
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;    // 0=空闲 1=运行 2=暂停 3=到达
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: // 空闲:等待 Start
36               #Elapsed := T#0MS;
37               IF #Start THEN
38                  #StateInt := 1;
39               END_IF;
40            1: // 运行:累加 CycleTime,到点转 3
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: // 暂停:保持 Elapsed,等待恢复
50               IF NOT #Pause THEN
51                  #StateInt := 1;
52               END_IF;
53            3: // 到达:保持到 Reset
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: // 空闲:等待 Start
36               #Elapsed := T#0MS;
37               IF #Start THEN
38                  #StateInt := 1;
39               END_IF;
40            1: // 运行:累加 CycleTime,到点转 3
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: // 暂停:保持 Elapsed,等待恢复
50               IF NOT #Pause THEN
51                  #StateInt := 1;
52               END_IF;
53            3: // 到达:保持到 Reset
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 次免费调用。

  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 软定时器:启动、暂停、恢复、到点
// 复制后包含完整 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 云仿真PASS2026-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 上完成。两者不能互相替代。

来源与许可

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

原文件是 600 个定时器的库(UDT + DB + 多个 FC);本站提炼为单定时器 FB,把系统时钟差改为 CycleTime 输入,便于逐扫描仿真。

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

把这个示例带进你的工程

TiaLink

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

了解 TiaLink →

PLC 云仿真

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

打开云仿真 →

Learn-LAD

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

浏览 LAD 示例 →