跳到正文
Learn-SCL
SCL 示例库/SCL 一阶温度模型:加热与冷却仿真

SCL 一阶温度模型:加热与冷却仿真

用一阶惯性环节模拟加热/冷却:输入加热功率,按扫描周期逼近目标温度,并做上下限幅。

进阶 S7-1200 S7-1500 REAL 仿真 一阶惯性 限幅 1 块 / 74 行 可仿真 TIA 已验证

一句话:一阶惯性 Temp := Temp + (Target - Temp) * (CycleTime / Tau),加热/冷却用不同 Tau,最后按 UmgebungTemp..MaxTemp 限幅。

导入到我的 TIA 项目

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

TiaLink 设置

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

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

这是一个“用 PLC 算物理量”的入门示例:不接真实温度传感器,而是用一阶惯性环节模拟一个加热炉的温度,让程序在仿真里也能看到温度慢慢上升、慢慢下降。

SCL 一阶温度模型曲线:加热 8s、冷却 15s 时间常数,温度逼近目标后限幅
加热和冷却使用不同的时间常数,温度按一阶惯性逼近目标并限幅。

概述

FB_TempSimulation 把加热功率(0–100%)映射成目标温度,然后用“当前温度向目标温度逼近”的方式模拟热惯性:

Δ = (目标温度 - 当前温度) × (CYCLE_TIME / 时间常数)
当前温度 = 当前温度 + Δ

加热和冷却使用不同的时间常数,最后做环境温度/最高温度限幅,并输出一个 ×10 的整数温度。

你将学到

  • REAL 运算、INT_TO_REAL / REAL_TO_INT 转换;
  • 用 VAR_IN_OUT 保存跨扫描的模拟量状态;
  • 一阶惯性环节的离散化写法;
  • 如何用场景验证“加热变快、冷却变慢”。

接口与变量

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

FB_TempSimulation FB

区块名称类型说明
输入HeizleistungReal
输入ZeitkonstHeizenReal
输入ZeitkonstKühlenReal
输入MaxTempInt
输入UmgebungTempInt
输出TemperaturInt
输入/输出Temp_AktuellReal
静态Temp_SollReal
静态AlphaReal
静态DeltaReal
常量CYCLE_TIMEReal

程序结构

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

  1. FB_TempSimulation FB 温度一阶惯性模型 · 74 行 · 4 段

FB_TempSimulation FB

温度一阶惯性模型 · 74 行 · 源文件 blocks/FB_TempSimulation.scl

查看 / 复制完整块源码
FB_TempSimulation.scl
1FUNCTION_BLOCK "FB_TempSimulation"
2{ S7_Optimized_Access := 'TRUE' }
3VERSION : 0.1
4   VAR_INPUT
5      Heizleistung : Real;             // 加热功率 0.0 ... 100.0 %
6      ZeitkonstHeizen : Real := 8000.0; // 加热时间常数(秒)
7      ZeitkonstKühlen : Real := 15000.0; // 冷却时间常数(秒)
8      MaxTemp : Int := 3000;            // 最高温度 ×10,例如 3000 = 300.0 °C
9      UmgebungTemp : Int := 150;        // 环境温度 ×10,例如 150 = 15.0 °C
10   END_VAR
11 
12   VAR_OUTPUT
13      Temperatur : Int;                // 当前温度 ×10,例如 3000 = 300.0 °C
14   END_VAR
15 
16   VAR_IN_OUT
17      Temp_Aktuell : Real;             // 内部温度状态 ×10;首次调用前由调用方给初值
18   END_VAR
19 
20   VAR
21      Temp_Soll : Real;                // 目标温度 ×10
22      Alpha : Real;                    // 每个扫描周期的逼近系数
23      Delta : Real;                    // 本周期温度变化量
24   END_VAR
25 
26   VAR CONSTANT
27      CYCLE_TIME : Real := 0.1;        // 假定 100 ms 扫描周期;与 OB 实际周期保持一致
28   END_VAR
29 
30BEGIN
31   REGION 计算目标温度
32      // 加热功率 0% 时目标温度 = 环境温度;100% 时目标温度 = MaxTemp。
33      #Temp_Soll := INT_TO_REAL(#UmgebungTemp)
34         + ((INT_TO_REAL(#MaxTemp) - INT_TO_REAL(#UmgebungTemp))
35         * (#Heizleistung / 100.0));
36   END_REGION
37 
38   REGION 加热或冷却
39      IF #Temp_Soll > #Temp_Aktuell THEN
40         // 一阶惯性环节:Δ = (目标 - 当前) × (CYCLE_TIME / 时间常数)
41         IF #ZeitkonstHeizen > 0 THEN
42            #Alpha := #CYCLE_TIME / #ZeitkonstHeizen;
43         ELSE
44            #Alpha := 1.0; // 时间常数为 0 时立即跳到目标值
45         END_IF;
46         #Delta := (#Temp_Soll - #Temp_Aktuell) * #Alpha;
47         #Temp_Aktuell := #Temp_Aktuell + #Delta;
48      ELSIF #Temp_Soll < #Temp_Aktuell THEN
49         IF #ZeitkonstKühlen > 0 THEN
50            #Alpha := #CYCLE_TIME / #ZeitkonstKühlen;
51         ELSE
52            #Alpha := 1.0;
53         END_IF;
54         #Delta := (#Temp_Aktuell - #Temp_Soll) * #Alpha;
55         #Temp_Aktuell := #Temp_Aktuell - #Delta;
56      END_IF;
57   END_REGION
58 
59   REGION 限幅
60      // 温度不能低于环境温度,也不能高于最高温度。
61      IF #Temp_Aktuell < INT_TO_REAL(#UmgebungTemp) THEN
62         #Temp_Aktuell := INT_TO_REAL(#UmgebungTemp);
63      END_IF;
64      IF #Temp_Aktuell > INT_TO_REAL(#MaxTemp) THEN
65         #Temp_Aktuell := INT_TO_REAL(#MaxTemp);
66      END_IF;
67   END_REGION
68 
69   REGION 输出
70      // 对外输出整数温度(×10),方便 HMI 显示。
71      #Temperatur := REAL_TO_INT(#Temp_Aktuell);
72   END_REGION
73END_FUNCTION_BLOCK
74 

FB_TempSimulation / 计算目标温度 — 把功率映射成温度

FB_TempSimulation.scl · 计算目标温度
31   REGION 计算目标温度
32      // 加热功率 0% 时目标温度 = 环境温度;100% 时目标温度 = MaxTemp。
33      #Temp_Soll := INT_TO_REAL(#UmgebungTemp)
34         + ((INT_TO_REAL(#MaxTemp) - INT_TO_REAL(#UmgebungTemp))
35         * (#Heizleistung / 100.0));
36   END_REGION

目的:把 0–100% 的加热功率转换成目标温度。

条件 → 动作:Heizleistung=0 → 目标温度=环境温度;Heizleistung=100 → 目标温度=MaxTemp;中间按比例插值。

为什么:真实加热功率和稳态温度通常不是线性关系,但作为教学模型,线性映射足够展示“功率越大、目标越高”。

注意:MaxTemp 和 UmgebungTemp 都是 ×10 的整数(3000 = 300.0 °C),所以要先 INT_TO_REAL 再参与运算。

FB_TempSimulation / 加热或冷却 — 一阶惯性逼近

FB_TempSimulation.scl · 加热或冷却
38   REGION 加热或冷却
39      IF #Temp_Soll > #Temp_Aktuell THEN
40         // 一阶惯性环节:Δ = (目标 - 当前) × (CYCLE_TIME / 时间常数)
41         IF #ZeitkonstHeizen > 0 THEN
42            #Alpha := #CYCLE_TIME / #ZeitkonstHeizen;
43         ELSE
44            #Alpha := 1.0; // 时间常数为 0 时立即跳到目标值
45         END_IF;
46         #Delta := (#Temp_Soll - #Temp_Aktuell) * #Alpha;
47         #Temp_Aktuell := #Temp_Aktuell + #Delta;
48      ELSIF #Temp_Soll < #Temp_Aktuell THEN
49         IF #ZeitkonstKühlen > 0 THEN
50            #Alpha := #CYCLE_TIME / #ZeitkonstKühlen;
51         ELSE
52            #Alpha := 1.0;
53         END_IF;
54         #Delta := (#Temp_Aktuell - #Temp_Soll) * #Alpha;
55         #Temp_Aktuell := #Temp_Aktuell - #Delta;
56      END_IF;
57   END_REGION

目的:让当前温度每个扫描周期向目标温度靠近一点。

条件 → 动作:目标 > 当前 → 按 ZeitkonstHeizen 加热;目标 < 当前 → 按 ZeitkonstKühlen 冷却;相等 → 不动作。

为什么:真实炉子有热惯性,温度不会瞬间跳到目标;CYCLE_TIME / 时间常数 就是每个周期的逼近比例。

注意:时间常数必须大于 0;代码里对 0 做了保护(直接跳到目标),但实际工程中应避免 0。

FB_TempSimulation / 限幅 — 不超出物理范围

FB_TempSimulation.scl · 限幅
59   REGION 限幅
60      // 温度不能低于环境温度,也不能高于最高温度。
61      IF #Temp_Aktuell < INT_TO_REAL(#UmgebungTemp) THEN
62         #Temp_Aktuell := INT_TO_REAL(#UmgebungTemp);
63      END_IF;
64      IF #Temp_Aktuell > INT_TO_REAL(#MaxTemp) THEN
65         #Temp_Aktuell := INT_TO_REAL(#MaxTemp);
66      END_IF;
67   END_REGION

目的:防止模型算出低于环境温度或高于最高温度的值。

条件 → 动作:当前 < 环境 → 拉到环境;当前 > 最高 → 拉到最高。

为什么:数值模型在极端输入下可能过冲;限幅让输出始终落在合理范围。

注意:限幅放在加热/冷却之后,否则本周期仍可能输出越界值。

FB_TempSimulation / 输出 — 转成 HMI 友好的整数

FB_TempSimulation.scl · 输出
69   REGION 输出
70      // 对外输出整数温度(×10),方便 HMI 显示。
71      #Temperatur := REAL_TO_INT(#Temp_Aktuell);
72   END_REGION

目的:把内部 REAL 温度转成 ×10 的 Int。

条件 → 动作:Temperatur := REAL_TO_INT(Temp_Aktuell)。

为什么:HMI 和上位机通常用整数显示温度,避免浮点格式差异。

注意:REAL_TO_INT 是截断而不是四舍五入;如果需要四舍五入,先加 0.5 或用 ROUND。

仿真验证:让 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  满功率加热 1.5 秒 (1 step(s), 15 scan(s))
PASS  停止加热 100 秒后缓慢回落 (1 step(s), 1000 scan(s))
RESULT: ALL PASS (1015 scan(s))
BASELINE JSON
{
  "sources": [
    {
      "name": "blocks/FB_TempSimulation.scl",
      "source": "FUNCTION_BLOCK \"FB_TempSimulation\"\n{ S7_Optimized_Access := 'TRUE' }\nVERSION : 0.1\n   VAR_INPUT\n      Heizleistung : Real;             // 加热功率 0.0 ... 100.0 %\n      ZeitkonstHeizen : Real := 8000.0; // 加热时间常数(秒)\n      ZeitkonstKühlen : Real := 15000.0; // 冷却时间常数(秒)\n      MaxTemp : Int := 3000;            // 最高温度 ×10,例如 3000 = 300.0 °C\n      UmgebungTemp : Int := 150;        // 环境温度 ×10,例如 150 = 15.0 °C\n   END_VAR\n\n   VAR_OUTPUT\n      Temperatur : Int;                // 当前温度 ×10,例如 3000 = 300.0 °C\n   END_VAR\n\n   VAR_IN_OUT\n      Temp_Aktuell : Real;             // 内部温度状态 ×10;首次调用前由调用方给初值\n   END_VAR\n\n   VAR\n      Temp_Soll : Real;                // 目标温度 ×10\n      Alpha : Real;                    // 每个扫描周期的逼近系数\n      Delta : Real;                    // 本周期温度变化量\n   END_VAR\n\n   VAR CONSTANT\n      CYCLE_TIME : Real := 0.1;        // 假定 100 ms 扫描周期;与 OB 实际周期保持一致\n   END_VAR\n\nBEGIN\n   REGION 计算目标温度\n      // 加热功率 0% 时目标温度 = 环境温度;100% 时目标温度 = MaxTemp。\n      #Temp_Soll := INT_TO_REAL(#UmgebungTemp)\n         + ((INT_TO_REAL(#MaxTemp) - INT_TO_REAL(#UmgebungTemp))\n         * (#Heizleistung / 100.0));\n   END_REGION\n\n   REGION 加热或冷却\n      IF #Temp_Soll > #Temp_Aktuell THEN\n         // 一阶惯性环节:Δ = (目标 - 当前) × (CYCLE_TIME / 时间常数)\n         IF #ZeitkonstHeizen > 0 THEN\n            #Alpha := #CYCLE_TIME / #ZeitkonstHeizen;\n         ELSE\n            #Alpha := 1.0; // 时间常数为 0 时立即跳到目标值\n         END_IF;\n         #Delta := (#Temp_Soll - #Temp_Aktuell) * #Alpha;\n         #Temp_Aktuell := #Temp_Aktuell + #Delta;\n      ELSIF #Temp_Soll < #Temp_Aktuell THEN\n         IF #ZeitkonstKühlen > 0 THEN\n            #Alpha := #CYCLE_TIME / #ZeitkonstKühlen;\n         ELSE\n            #Alpha := 1.0;\n         END_IF;\n         #Delta := (#Temp_Aktuell - #Temp_Soll) * #Alpha;\n         #Temp_Aktuell := #Temp_Aktuell - #Delta;\n      END_IF;\n   END_REGION\n\n   REGION 限幅\n      // 温度不能低于环境温度,也不能高于最高温度。\n      IF #Temp_Aktuell < INT_TO_REAL(#UmgebungTemp) THEN\n         #Temp_Aktuell := INT_TO_REAL(#UmgebungTemp);\n      END_IF;\n      IF #Temp_Aktuell > INT_TO_REAL(#MaxTemp) THEN\n         #Temp_Aktuell := INT_TO_REAL(#MaxTemp);\n      END_IF;\n   END_REGION\n\n   REGION 输出\n      // 对外输出整数温度(×10),方便 HMI 显示。\n      #Temperatur := REAL_TO_INT(#Temp_Aktuell);\n   END_REGION\nEND_FUNCTION_BLOCK\n"
    }
  ],
  "scenario": {
    "docs": [
      "blocks/FB_TempSimulation.scl"
    ],
    "block": "FB_TempSimulation",
    "tickMs": 100,
    "scenarios": [
      {
        "name": "满功率加热 1.5 秒",
        "steps": [
          {
            "set": {
              "Heizleistung": 100,
              "Temp_Aktuell": 150
            },
            "ticks": 15,
            "expect": {
              "Temperatur": 151
            }
          }
        ]
      },
      {
        "name": "停止加热 100 秒后缓慢回落",
        "steps": [
          {
            "set": {
              "Heizleistung": 0,
              "Temp_Aktuell": 300
            },
            "ticks": 1000,
            "expect": {
              "Temperatur": 299
            }
          }
        ]
      }
    ]
  }
}

打开 PLC 云仿真

关键 SCL 语法

写法 含义
REAL 32 位浮点数
INT_TO_REAL / REAL_TO_INT 整数与浮点转换
VAR_IN_OUT 按引用传递,函数内修改会写回调用方
IF / ELSIF / ELSE 分支
T#0MS 时间字面量(本页只用于比较,不参与运算)

常见坑

  • 忘记给 Temp_Aktuell 初值:VAR_IN_OUT 不会自动初始化,首次调用前应由调用方写入环境温度。
  • CYCLE_TIME 和实际 OB 周期不一致:模型会偏快或偏慢;实际工程中应把 OB 的扫描周期或测得的时间差接进来。
  • 时间常数单位弄错:这里按秒理解;如果填成毫秒,温度会几乎不动。
  • 用 Int 做中间运算:会丢失小数,必须用 REAL。

变体与扩展

  • 把 CYCLE_TIME 改成输入 CycleTime : Time,用 TIME_TO_DINT 转成秒,模型就能跟随实际扫描周期。
  • 加一个 Stoerung 输入模拟传感器断线,输出进入安全值。
  • 用 PID 替代一阶模型,做真实温度闭环;本页只做被控对象模型。

常见问题

SCL 怎么模拟温度上升和下降?

用一阶惯性:Temp := Temp + (Target - Temp) * (CycleTime / Tau)。加热和冷却用不同的 Tau(示例 8s/15s),最后把 Temp 限幅在环境温度到 MaxTemp 之间。

时间常数 Tau 怎么取?

Tau 是达到目标 63% 所需的时间,3 个 Tau 约 95%。示例的 8s/15s 是为了让曲线在几十秒内可见,不是真实炉温参数;实际项目要按设备热容和实测曲线辨识。

验证状态

项目结果日期证据
本地仿真场景PASS(1015 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 的 FB_TempSimulation.scl,原作者 Daniel Cáceres López,以 MIT 许可发布。中文注释、接口调整、仿真场景与页面讲解由 Learn-SCL 编写。

保留原始一阶惯性算法;补充中文注释、REGION 分段和可复现的仿真场景。

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

把这个示例带进你的工程

TiaLink

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

了解 TiaLink →

PLC 云仿真

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

打开云仿真 →

Learn-LAD

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

浏览 LAD 示例 →