SCL 示例库/SCL DB 备份与恢复:WRIT_DBL / READ_DBL 序列
SCL DB 备份与恢复:WRIT_DBL / READ_DBL 序列
TIA-only:用主计数器 + 每块头管理多个 DB 的备份/恢复,WRIT_DBL 写入负载内存,READ_DBL 读回,含 RET_VAL 和忙标志。
综合
S7-1200
S7-1500
WRIT_DBL
READ_DBL
备份
恢复
Variant
TIA-only
2 块 / 156 行
TIA 验证
TIA 已验证
一句话:主头保存 Save/Restore 和写/读计数器,每个块一个 BlockHeader;当前块号等于计数器时启动 WRIT_DBL(备份)或 READ_DBL(恢复),RET_VAL 为 0 或 28672 时计数器加一,直到 LastBlock+1 后复位。每个块要用独立的 FB 实例。
导入到我的 TIA 项目
通过 TiaLink 把本示例的 2 个 SCL 块作为“外部源”导入你当前打开的 TIA Portal 项目:自动生成块、编译,并清理外部源。导入后不会自动保存;FB 示例导入后需要你在 OB 里调用。
TIA-only 示例:WRIT_DBL / READ_DBL 操作的是 CPU 负载内存,依赖真实 CPU 的存储卡/负载内存;本地 SCL 仿真引擎没有负载内存模型,所以不提供云仿真场景。验证方式是导入 TIA Portal 编译。
现场调试时,经常需要把一组数据 DB“存一份”,改坏了再“读回来”。S7-1200/1500 用 WRIT_DBL 把工作内存中的 DB 写到负载内存,用 READ_DBL 从负载内存读回工作内存。这个示例把多个 DB 的备份/恢复做成一个按块调用的序列:主头保存计数器,每个块保存自己的忙碌/完成/返回值。
原工程 backup.scl 用两个 UDT(backupMainHeaderUDT / backupBLKHeaderUDT)+ Program_Alarm + Variant DB;本页用 inline STRUCT 保留同样的字段,去掉 Program_Alarm,把 WRIT_DBL / READ_DBL 和计数器逻辑完整保留。
MainHeader 保存计数器,Blocks[1..4] 保存每块状态;只有 Nr = 计数器的那一块启动 WRIT_DBL 或 READ_DBL。
概述
DB_BackupControl:MainHeader 保存 Save / Restore / WrCounter / RdCounter / LastBlock;Blocks[1..4] 保存 4 个块的 BlockHeader。
FB_BlockBackup:每次调用处理一个块,Nr 是块号,DBBlock 是要备份/恢复的 DB(Variant)。
- 备份流程:
MainHeader.Save=1 → 当前块 Nr = WrCounter → BlockHeader.Save=1 → WRIT_DBL → 成功后 WrCounter+1。
- 恢复流程:
MainHeader.Restore=1 → 当前块 Nr = RdCounter → BlockHeader.Restore=1 → READ_DBL → 成功后 RdCounter+1。
- 所有块完成后,备份计数器回到 0,恢复计数器回到 1,
Save / Restore 自动撤销。
每个块必须使用独立的 FB 实例;一个实例处理多个块会共用 BlockHeader 状态,无法区分当前正在写/读哪一块。
你将学到
WRIT_DBL / READ_DBL 的 REQ / BUSY / RET_VAL 握手;
- 主计数器 + 每块头如何把多个 DB 串成一个序列;
- 为什么
RET_VAL=0 和 28672 都算成功;
- 备份/恢复的优先级和块头复位;
- 为什么这种示例只能做 TIA 编译验证,不能在本地 SCL 仿真里跑。
接口与变量
下表由构建器从 SCL 源码的接口声明自动生成;页面上展示的代码就是导入 TIA 的同一份源码。
FB_BlockBackup FB
| 区块 | 名称 | 类型 | 说明 |
|---|
| 输入 | Nr | Int | |
| 输入/输出 | MainHeader | STRUCT | |
| 输入/输出 | BlockHeader | STRUCT | |
| 输入/输出 | DBBlock | "Variant" | |
DB_BackupControl DB
| 区块 | 名称 | 类型 | 说明 |
|---|
| 静态 | MainHeader | STRUCT | |
| 静态 | Blocks | ARRAY[1..4] OF STRUCT | |
程序结构
导入顺序以下表为准:被依赖的块/类型先导入;本页已按依赖排好。
- FB_BlockBackup FB 单块备份/恢复,主计数器 + 块头状态 · 124 行 · 2 段
- DB_BackupControl DB 主控制头和 4 个块头 · 32 行 · 1 段
FB_BlockBackup FB
单块备份/恢复,主计数器 + 块头状态 · 124 行 · 源文件 blocks/FB_BlockBackup.scl
查看 / 复制完整块源码
FB_BlockBackup.scl
1FUNCTION_BLOCK "FB_BlockBackup"
2{ S7_Optimized_Access := 'TRUE' }
3VERSION : 0.1
4 VAR_INPUT
5 Nr : Int;
6 END_VAR
7
8 VAR_IN_OUT
9 MainHeader : Struct
10 Save : Bool;
11 Restore : Bool;
12 WrCounter : Int;
13 RdCounter : Int;
14 LastBlock : Int;
15 END_STRUCT;
16 BlockHeader : Struct
17 Save : Bool;
18 Restore : Bool;
19 Write : Bool;
20 WrBusy : Bool;
21 WrProcess : Bool;
22 WrDone : Bool;
23 WrRet : Int;
24 WrRetMem : Int;
25 Read : Bool;
26 RdBusy : Bool;
27 RdProcess : Bool;
28 RdDone : Bool;
29 RdRet : Int;
30 RdRetMem : Int;
31 END_STRUCT;
32 DBBlock : Variant;
33 END_VAR
34
35BEGIN
36 REGION 备份
37
38 IF #MainHeader.Save AND NOT #MainHeader.Restore AND #MainHeader.WrCounter = #Nr AND NOT #BlockHeader.Save THEN
39 #BlockHeader.Save := TRUE;
40 END_IF;
41
42 IF #MainHeader.Restore THEN
43 #BlockHeader.Save := FALSE;
44 #MainHeader.Save := FALSE;
45 END_IF;
46 IF #MainHeader.WrCounter = 0 THEN
47 #MainHeader.WrCounter := 1;
48 END_IF;
49
50
51 IF #BlockHeader.Save AND NOT #BlockHeader.WrProcess THEN
52 #BlockHeader.WrProcess := TRUE;
53 #BlockHeader.Write := TRUE;
54 #BlockHeader.WrDone := FALSE;
55 END_IF;
56
57 IF #BlockHeader.Save THEN
58 #BlockHeader.WrRet := WRIT_DBL(
59 REQ := #BlockHeader.Write,
60 SRCBLK := #DBBlock,
61 BUSY => #BlockHeader.WrBusy,
62 DSTBLK := #DBBlock);
63 END_IF;
64
65 IF #BlockHeader.WrBusy THEN
66 #BlockHeader.Write := FALSE;
67 #BlockHeader.WrRetMem := #BlockHeader.WrRet;
68 END_IF;
69
70
71 IF #BlockHeader.WrProcess AND ((#BlockHeader.WrRet = 0) OR (#BlockHeader.WrRet = 28672)) THEN
72 #MainHeader.WrCounter := #MainHeader.WrCounter + 1;
73 #BlockHeader.WrProcess := FALSE;
74 #BlockHeader.Save := FALSE;
75 #BlockHeader.WrDone := TRUE;
76 END_IF;
77
78 IF #MainHeader.WrCounter = #MainHeader.LastBlock + 1 THEN
79 #MainHeader.WrCounter := 0;
80 #MainHeader.Save := FALSE;
81 END_IF;
82 END_REGION
83
84 REGION 恢复
85 IF #MainHeader.RdCounter = 0 THEN
86 #MainHeader.RdCounter := 1;
87 END_IF;
88 IF #MainHeader.Restore AND #MainHeader.RdCounter = #Nr AND NOT #BlockHeader.Restore AND NOT #BlockHeader.WrProcess THEN
89 #BlockHeader.Restore := TRUE;
90 END_IF;
91
92 IF #BlockHeader.Restore AND NOT #BlockHeader.RdProcess THEN
93 #BlockHeader.RdProcess := TRUE;
94 #BlockHeader.Read := TRUE;
95 #BlockHeader.RdDone := FALSE;
96 END_IF;
97
98 IF #BlockHeader.Restore THEN
99 #BlockHeader.RdRet := READ_DBL(
100 REQ := #BlockHeader.Read,
101 SRCBLK := #DBBlock,
102 BUSY => #BlockHeader.RdBusy,
103 DSTBLK := #DBBlock);
104 END_IF;
105
106 IF #BlockHeader.RdBusy THEN
107 #BlockHeader.Read := FALSE;
108 #BlockHeader.RdRetMem := #BlockHeader.RdRet;
109 END_IF;
110
111 IF #BlockHeader.RdProcess AND ((#BlockHeader.RdRet = 0) OR (#BlockHeader.RdRet = 28672)) THEN
112 #MainHeader.RdCounter := #MainHeader.RdCounter + 1;
113 #BlockHeader.RdProcess := FALSE;
114 #BlockHeader.Restore := FALSE;
115 #BlockHeader.RdDone := TRUE;
116 END_IF;
117
118 IF #MainHeader.RdCounter = #MainHeader.LastBlock + 1 THEN
119 #MainHeader.RdCounter := 1;
120 #MainHeader.Restore := FALSE;
121 END_IF;
122 END_REGION
123END_FUNCTION_BLOCK
124
FB_BlockBackup / 备份 — 轮到哪一块,就写哪一块
FB_BlockBackup.scl · 备份
36 REGION 备份
37
38 IF #MainHeader.Save AND NOT #MainHeader.Restore AND #MainHeader.WrCounter = #Nr AND NOT #BlockHeader.Save THEN
39 #BlockHeader.Save := TRUE;
40 END_IF;
41
42 IF #MainHeader.Restore THEN
43 #BlockHeader.Save := FALSE;
44 #MainHeader.Save := FALSE;
45 END_IF;
46 IF #MainHeader.WrCounter = 0 THEN
47 #MainHeader.WrCounter := 1;
48 END_IF;
49
50
51 IF #BlockHeader.Save AND NOT #BlockHeader.WrProcess THEN
52 #BlockHeader.WrProcess := TRUE;
53 #BlockHeader.Write := TRUE;
54 #BlockHeader.WrDone := FALSE;
55 END_IF;
56
57 IF #BlockHeader.Save THEN
58 #BlockHeader.WrRet := WRIT_DBL(
59 REQ := #BlockHeader.Write,
60 SRCBLK := #DBBlock,
61 BUSY => #BlockHeader.WrBusy,
62 DSTBLK := #DBBlock);
63 END_IF;
64
65 IF #BlockHeader.WrBusy THEN
66 #BlockHeader.Write := FALSE;
67 #BlockHeader.WrRetMem := #BlockHeader.WrRet;
68 END_IF;
69
70
71 IF #BlockHeader.WrProcess AND ((#BlockHeader.WrRet = 0) OR (#BlockHeader.WrRet = 28672)) THEN
72 #MainHeader.WrCounter := #MainHeader.WrCounter + 1;
73 #BlockHeader.WrProcess := FALSE;
74 #BlockHeader.Save := FALSE;
75 #BlockHeader.WrDone := TRUE;
76 END_IF;
77
78 IF #MainHeader.WrCounter = #MainHeader.LastBlock + 1 THEN
79 #MainHeader.WrCounter := 0;
80 #MainHeader.Save := FALSE;
81 END_IF;
82 END_REGION
目的:按 WrCounter 的顺序,把每个块的 DB 写到负载内存。
条件 → 动作:
MainHeader.Save=1 且 WrCounter = Nr 且当前块还没开始 → BlockHeader.Save=1;
- 如果当前块在恢复,
BlockHeader.Save=0(恢复优先);
WrCounter=0 时初始化为 1;
BlockHeader.Save 且 WrProcess=0 → WrProcess=1、Write=1、WrDone=0;
- 调用
WRIT_DBL(REQ := Write, SRCBLK := DBBlock, BUSY => WrBusy, DSTBLK := DBBlock);
WrBusy=1 时撤销 Write,保存 WrRetMem;
WrProcess=1 且 WrRet 为 0 或 28672 → WrCounter+1、WrProcess=0、Save=0、WrDone=1;
WrCounter = LastBlock+1 → WrCounter=0、MainHeader.Save=0。
为什么:WRIT_DBL 是异步的,REQ 只需要一个周期;WrProcess 防止重复启动,WrBusy 期间不能改 REQ。
注意:RET_VAL=28672(16#7000)表示“后台任务已启动/完成”,原工程也把它当成功;28673/28674 是异常完成,不参与成功判断。WrDone 保持到下一块开始,方便 HMI 读取。
FB_BlockBackup / 恢复 — 轮到哪一块,就读哪一块
FB_BlockBackup.scl · 恢复
84 REGION 恢复
85 IF #MainHeader.RdCounter = 0 THEN
86 #MainHeader.RdCounter := 1;
87 END_IF;
88 IF #MainHeader.Restore AND #MainHeader.RdCounter = #Nr AND NOT #BlockHeader.Restore AND NOT #BlockHeader.WrProcess THEN
89 #BlockHeader.Restore := TRUE;
90 END_IF;
91
92 IF #BlockHeader.Restore AND NOT #BlockHeader.RdProcess THEN
93 #BlockHeader.RdProcess := TRUE;
94 #BlockHeader.Read := TRUE;
95 #BlockHeader.RdDone := FALSE;
96 END_IF;
97
98 IF #BlockHeader.Restore THEN
99 #BlockHeader.RdRet := READ_DBL(
100 REQ := #BlockHeader.Read,
101 SRCBLK := #DBBlock,
102 BUSY => #BlockHeader.RdBusy,
103 DSTBLK := #DBBlock);
104 END_IF;
105
106 IF #BlockHeader.RdBusy THEN
107 #BlockHeader.Read := FALSE;
108 #BlockHeader.RdRetMem := #BlockHeader.RdRet;
109 END_IF;
110
111 IF #BlockHeader.RdProcess AND ((#BlockHeader.RdRet = 0) OR (#BlockHeader.RdRet = 28672)) THEN
112 #MainHeader.RdCounter := #MainHeader.RdCounter + 1;
113 #BlockHeader.RdProcess := FALSE;
114 #BlockHeader.Restore := FALSE;
115 #BlockHeader.RdDone := TRUE;
116 END_IF;
117
118 IF #MainHeader.RdCounter = #MainHeader.LastBlock + 1 THEN
119 #MainHeader.RdCounter := 1;
120 #MainHeader.Restore := FALSE;
121 END_IF;
122 END_REGION
目的:按 RdCounter 的顺序,把每个块的 DB 从负载内存读回工作内存。
条件 → 动作:
RdCounter=0 时初始化为 1;
MainHeader.Restore=1 且 RdCounter = Nr 且当前块还没开始 → BlockHeader.Restore=1;
Restore 且 RdProcess=0 → RdProcess=1、Read=1、RdDone=0;
- 调用
READ_DBL(REQ := Read, SRCBLK := DBBlock, BUSY => RdBusy, DSTBLK := DBBlock);
RdBusy=1 时撤销 Read,保存 RdRetMem;
RdProcess=1 且 RdRet 为 0 或 28672 → RdCounter+1、RdProcess=0、Restore=0、RdDone=1;
RdCounter = LastBlock+1 → RdCounter=1、MainHeader.Restore=0。
为什么:恢复和备份共用同一组块头字段,但用不同的计数器;Restore 置位时备份会主动让路,避免同一块同时读写。
注意:恢复完成后 RdCounter 回到 1,不是 0;这是原工程的约定。下一次恢复会从第 1 块重新开始。RdDone 同样保持到下一块开始。
DB_BackupControl DB
主控制头和 4 个块头 · 32 行 · 源文件 blocks/DB_BackupControl.scl
查看 / 复制完整块源码
DB_BackupControl.scl
1DATA_BLOCK "DB_BackupControl"
2{ S7_Optimized_Access := 'TRUE' }
3VERSION : 0.1
4 VAR
5 MainHeader : Struct
6 Save : Bool;
7 Restore : Bool;
8 WrCounter : Int;
9 RdCounter : Int;
10 LastBlock : Int;
11 END_STRUCT;
12 Blocks : Array[1..4] of Struct
13 Save : Bool;
14 Restore : Bool;
15 Write : Bool;
16 WrBusy : Bool;
17 WrProcess : Bool;
18 WrDone : Bool;
19 WrRet : Int;
20 WrRetMem : Int;
21 Read : Bool;
22 RdBusy : Bool;
23 RdProcess : Bool;
24 RdDone : Bool;
25 RdRet : Int;
26 RdRetMem : Int;
27 END_STRUCT;
28 END_VAR
29BEGIN
30 MainHeader.LastBlock := 4;
31END_DATA_BLOCK
32
DB_BackupControl / 整体逻辑 — 主头和 4 个块头
DB_BackupControl.scl · 整体逻辑
30 MainHeader.LastBlock := 4;
目的:给所有 FB 实例提供共享的主计数器和每块状态。
条件 → 动作:MainHeader 有 5 个字段;Blocks[1..4] 每个都有 14 个字段;BEGIN 里把 MainHeader.LastBlock 设为 4。
为什么:主计数器必须只有一个(所有块共享),块头必须每块一份(每块独立忙/完成);把两者放在一个 DB 里,调用方只需要把 MainHeader 和 Blocks[Nr] 传给对应实例。
注意:LastBlock 和 Blocks 的数组上界必须一致;增加块数时两个地方都要改。块头里的 WrRet / RdRet 是最近一次的返回值,WrRetMem / RdRetMem 是忙期间锁存的值。
仿真验证
本示例依赖 TIA Portal 系统库或硬件环境,暂不在云端仿真中执行。请使用上方 TiaLink 面板导入 TIA,在真实编译/仿真环境中验证。
关键 SCL 语法
| 写法 |
含义 |
WRIT_DBL |
把工作内存 DB 写到负载内存,返回 RET_VAL |
READ_DBL |
从负载内存读回工作内存 DB |
SRCBLK := #DBBlock / DSTBLK := #DBBlock |
Variant 指向要操作的 DB |
REQ := #BlockHeader.Write |
单周期启动 |
BUSY => #BlockHeader.WrBusy |
异步忙标志 |
#BlockHeader.WrRet = 28672 |
后台任务完成,按成功处理 |
Variant |
允许调用方传入不同的 DB |
inline STRUCT |
本页用它替代 UDT,保持字段和导入方式简单 |
常见坑
- 一个 FB 实例处理多个块:块头状态共用,第二块会覆盖第一块的忙/完成标志;每块一个实例。
REQ 一直为 1:WRIT_DBL/READ_DBL 反复启动;必须用 WrProcess/RdProcess 做单周期。
- 把 28672 当错误:16#7000 是后台任务完成的正常返回;原工程把 0 和 28672 都当成功。
- 备份/恢复同时启动:同一块可能一边写一边读;本页让
Restore 优先,备份块头会被清掉。
- 忘记改
LastBlock:计数器永远到不了 LastBlock+1,Save/Restore 不会自动撤销。
- 把 DB 名写死:
DBBlock 是 Variant,调用方可以传不同 DB;写死会失去复用性。
- 在本地 SCL 仿真里跑:本地 SCL 仿真引擎没有负载内存模型,WRIT_DBL/READ_DBL 只是桩;本页只做 TIA 编译验证。
变体与扩展
- 多实例调用:在 OB 里为每个块建一个
FB_BlockBackup 实例,或者在 FB 里用多实例数组;每个实例传不同的 Blocks[Nr] 和 DBBlock。
- 状态报警:原工程用
Program_Alarm 把 WrDone/RdDone 和 RET_VAL 发成报警;本页把 WrDone/RdDone 留在块头里,HMI 直接读。
- 自动备份:在
MainHeader.Save 的上升沿触发一次;完成后把 WrCounter=0 作为“全部完成”信号。
- 只备份变化块:给每个块加
Dirty 标志,只对 Dirty=1 的块执行 WRIT_DBL,减少负载内存写入。
- 恢复前校验:在
READ_DBL 前检查 WrRetMem/RdRetMem,或用一个版本号/校验和确认负载内存里的备份是完整的。
常见问题
WRIT_DBL 和 READ_DBL 有什么区别?
WRIT_DBL 把工作内存中的 DB 内容写到负载内存(备份),READ_DBL 把负载内存中的 DB 内容读回工作内存(恢复)。两者都用 REQ/BUSY/RET_VAL 握手,RET_VAL=0 或 28672 表示本块完成。
为什么要按块调用,而不是一个 FB 处理所有块?
原工程把主计数器放在共享 MainHeader,把每个块的忙碌/完成/返回值放在各自的 BlockHeader;调用方对每个块使用独立实例,Nr 表示块号。一个实例处理多个块会共用同一份块状态,无法区分哪个块正在写/读。
验证状态
| 项目 | 结果 | 日期 | 证据 |
|---|
| 本地仿真场景 | 未启用 | — | 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_BlockBackup + DB_BackupControl 编译 0 错误 / 0 警告,已保存。 |
本地仿真引擎验证语法、结构和逐扫描行为;TIA 编译验证在 Windows + TIA Portal + TiaLink 上完成。两者不能互相替代。
来源与许可
本页示例改编自 github.com/lopez-dev/Siemens-SCL-Source-Files 的 BackupAndRestore/backup.scl,原作者 Daniel Cáceres López,以 MIT 许可发布。中文注释、接口调整、仿真场景与页面讲解由 Learn-SCL 编写。
原文件用两个 UDT + Program_Alarm + Variant DB,按块调用;本站用 inline STRUCT 保留主/块头字段,去掉 Program_Alarm,保留 WRIT_DBL/READ_DBL 和计数器逻辑。
示例源码以 MIT 许可发布(上游仓库);中文讲解与页面内容版权归汇智网(Hubwiz)。官方参考:Siemens TIA Portal。