一句话:一阶惯性 Temp := Temp + (Target - Temp) * (CycleTime / Tau),加热/冷却用不同 Tau,最后按 UmgebungTemp..MaxTemp 限幅。
导入到我的 TIA 项目
通过 TiaLink 把本示例的 1 个 SCL 块作为“外部源”导入你当前打开的 TIA Portal 项目:自动生成块、编译,并清理外部源。导入后不会自动保存;FB 示例导入后需要你在 OB 里调用。
这是一个“用 PLC 算物理量”的入门示例:不接真实温度传感器,而是用一阶惯性环节模拟一个加热炉的温度,让程序在仿真里也能看到温度慢慢上升、慢慢下降。
加热和冷却使用不同的时间常数,温度按一阶惯性逼近目标并限幅。
概述
FB_TempSimulation 把加热功率(0–100%)映射成目标温度,然后用“当前温度向目标温度逼近”的方式模拟热惯性:
Δ = (目标温度 - 当前温度) × (CYCLE_TIME / 时间常数)
当前温度 = 当前温度 + Δ
加热和冷却使用不同的时间常数,最后做环境温度/最高温度限幅,并输出一个 ×10 的整数温度。
你将学到
REAL 运算、INT_TO_REAL / REAL_TO_INT 转换;
- 用
VAR_IN_OUT 保存跨扫描的模拟量状态;
- 一阶惯性环节的离散化写法;
- 如何用场景验证“加热变快、冷却变慢”。
接口与变量
下表由构建器从 SCL 源码的接口声明自动生成;页面上展示的代码就是导入 TIA 的同一份源码。
FB_TempSimulation FB
| 区块 | 名称 | 类型 | 说明 |
|---|
| 输入 | Heizleistung | Real | |
| 输入 | ZeitkonstHeizen | Real | |
| 输入 | ZeitkonstKühlen | Real | |
| 输入 | MaxTemp | Int | |
| 输入 | UmgebungTemp | Int | |
| 输出 | Temperatur | Int | |
| 输入/输出 | Temp_Aktuell | Real | |
| 静态 | Temp_Soll | Real | |
| 静态 | Alpha | Real | |
| 静态 | Delta | Real | |
| 常量 | CYCLE_TIME | Real | |
程序结构
导入顺序以下表为准:被依赖的块/类型先导入;本页已按依赖排好。
- 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;
6 ZeitkonstHeizen : Real := 8000.0;
7 ZeitkonstKühlen : Real := 15000.0;
8 MaxTemp : Int := 3000;
9 UmgebungTemp : Int := 150;
10 END_VAR
11
12 VAR_OUTPUT
13 Temperatur : Int;
14 END_VAR
15
16 VAR_IN_OUT
17 Temp_Aktuell : Real;
18 END_VAR
19
20 VAR
21 Temp_Soll : Real;
22 Alpha : Real;
23 Delta : Real;
24 END_VAR
25
26 VAR CONSTANT
27 CYCLE_TIME : Real := 0.1;
28 END_VAR
29
30BEGIN
31 REGION 计算目标温度
32
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
41 IF #ZeitkonstHeizen > 0 THEN
42 #Alpha := #CYCLE_TIME / #ZeitkonstHeizen;
43 ELSE
44 #Alpha := 1.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
71 #Temperatur := REAL_TO_INT(#Temp_Aktuell);
72 END_REGION
73END_FUNCTION_BLOCK
74
FB_TempSimulation / 计算目标温度 — 把功率映射成温度
FB_TempSimulation.scl · 计算目标温度
31 REGION 计算目标温度
32
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
41 IF #ZeitkonstHeizen > 0 THEN
42 #Alpha := #CYCLE_TIME / #ZeitkonstHeizen;
43 ELSE
44 #Alpha := 1.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
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 次免费调用。
- 复制提示词(包含完整 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 满功率加热 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 云仿真 | 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 上完成。两者不能互相替代。