SCL 示例库 / SCL 配方导入导出:RecipeExport / RecipeImport
SCL 配方导入导出:RecipeExport / RecipeImport
TIA-only:用标准访问 DB + UDT 存 5 条配方,一个 FB 管理 CSV 导出、导入、Busy/Done/Error 和状态码。
综合
S7-1200
S7-1500
RecipeExport
RecipeImport
UDT
CSV
TIA-only
2 块 / 150 行
TIA 验证
TIA 已验证
一句话: 配方 DB 用标准访问 + Array[1..5] of RecipeUDT 存配方;RecipeExport 把 DB 导成与 DB 同名的 CSV,RecipeImport 从 CSV 读回。FB 用 Mode 0/1/2 保证同一时间只有一个指令在跑,REQ 取上升沿,Done 单周期,Error 保持到 Reset。
导入到我的 TIA 项目
通过 TiaLink 把本示例的 2 个 SCL 块作为“外部源”导入你当前打开的 TIA Portal 项目:自动生成块、编译,并清理外部源。导入后不会自动保存;FB 示例导入后需要你在 OB 里调用 。
导入到我的 TIA 项目
TiaLink 设置
目标 PLC
自动(第一个 PLC)
TIA-only 示例 :RecipeExport / RecipeImport 是 TIA Portal 的文件系统指令,依赖 CPU 的存储卡、Web 服务器和配方目录;本地 SCL 仿真引擎只有硬件桩,不会真正生成 CSV,所以不提供云仿真场景。验证方式是导入 TIA Portal 编译。
配方(Recipe)是设备参数的一组命名数据:同一个程序,换一组配方就能换产品。S7-1200/1500 用 RecipeExport 把配方 DB 导出成 CSV,用 RecipeImport 把修改后的 CSV 读回 DB。这个示例用 5 条啤酒配方演示完整的导出/导入握手。
原工程 rezept_ex-import.scl 用 exportDataUDT / importDataUDT + Program_Alarm + Variant DB;本页把它整理成 FB_Recipe + DB_Recipe,保留 RecipeExport / RecipeImport 指令,并修正原工程“导入 REQ 误用导出 busy”的问题。
DB_Recipe 存 5 条 RecipeUDT;FB_Recipe 在 0/1/2 状态间切换,导出和导入共用同一把忙锁。
概述
DB_Recipe:标准访问 DB,Recipes : Array[1..5] of "RecipeUDT",每条配方包含品名、水、麦芽、小麦、酒花、酵母、糖化温度、休止温度、休止时间。
RecipeUDT:定义在 FB_Recipe.scl 末尾(END_FUNCTION_BLOCK 之后),TIA 外部源会一次生成 FB_Recipe + RecipeUDT。
FB_Recipe:状态 0=空闲、1=导出、2=导入;ExportReq / ImportReq 取上升沿,Done 单周期,Error 保持到 Reset。
导入顺序必须是 FB_Recipe 先、DB_Recipe 后 :FB 文件先生成 RecipeUDT,DB 才能引用这个 UDT。编译在所有文件导入之后进行。
你将学到
RecipeExport / RecipeImport 的参数和 CSV 生成规则;
为什么 RECIPE_DB 必须是标准访问、基于 UDT 的 DB;
为什么 UDT 和 DB 的导入顺序不能反;
如何用一把 Mode 忙锁保证导出/导入不同时执行;
原工程的一个真实 bug:导入 REQ 用了导出的 busy。
本页目录
接口与变量
下表由构建器从 SCL 源码的接口声明自动生成;页面上展示的代码就是导入 TIA 的同一份源码。
FB_Recipe FB
区块 名称 类型 说明 输入 ExportReqBool输入 ImportReqBool输入 ResetBool输出 ReadyBool输出 BusyBool输出 DoneBool输出 ErrorBool输出 StatusWord输出 LastStatusWord输出 ModeInt静态 ExportInst"RecipeExport"静态 ImportInst"RecipeImport"静态 ExportPrevBool静态 ImportPrevBool静态 ExportPulseBool静态 ImportPulseBool静态 ExportDoneIntBool静态 ExportBusyIntBool静态 ExportErrorIntBool静态 ExportStatusIntWord静态 ImportDoneIntBool静态 ImportBusyIntBool静态 ImportErrorIntBool静态 ImportStatusIntWord静态 StatusIntWord静态 LastStatusIntWord静态 ErrorLatchedBool静态 ModeIntInt
DB_Recipe DB
区块 名称 类型 说明 静态 RecipesARRAY[1..5] OF "RecipeUDT"
程序结构
导入顺序以下表为准:被依赖的块/类型先导入;本页已按依赖排好。
FB_Recipe FB 导出/导入状态机,文件末尾定义 RecipeUDT · 141 行 · 4 段 DB_Recipe DB 标准访问配方 DB,Array[1..5] of RecipeUDT · 9 行 · 1 段
FB_Recipe FB
导出/导入状态机,文件末尾定义 RecipeUDT · 141 行 · 源文件 blocks/FB_Recipe.scl
查看 / 复制完整块源码
FB_Recipe.scl 复制
1 FUNCTION_BLOCK "FB_Recipe"
2 { S7_Optimized_Access := 'TRUE' }
3 VERSION : 0.1
4 VAR_INPUT
5 ExportReq : Bool ;
6 ImportReq : Bool ;
7 Reset : Bool ;
8 END_VAR
9
10 VAR_OUTPUT
11 Ready : Bool ;
12 Busy : Bool ;
13 Done : Bool ;
14 Error : Bool ;
15 Status : Word ;
16 LastStatus : Word ;
17 Mode : Int ;
18 END_VAR
19
20 VAR
21 ExportInst : RecipeExport;
22 ImportInst : RecipeImport;
23 ExportPrev : Bool ;
24 ImportPrev : Bool ;
25 ExportPulse : Bool ;
26 ImportPulse : Bool ;
27 ExportDoneInt : Bool ;
28 ExportBusyInt : Bool ;
29 ExportErrorInt : Bool ;
30 ExportStatusInt : Word ;
31 ImportDoneInt : Bool ;
32 ImportBusyInt : Bool ;
33 ImportErrorInt : Bool ;
34 ImportStatusInt : Word ;
35 StatusInt : Word := 16#0000 ;
36 LastStatusInt : Word := 16#0000 ;
37 ErrorLatched : Bool := FALSE ;
38 ModeInt : Int := 0 ;
39 END_VAR
40 BEGIN
41 REGION 边沿与复位
42
43 #ExportPulse := #ExportReq AND NOT #ExportPrev ;
44 #ExportPrev := #ExportReq ;
45 #ImportPulse := #ImportReq AND NOT #ImportPrev ;
46 #ImportPrev := #ImportReq ;
47
48 IF #Reset THEN
49 #ModeInt := 0 ;
50 #StatusInt := 16#0000 ;
51 #LastStatusInt := 16#0000 ;
52 #ErrorLatched := FALSE ;
53 END_IF ;
54
55 #Done := FALSE ;
56 #Busy := FALSE ;
57 END_REGION
58
59 REGION 导出
60
61 IF #ExportPulse AND #ModeInt = 0 AND NOT #ErrorLatched THEN
62 #ModeInt := 1 ;
63 #StatusInt := 16#0000 ;
64 END_IF ;
65
66 #ExportInst ( REQ := #ExportPulse ,
67 RECIPE_DB := "DB_Recipe" ,
68 DONE => #ExportDoneInt ,
69 BUSY => #ExportBusyInt ,
70 ERROR => #ExportErrorInt ,
71 STATUS => #ExportStatusInt ) ;
72
73 IF #ModeInt = 1 THEN
74 #Busy := #ExportBusyInt ;
75 #StatusInt := #ExportStatusInt ;
76 IF #ExportDoneInt THEN
77 #ModeInt := 0 ;
78 #Done := TRUE ;
79 #LastStatusInt := #ExportStatusInt ;
80 ELSIF #ExportErrorInt THEN
81 #ModeInt := 0 ;
82 #ErrorLatched := TRUE ;
83 #LastStatusInt := #ExportStatusInt ;
84 END_IF ;
85 END_IF ;
86 END_REGION
87
88 REGION 导入
89
90 IF #ImportPulse AND #ModeInt = 0 AND NOT #ErrorLatched THEN
91 #ModeInt := 2 ;
92 #StatusInt := 16#0000 ;
93 END_IF ;
94
95 #ImportInst ( REQ := #ImportPulse ,
96 RECIPE_DB := "DB_Recipe" ,
97 DONE => #ImportDoneInt ,
98 BUSY => #ImportBusyInt ,
99 ERROR => #ImportErrorInt ,
100 STATUS => #ImportStatusInt ) ;
101
102 IF #ModeInt = 2 THEN
103 #Busy := #ImportBusyInt ;
104 #StatusInt := #ImportStatusInt ;
105 IF #ImportDoneInt THEN
106 #ModeInt := 0 ;
107 #Done := TRUE ;
108 #LastStatusInt := #ImportStatusInt ;
109 ELSIF #ImportErrorInt THEN
110 #ModeInt := 0 ;
111 #ErrorLatched := TRUE ;
112 #LastStatusInt := #ImportStatusInt ;
113 END_IF ;
114 END_IF ;
115 END_REGION
116
117 REGION 状态输出
118
119 #Error := #ErrorLatched ;
120 #Ready := ( #ModeInt = 0 ) AND NOT #ErrorLatched ;
121 #Status := #StatusInt ;
122 #LastStatus := #LastStatusInt ;
123 #Mode := #ModeInt ;
124 END_REGION
125 END_FUNCTION_BLOCK
126
127 TYPE "RecipeUDT"
128 VERSION : 0.1
129 STRUCT
130 ProductName : String [ 32 ] ;
131 Water : Int ;
132 Barley : Int ;
133 Wheat : Int ;
134 Hops : Int ;
135 Yeast : Int ;
136 WaterTmp : Int ;
137 MashTmp : Int ;
138 MashTime : Int ;
139 END_STRUCT ;
140 END_TYPE
141
FB_Recipe / 边沿与复位 — 一次请求只启动一次
FB_Recipe.scl · 边沿与复位 复制
41 REGION 边沿与复位
42
43 #ExportPulse := #ExportReq AND NOT #ExportPrev ;
44 #ExportPrev := #ExportReq ;
45 #ImportPulse := #ImportReq AND NOT #ImportPrev ;
46 #ImportPrev := #ImportReq ;
47
48 IF #Reset THEN
49 #ModeInt := 0 ;
50 #StatusInt := 16#0000 ;
51 #LastStatusInt := 16#0000 ;
52 #ErrorLatched := FALSE ;
53 END_IF ;
54
55 #Done := FALSE ;
56 #Busy := FALSE ;
57 END_REGION
目的 :把 HMI 的导出/导入电平转换成单周期 REQ,并处理 Reset。
条件 → 动作 :ExportPulse := ExportReq AND NOT ExportPrev;ImportPulse 同理;Reset=1 时把 Mode=0、Status=0、LastStatus=0、ErrorLatched=FALSE;每周期先清 Done / Busy。
为什么 :RecipeExport/Import 的 REQ 是上升沿触发,给一个周期就够;一直给 1 会反复启动。Reset 是清除错误锁存的唯一入口。
注意 :ExportPrev / ImportPrev 必须是静态变量;Done 是单周期脉冲,HMI 要自锁或用上升沿。
FB_Recipe / 导出 — RecipeExport
FB_Recipe.scl · 导出 复制
59 REGION 导出
60
61 IF #ExportPulse AND #ModeInt = 0 AND NOT #ErrorLatched THEN
62 #ModeInt := 1 ;
63 #StatusInt := 16#0000 ;
64 END_IF ;
65
66 #ExportInst ( REQ := #ExportPulse ,
67 RECIPE_DB := "DB_Recipe" ,
68 DONE => #ExportDoneInt ,
69 BUSY => #ExportBusyInt ,
70 ERROR => #ExportErrorInt ,
71 STATUS => #ExportStatusInt ) ;
72
73 IF #ModeInt = 1 THEN
74 #Busy := #ExportBusyInt ;
75 #StatusInt := #ExportStatusInt ;
76 IF #ExportDoneInt THEN
77 #ModeInt := 0 ;
78 #Done := TRUE ;
79 #LastStatusInt := #ExportStatusInt ;
80 ELSIF #ExportErrorInt THEN
81 #ModeInt := 0 ;
82 #ErrorLatched := TRUE ;
83 #LastStatusInt := #ExportStatusInt ;
84 END_IF ;
85 END_IF ;
86 END_REGION
目的 :把 DB_Recipe 的 5 条配方写成 CSV。
条件 → 动作 :空闲、无未复位错误、收到 ExportPulse → Mode=1;调用 RecipeExport,REQ := ExportPulse、RECIPE_DB := "DB_Recipe";DONE → Mode=0、Done=TRUE、LastStatus=STATUS;ERROR → Mode=0、ErrorLatched=TRUE。
为什么 :CSV 文件名与配方 DB 同名(DB_Recipe.csv),列顺序由 RecipeUDT 的成员顺序决定;导出前把配方值写进 DB 即可。
注意 :导出错误常见于文件名非法(16#8090)、DB 结构不可处理(16#8091)、DB 超过 5000 字节(16#8092)、存储卡写保护(16#80B4)。STATUS 只在一个周期有效,所以本页用 LastStatus 锁存。
FB_Recipe / 导入 — RecipeImport
FB_Recipe.scl · 导入 复制
88 REGION 导入
89
90 IF #ImportPulse AND #ModeInt = 0 AND NOT #ErrorLatched THEN
91 #ModeInt := 2 ;
92 #StatusInt := 16#0000 ;
93 END_IF ;
94
95 #ImportInst ( REQ := #ImportPulse ,
96 RECIPE_DB := "DB_Recipe" ,
97 DONE => #ImportDoneInt ,
98 BUSY => #ImportBusyInt ,
99 ERROR => #ImportErrorInt ,
100 STATUS => #ImportStatusInt ) ;
101
102 IF #ModeInt = 2 THEN
103 #Busy := #ImportBusyInt ;
104 #StatusInt := #ImportStatusInt ;
105 IF #ImportDoneInt THEN
106 #ModeInt := 0 ;
107 #Done := TRUE ;
108 #LastStatusInt := #ImportStatusInt ;
109 ELSIF #ImportErrorInt THEN
110 #ModeInt := 0 ;
111 #ErrorLatched := TRUE ;
112 #LastStatusInt := #ImportStatusInt ;
113 END_IF ;
114 END_IF ;
115 END_REGION
目的 :把编辑后的 CSV 读回 DB_Recipe。
条件 → 动作 :空闲、无未复位错误、收到 ImportPulse → Mode=2;调用 RecipeImport,REQ := ImportPulse、RECIPE_DB := "DB_Recipe";DONE → Mode=0、Done=TRUE、LastStatus=STATUS;ERROR → Mode=0、ErrorLatched=TRUE。
为什么 :导入前必须先删除旧的 CSV(或在 Web 服务器里覆盖),否则 CPU 可能读到旧文件;CSV 的列结构必须和 RecipeUDT 完全一致。
注意 :原工程的导入 REQ 写的是 #importDataUDT.req AND NOT #exportDataUDT.busy——用导出的 busy 来判断导入,导出运行时导入会被错误阻塞,导入运行时也可能重复触发。本页改成共用 Mode 忙锁,并让导入只检查自己的状态。
FB_Recipe / 状态输出 — Ready / Busy / Done / Error
FB_Recipe.scl · 状态输出 复制
117 REGION 状态输出
118
119 #Error := #ErrorLatched ;
120 #Ready := ( #ModeInt = 0 ) AND NOT #ErrorLatched ;
121 #Status := #StatusInt ;
122 #LastStatus := #LastStatusInt ;
123 #Mode := #ModeInt ;
124 END_REGION
目的 :给 HMI 一个稳定的状态集合。
条件 → 动作 :Error := ErrorLatched;Ready := (Mode=0) AND NOT ErrorLatched;Status := StatusInt;LastStatus := LastStatusInt;Mode := ModeInt。
为什么 :Done 只闪一个周期,HMI 用 LastStatus 显示“上次结果”;Error 保持到 Reset,避免操作员错过错误。
注意 :Ready=0 不代表一定有错;导出/导入进行中 Busy=1、Ready=0。HMI 应同时看 Busy 和 Error。
DB_Recipe DB
标准访问配方 DB,Array[1..5] of RecipeUDT · 9 行 · 源文件 blocks/DB_Recipe.scl
查看 / 复制完整块源码
DB_Recipe.scl 复制
1 DATA_BLOCK "DB_Recipe"
2 { S7_Optimized_Access := 'FALSE' }
3 VERSION : 0.1
4 VAR
5 Recipes : Array [ 1 . . 5 ] of "RecipeUDT" ;
6 END_VAR
7 BEGIN
8 END_DATA_BLOCK
9
DB_Recipe / 整体逻辑 — 标准访问 + UDT 数组
DB_Recipe.scl · 整体逻辑 复制
8 END_DATA_BLOCK
目的 :给 RecipeExport/Import 提供一个可被 VARIANT 引用的配方 DB。
条件 → 动作 :S7_Optimized_Access := 'FALSE';Recipes : Array[1..5] of "RecipeUDT";每条记录包含 9 个字段。
为什么 :RECIPE_DB 是 VARIANT,TIA 只接受基于 PLC 数据类型(UDT)或系统数据类型的标准访问 DB。匿名 STRUCT 或优化访问 DB 会报 24/51 错误。
注意 :数组上限 1..5 就是最大配方数;CSV 里的记录数不能超过它。修改 RecipeUDT 的成员顺序/类型后,旧 CSV 不再兼容,必须重新导出模板。
仿真验证
本示例依赖 TIA Portal 系统库或硬件环境,暂不在云端仿真中执行。请使用上方 TiaLink 面板导入 TIA,在真实编译/仿真环境中验证。
关键 SCL 语法
写法
含义
RecipeExport / RecipeImport
TIA 配方文件指令,实例放静态 VAR
RECIPE_DB := "DB_Recipe"
指向标准访问、基于 UDT 的配方 DB
Array[1..5] of "RecipeUDT"
5 条配方的数组
TYPE "RecipeUDT" ... END_TYPE
PLC 数据类型,定义 CSV 的列
REQ := #ExportPulse
上升沿启动,单周期
DONE => #DoneInt
指令输出接变量
VARIANT
RecipeExport/Import 的 InOut 参数类型
常见坑
配方 DB 用优化访问 :VARIANT 报“Only data blocks based on a PLC data type…”,必须 S7_Optimized_Access := 'FALSE'。
DB 用匿名 STRUCT :即使标准访问,也不满足“基于 UDT”的要求;必须 Array[1..5] of "RecipeUDT"。
先导入 DB 再导入 UDT :DB 生成时找不到 RecipeUDT,外部源报“数据类型未知”;必须先导入带 UDT 的 FB 文件。
导入 REQ 用了导出的 busy :原工程 bug;导入应检查导入自己的 BUSY,并用共用忙锁防止两个指令同时跑。
CSV 列结构和 UDT 不一致 :导入报 16#8091 或数据错位;改 UDT 后要重新导出模板。
文件名/DB 名不一致 :RecipeExport 生成的 CSV 名就是 DB 名;在 Web 服务器里找不到文件时先检查 DB 名。
没有启用 Web 服务器/权限 :CSV 文件无法下载或覆盖;需要在 CPU 属性里启用 Web 服务器并配置读写权限。
把配方当安全参数 :RecipeExport/Import 不适合需要高保护的数据;高保护场景用 HMI 配方管理。
变体与扩展
多配方 DB :每个产品族一个 DB,用 RecipeDB 选择;或者把 Array[1..N] 扩大。
状态报警 :原工程用 Program_Alarm 把导出/导入状态发成报警;本页用 Status / LastStatus 输出,减少系统库依赖。
HMI 配方管理 :HMI 配方视图可以直接编辑 CSV;PLC 只负责导出/导入和握手。
自动备份 :导出成功后把 CSV 复制到另一张存储卡/网盘;PLC 侧只保证 DONE 和 STATUS。
真实项目要先在 CPU 属性中启用 Web 服务器,并配置配方目录的读写权限,否则 CSV 无法下载/覆盖。
常见问题
为什么配方 DB 必须用标准访问? RecipeExport/RecipeImport 的 RECIPE_DB 是 VARIANT,TIA 只接受基于 PLC 数据类型(UDT)或系统数据类型的标准访问 DB。优化访问的匿名 STRUCT DB 会报“Only data blocks based on a PLC data type or a system data type can be addressed with the data type VARIANT”。
为什么 FB 文件里还带着 RecipeUDT? 本站的构建器每个 .scl 只解析一个块,不能单独放 TYPE 文件;把 RecipeUDT 追加在 FB 后面,TIA 外部源会一次生成 FB + UDT,DB 再引用这个 UDT。导入顺序必须先 FB 再 DB,且编译放在所有文件导入之后。
验证状态
项目 结果 日期 证据 本地仿真场景 未启用 — TIA-only 示例,不提供仿真请求 PLC 云仿真 未跑 — `scl_run_scenario`(托管 MCP) TIA 导入编译 PASS(0 error / 0 warning) 2026-11-01 TIA-only 示例:本地/云仿真未启用;TIA Portal V19 + TiaLink 1.4.0.0 导入 PLC_2,FB_Recipe + RecipeUDT + DB_Recipe 编译 0 错误 / 0 警告,已保存。
本地仿真引擎验证语法、结构和逐扫描行为;TIA 编译验证在 Windows + TIA Portal + TiaLink 上完成。两者不能互相替代。
来源与许可
本页示例改编自 github.com/lopez-dev/Siemens-SCL-Source-Files 的 rezeptExportUndImport/rezept_ex-import.scl,原作者 Daniel Cáceres López ,以 MIT 许可发布。中文注释、接口调整、仿真场景与页面讲解由 Learn-SCL 编写。
原文件用 exportDataUDT/importDataUDT + Program_Alarm + Variant DB;本站改成 DB_Recipe + FB_Recipe,修正导入 REQ 误用导出 busy 的问题,并保留 RecipeExport/RecipeImport 指令。
示例源码以 MIT 许可发布(上游仓库);中文讲解与页面内容版权归汇智网(Hubwiz)。官方参考:Siemens TIA Portal 。