跳到正文
Learn-SCL
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 里调用。

TiaLink 设置

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

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

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 和计数器逻辑完整保留。

主控制头、4 个块头和 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

区块名称类型说明
输入NrInt
输入/输出MainHeaderSTRUCT
输入/输出BlockHeaderSTRUCT
输入/输出DBBlock"Variant"

DB_BackupControl DB

区块名称类型说明
静态MainHeaderSTRUCT
静态BlocksARRAY[1..4] OF STRUCT

程序结构

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

  1. FB_BlockBackup FB 单块备份/恢复,主计数器 + 块头状态 · 124 行 · 2 段
  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;   // 当前块号(1..LastBlock)
6   END_VAR
7 
8   VAR_IN_OUT
9      MainHeader : Struct
10         Save : Bool;        // TRUE = 启动备份
11         Restore : Bool;     // TRUE = 启动恢复
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;   // 要备份/恢复的 DB
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      // 启动一次写:WrProcess 防止重复触发,Write 只给一个周期。
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      // RET_VAL 0 和 28672(后台完成)都算成功。
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      // 启动一次写:WrProcess 防止重复触发,Write 只给一个周期。
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      // RET_VAL 0 和 28672(后台完成)都算成功。
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 写到负载内存。

条件 → 动作:

  1. MainHeader.Save=1 且 WrCounter = Nr 且当前块还没开始 → BlockHeader.Save=1;
  2. 如果当前块在恢复,BlockHeader.Save=0(恢复优先);
  3. WrCounter=0 时初始化为 1;
  4. BlockHeader.Save 且 WrProcess=0 → WrProcess=1、Write=1、WrDone=0;
  5. 调用 WRIT_DBL(REQ := Write, SRCBLK := DBBlock, BUSY => WrBusy, DSTBLK := DBBlock);
  6. WrBusy=1 时撤销 Write,保存 WrRetMem;
  7. WrProcess=1 且 WrRet 为 0 或 28672 → WrCounter+1、WrProcess=0、Save=0、WrDone=1;
  8. 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 从负载内存读回工作内存。

条件 → 动作:

  1. RdCounter=0 时初始化为 1;
  2. MainHeader.Restore=1 且 RdCounter = Nr 且当前块还没开始 → BlockHeader.Restore=1;
  3. Restore 且 RdProcess=0 → RdProcess=1、Read=1、RdDone=0;
  4. 调用 READ_DBL(REQ := Read, SRCBLK := DBBlock, BUSY => RdBusy, DSTBLK := DBBlock);
  5. RdBusy=1 时撤销 Read,保存 RdRetMem;
  6. RdProcess=1 且 RdRet 为 0 或 28672 → RdCounter+1、RdProcess=0、Restore=0、RdDone=1;
  7. 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-01TIA-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。

把这个示例带进你的工程

TiaLink

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

了解 TiaLink →

PLC 云仿真

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

打开云仿真 →

Learn-LAD

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

浏览 LAD 示例 →