跳到正文
Learn-SCL
SCL 示例库/SCL 数据日志:DataLogCreate / Write / Close

SCL 数据日志:DataLogCreate / Write / Close

TIA-only:用 DB 定义日志表头和数据列,一个 FB 管理创建、写入、关闭和错误状态,不依赖云仿真。

综合 S7-1200 S7-1500 DataLog DB 状态机 TIA-only 2 块 / 178 行 TIA 验证 TIA 已验证

一句话:DataLog 用 DB 定义日志的 HEADER 和 DATA 列,用 DataLogCreate 创建文件并取得 ID,DataLogWrite 按 ID 写记录,DataLogClose 关闭;FB 用 0/10/20/30/40/90 状态机管理 REQ/DONE/BUSY/ERROR/STATUS,所有实例每周期都要调用。

导入到我的 TIA 项目

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

TiaLink 设置

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

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

TIA-only 示例:本页使用 TIA Portal 的 DataLog 文件系统指令,依赖 CPU 的存储卡/Web 服务器环境;本地 SCL 仿真引擎只有硬件桩,不会真正写文件,所以不提供云仿真场景。验证方式是导入 TIA Portal 编译。

数据日志(DataLog)是 S7-1200/1500 把过程数据写成 CSV 文件的系统功能。它不像普通数组那样“写内存”,而是由 CPU 的文件系统管理:先创建日志文件,拿到一个 ID,之后每次写入都用这个 ID,最后关闭文件。

原工程 LogMsg.scl 是一个 49KB 的日志框架,包含创建、写入、关闭、打开、新建文件、删除、缓冲队列和状态文本。本页抽取最常用的 Create → Write → Close 主链路,整理成 DB_DataLog + FB_DataLog 两个块,既能在 TIA 里编译,也方便逐段理解。

DataLog 创建、写入、关闭的状态机和 DB 表头/数据列
DB 定义列;FB 在 0/10/20/30/40/90 状态间推进,DataLogCreate → Write → Close 都按 ID 操作。

概述

  • DB_DataLog:定义日志文件的表头 Header 和数据列 Data。Data 里的字段就是 CSV 的列:ID、Value、Text。
  • FB_DataLog:管理日志生命周期,状态码:
状态 含义
0 空闲,可以创建
10 正在创建(DataLogCreate)
20 已创建,可以写入
30 正在写入(DataLogWrite)
40 正在关闭(DataLogClose)
90 错误,等待关闭/复位

Create / Write / Close 都取上升沿,避免 HMI 电平在一个扫描周期里重复触发。每次操作成功,Done 只在一个扫描周期为 1;Error 在状态 90 保持,Status 保存最后一次错误码。

你将学到

  • DataLogCreate / DataLogWrite / DataLogClose 的参数和调用顺序;
  • 为什么 HEADER / DATA 必须放在 DB 里;
  • 如何用状态机管理 REQ / DONE / BUSY / ERROR / STATUS;
  • 为什么所有 DataLog 实例必须每个扫描周期都调用;
  • 日志 ID 和写入计数的保存方式。

接口与变量

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

DB_DataLog DB

区块名称类型说明
静态HeaderSTRUCT
静态DataSTRUCT

FB_DataLog FB

区块名称类型说明
输入CreateBool
输入WriteBool
输入CloseBool
输入FileNameSTRING[64]
输入RecordIDUInt
输入RecordValueReal
输入RecordTextSTRING[64]
输出ReadyBool
输出BusyBool
输出DoneBool
输出ErrorBool
输出StatusWord
输出LogIDDWord
输出WrittenUInt
输出StateInt
静态CreateInst"DataLogCreate"
静态WriteInst"DataLogWrite"
静态CloseInst"DataLogClose"
静态CreatePrevBool
静态WritePrevBool
静态ClosePrevBool
静态CreatePulseBool
静态WritePulseBool
静态ClosePulseBool
静态CreateDoneIntBool
静态CreateBusyIntBool
静态CreateErrorIntBool
静态CreateStatusIntWord
静态WriteDoneIntBool
静态WriteBusyIntBool
静态WriteErrorIntBool
静态WriteStatusIntWord
静态CloseDoneIntBool
静态CloseBusyIntBool
静态CloseErrorIntBool
静态CloseStatusIntWord
静态LogIDIntDWord
静态WrittenIntUInt
静态StateIntInt
静态StatusIntWord

程序结构

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

  1. DB_DataLog DB 日志表头和数据列 · 16 行 · 1 段
  2. FB_DataLog FB 创建/写入/关闭状态机 · 162 行 · 5 段

DB_DataLog DB

日志表头和数据列 · 16 行 · 源文件 blocks/DB_DataLog.scl

查看 / 复制完整块源码
DB_DataLog.scl
1DATA_BLOCK "DB_DataLog"
2{ S7_Optimized_Access := 'TRUE' }
3VERSION : 0.1
4   VAR
5      Header : Struct
6         Site : String[32];
7      END_STRUCT;
8      Data : Struct
9         ID : UInt;
10         Value : Real;
11         Text : String[64];
12      END_STRUCT;
13   END_VAR
14BEGIN
15END_DATA_BLOCK
16 

DB_DataLog / 整体逻辑 — 日志的列定义

DB_DataLog.scl · 整体逻辑
15END_DATA_BLOCK

目的:给 DataLogCreate 提供表头和数据列。

条件 → 动作:Header 里放 Site / Line,Data 里放 ID / Value / Text;FB 在写入前把值填进 "DB_DataLog".Data。

为什么:DataLogCreate 的 HEADER 和 DATA 参数要求指向 DB 中的结构;如果传局部变量或临时变量,运行时会报“指向本地内存”的错误(例如 16#8B24 / 16#8C24)。

注意:Data 的字段顺序就是 CSV 的列顺序。增删字段后,已经存在的日志文件定义会不匹配,DataLogWrite 可能返回 16#8093;这时要新建日志文件。

FB_DataLog FB

创建/写入/关闭状态机 · 162 行 · 源文件 blocks/FB_DataLog.scl

查看 / 复制完整块源码
FB_DataLog.scl
1FUNCTION_BLOCK "FB_DataLog"
2{ S7_Optimized_Access := 'TRUE' }
3VERSION : 0.1
4   VAR_INPUT
5      Create : Bool;
6      Write : Bool;
7      Close : Bool;
8      FileName : String[64] := 'LearnSCL_Log';
9      RecordID : UInt;
10      RecordValue : Real;
11      RecordText : String[64];
12   END_VAR
13 
14   VAR_OUTPUT
15      Ready : Bool;
16      Busy : Bool;
17      Done : Bool;
18      Error : Bool;
19      Status : Word;
20      LogID : DWord;
21      Written : UInt;
22      State : Int;
23   END_VAR
24 
25   VAR
26      CreateInst : DataLogCreate;
27      WriteInst : DataLogWrite;
28      CloseInst : DataLogClose;
29      CreatePrev : Bool;
30      WritePrev : Bool;
31      ClosePrev : Bool;
32      CreatePulse : Bool;
33      WritePulse : Bool;
34      ClosePulse : Bool;
35      CreateDoneInt : Bool;
36      CreateBusyInt : Bool;
37      CreateErrorInt : Bool;
38      CreateStatusInt : Word;
39      WriteDoneInt : Bool;
40      WriteBusyInt : Bool;
41      WriteErrorInt : Bool;
42      WriteStatusInt : Word;
43      CloseDoneInt : Bool;
44      CloseBusyInt : Bool;
45      CloseErrorInt : Bool;
46      CloseStatusInt : Word;
47      LogIDInt : DWord := 0;
48      WrittenInt : UInt := 0;
49      StateInt : Int := 0;
50      StatusInt : Word := 16#0000;
51   END_VAR
52BEGIN
53   REGION 边沿与复位
54      #CreatePulse := #Create AND NOT #CreatePrev;
55      #CreatePrev := #Create;
56      #WritePulse := #Write AND NOT #WritePrev;
57      #WritePrev := #Write;
58      #ClosePulse := #Close AND NOT #ClosePrev;
59      #ClosePrev := #Close;
60 
61      #Done := FALSE;
62      #Busy := FALSE;
63      #CreateDoneInt := FALSE;
64      #WriteDoneInt := FALSE;
65      #CloseDoneInt := FALSE;
66   END_REGION
67 
68   REGION 创建日志
69      IF #CreatePulse AND #StateInt = 0 THEN
70         #StateInt := 10;
71         #StatusInt := 16#0000;
72      END_IF;
73 
74      #CreateInst(REQ := #StateInt = 10,
75         RECORDS := 1000,
76         FORMAT := 1,
77         TIMESTAMP := 4,
78         NAME := #FileName,
79         ID := #LogIDInt,
80         HEADER := "DB_DataLog".Header,
81         DATA := "DB_DataLog".Data,
82         DONE => #CreateDoneInt,
83         BUSY => #CreateBusyInt,
84         ERROR => #CreateErrorInt,
85         STATUS => #CreateStatusInt);
86 
87      IF #StateInt = 10 THEN
88         #Busy := #CreateBusyInt;
89         IF #CreateDoneInt THEN
90            #StateInt := 20;
91            #Done := TRUE;
92         ELSIF #CreateErrorInt THEN
93            #StateInt := 90;
94            #StatusInt := #CreateStatusInt;
95         END_IF;
96      END_IF;
97   END_REGION
98 
99   REGION 写入记录
100      IF #WritePulse AND #StateInt = 20 THEN
101         "DB_DataLog".Data.ID := #RecordID;
102         "DB_DataLog".Data.Value := #RecordValue;
103         "DB_DataLog".Data.Text := #RecordText;
104         #StateInt := 30;
105      END_IF;
106 
107      #WriteInst(REQ := #StateInt = 30,
108         ID := #LogIDInt,
109         DONE => #WriteDoneInt,
110         BUSY => #WriteBusyInt,
111         ERROR => #WriteErrorInt,
112         STATUS => #WriteStatusInt);
113 
114      IF #StateInt = 30 THEN
115         #Busy := #WriteBusyInt;
116         IF #WriteDoneInt THEN
117            #StateInt := 20;
118            #WrittenInt := #WrittenInt + 1;
119            #Done := TRUE;
120         ELSIF #WriteErrorInt THEN
121            #StateInt := 90;
122            #StatusInt := #WriteStatusInt;
123         END_IF;
124      END_IF;
125   END_REGION
126 
127   REGION 关闭日志
128      IF #ClosePulse AND (#StateInt = 20 OR #StateInt = 90) THEN
129         #StateInt := 40;
130      END_IF;
131 
132      #CloseInst(REQ := #StateInt = 40,
133         ID := #LogIDInt,
134         DONE => #CloseDoneInt,
135         BUSY => #CloseBusyInt,
136         ERROR => #CloseErrorInt,
137         STATUS => #CloseStatusInt);
138 
139      IF #StateInt = 40 THEN
140         #Busy := #CloseBusyInt;
141         IF #CloseDoneInt THEN
142            #StateInt := 0;
143            #LogIDInt := 0;
144            #WrittenInt := 0;
145            #Done := TRUE;
146         ELSIF #CloseErrorInt THEN
147            #StateInt := 90;
148            #StatusInt := #CloseStatusInt;
149         END_IF;
150      END_IF;
151   END_REGION
152 
153   REGION 状态输出
154      #Ready := #StateInt = 20;
155      #Error := #StateInt = 90;
156      #Status := #StatusInt;
157      #LogID := #LogIDInt;
158      #Written := #WrittenInt;
159      #State := #StateInt;
160   END_REGION
161END_FUNCTION_BLOCK
162 

FB_DataLog / 边沿与复位 — 一次请求只执行一次

FB_DataLog.scl · 边沿与复位
53   REGION 边沿与复位
54      #CreatePulse := #Create AND NOT #CreatePrev;
55      #CreatePrev := #Create;
56      #WritePulse := #Write AND NOT #WritePrev;
57      #WritePrev := #Write;
58      #ClosePulse := #Close AND NOT #ClosePrev;
59      #ClosePrev := #Close;
60 
61      #Done := FALSE;
62      #Busy := FALSE;
63      #CreateDoneInt := FALSE;
64      #WriteDoneInt := FALSE;
65      #CloseDoneInt := FALSE;
66   END_REGION

目的:把 HMI 的 Create / Write / Close 电平转换成单扫描周期脉冲,并清理上一周期的 Done / Busy 标志。

条件 → 动作:CreatePulse := Create AND NOT CreatePrev,每个周期更新 CreatePrev;WritePulse / ClosePulse 同理;每个周期先把 Done、Busy 和三个实例的 DONE 中间变量清 0。

为什么:DataLog 指令是异步的,REQ 只需要一个周期;如果一直给 REQ=1,实例会反复启动,状态机无法判断“这一次操作什么时候完成”。

注意:CreatePrev / WritePrev / ClosePrev 必须是静态变量;放 VAR_TEMP 会每个周期重建,边沿永远检测不到。

FB_DataLog / 创建日志 — DataLogCreate

FB_DataLog.scl · 创建日志
68   REGION 创建日志
69      IF #CreatePulse AND #StateInt = 0 THEN
70         #StateInt := 10;
71         #StatusInt := 16#0000;
72      END_IF;
73 
74      #CreateInst(REQ := #StateInt = 10,
75         RECORDS := 1000,
76         FORMAT := 1,
77         TIMESTAMP := 4,
78         NAME := #FileName,
79         ID := #LogIDInt,
80         HEADER := "DB_DataLog".Header,
81         DATA := "DB_DataLog".Data,
82         DONE => #CreateDoneInt,
83         BUSY => #CreateBusyInt,
84         ERROR => #CreateErrorInt,
85         STATUS => #CreateStatusInt);
86 
87      IF #StateInt = 10 THEN
88         #Busy := #CreateBusyInt;
89         IF #CreateDoneInt THEN
90            #StateInt := 20;
91            #Done := TRUE;
92         ELSIF #CreateErrorInt THEN
93            #StateInt := 90;
94            #StatusInt := #CreateStatusInt;
95         END_IF;
96      END_IF;
97   END_REGION

目的:创建日志文件并取得 ID。

条件 → 动作:状态 0 收到 CreatePulse → 状态 10;调用 DataLogCreate,REQ := State=10,RECORDS := 1000、FORMAT := 1、TIMESTAMP := 4、NAME := FileName、HEADER := "DB_DataLog".Header、DATA := "DB_DataLog".Data、ID := LogIDInt;DONE → 状态 20,ERROR → 状态 90。

为什么:ID 是后续 Write/Close 的句柄,必须在 Create 成功后保存;RECORDS 是文件容量,满了以后 DataLogWrite 会返回 16#0001(覆盖最旧记录)。

注意:DataLogCreate 实例要每个周期调用,即使 REQ=0;TIA 需要在每个扫描周期更新实例状态。创建失败时把 STATUS 保存到 StatusInt,不要只闪一个周期。

FB_DataLog / 写入记录 — DataLogWrite

FB_DataLog.scl · 写入记录
99   REGION 写入记录
100      IF #WritePulse AND #StateInt = 20 THEN
101         "DB_DataLog".Data.ID := #RecordID;
102         "DB_DataLog".Data.Value := #RecordValue;
103         "DB_DataLog".Data.Text := #RecordText;
104         #StateInt := 30;
105      END_IF;
106 
107      #WriteInst(REQ := #StateInt = 30,
108         ID := #LogIDInt,
109         DONE => #WriteDoneInt,
110         BUSY => #WriteBusyInt,
111         ERROR => #WriteErrorInt,
112         STATUS => #WriteStatusInt);
113 
114      IF #StateInt = 30 THEN
115         #Busy := #WriteBusyInt;
116         IF #WriteDoneInt THEN
117            #StateInt := 20;
118            #WrittenInt := #WrittenInt + 1;
119            #Done := TRUE;
120         ELSIF #WriteErrorInt THEN
121            #StateInt := 90;
122            #StatusInt := #WriteStatusInt;
123         END_IF;
124      END_IF;
125   END_REGION

目的:把一条记录写进已经创建的日志。

条件 → 动作:状态 20 收到 WritePulse → 把 RecordID / RecordValue / RecordText 填进 "DB_DataLog".Data → 状态 30;调用 DataLogWrite,REQ := State=30、ID := LogIDInt;DONE → 状态 20,WrittenInt := WrittenInt + 1;ERROR → 状态 90。

为什么:DataLogWrite 不接收数据参数,它写的是创建时 DATA 指向的 DB 当前值;所以必须先填 DB,再给 REQ。

注意:不要在 REQ=1 的同一个周期修改 "DB_DataLog".Data 的字段顺序或类型;DataLog 的列定义在创建时已经固定。写入失败时状态 90,需要先 Close 再重新 Create。

FB_DataLog / 关闭日志 — DataLogClose

FB_DataLog.scl · 关闭日志
127   REGION 关闭日志
128      IF #ClosePulse AND (#StateInt = 20 OR #StateInt = 90) THEN
129         #StateInt := 40;
130      END_IF;
131 
132      #CloseInst(REQ := #StateInt = 40,
133         ID := #LogIDInt,
134         DONE => #CloseDoneInt,
135         BUSY => #CloseBusyInt,
136         ERROR => #CloseErrorInt,
137         STATUS => #CloseStatusInt);
138 
139      IF #StateInt = 40 THEN
140         #Busy := #CloseBusyInt;
141         IF #CloseDoneInt THEN
142            #StateInt := 0;
143            #LogIDInt := 0;
144            #WrittenInt := 0;
145            #Done := TRUE;
146         ELSIF #CloseErrorInt THEN
147            #StateInt := 90;
148            #StatusInt := #CloseStatusInt;
149         END_IF;
150      END_IF;
151   END_REGION

目的:关闭文件,释放日志 ID。

条件 → 动作:状态 20 或 90 收到 ClosePulse → 状态 40;调用 DataLogClose,REQ := State=40、ID := LogIDInt;DONE → 状态 0,LogIDInt := 0、WrittenInt := 0;ERROR → 状态 90。

为什么:关闭会刷新文件系统缓冲;不关闭就断电,最后几条记录可能丢失。错误状态也允许 Close,避免日志一直占着 ID。

注意:Close 之后 LogIDInt 清 0;下一次 Create 会生成新的 ID。不要用旧的 ID 继续 Write。

FB_DataLog / 状态输出 — 给 HMI 的汇总

FB_DataLog.scl · 状态输出
153   REGION 状态输出
154      #Ready := #StateInt = 20;
155      #Error := #StateInt = 90;
156      #Status := #StatusInt;
157      #LogID := #LogIDInt;
158      #Written := #WrittenInt;
159      #State := #StateInt;
160   END_REGION

目的:把内部状态翻译成 Ready、Busy、Done、Error、Status、LogID、Written。

条件 → 动作:Ready := State=20;Error := State=90;Status := StatusInt;LogID := LogIDInt;Written := WrittenInt;State := StateInt。

为什么:HMI 不需要理解 0/10/20/30/40/90,只需要“能不能写”“有没有错”“写了多少条”。

注意:Done 是单周期脉冲,HMI 要用上升沿或自锁;Error 是状态 90 的电平,直到 Close/重新 Create 才消失。

仿真验证

本示例依赖 TIA Portal 系统库或硬件环境,暂不在云端仿真中执行。请使用上方 TiaLink 面板导入 TIA,在真实编译/仿真环境中验证。

关键 SCL 语法

写法 含义
DataLogCreate / DataLogWrite / DataLogClose TIA 数据日志指令,实例放在静态 VAR
REQ := State = 10 用比较表达式给指令的请求输入
DONE => #DoneInt 指令输出用 => 接变量
ID := #LogIDInt 日志句柄,Create 后保存,Write/Close 复用
"DB_DataLog".Data.ID := #RecordID 写 DB 的列值
String[64] 文件名/文本列的字符串长度
REGION ... END_REGION 代码分段

常见坑

  • 把 HEADER/DATA 放在局部变量:运行时报“指向本地内存”;必须用 DB 中的结构。
  • 不每个周期调用实例:DataLog 实例需要周期调用才能更新 BUSY/DONE;只在 REQ 时调用会卡住。
  • REQ 一直为 1:实例反复启动,状态机永远等不到稳定的 DONE;必须用单周期脉冲。
  • Create 后不保存 ID:Write/Close 用 0 或旧 ID,报“日志不存在”。
  • 写入前不填 DB:CSV 里全是上一次的旧值;DataLogWrite 不接收数据参数。
  • 不关闭就断电:文件缓冲没有刷新,最后几条记录可能丢失。
  • 改 DB 列定义后继续写旧文件:列不匹配,DataLogWrite 返回 16#8093;重新 Create 新文件。

变体与扩展

  • 自动换文件:原工程在 DataLogWrite.STATUS = 16#0001(文件满)时触发 DataLogNewFile;本页只保留 Create/Write/Close,换文件可以按同样状态机扩展。
  • 缓冲队列:原工程先用数组缓冲日志记录,再批量写入,避免高频事件拖慢扫描;本页直接把单条记录写进 DB,适合低频日志。
  • 多日志文件:把 LogIDInt 做成 Array[0..N] of DWord,每个文件一个 ID;Create/Write/Close 用索引选择。
  • 删除/清空:TIA 还有 DataLogDelete / DataLogClear;本页不展开,避免权限和文件系统差异。
  • 真实项目里要先在 CPU 属性中启用 Web 服务器并配置访问权限,否则 DataLog 文件无法通过浏览器下载。

常见问题

为什么这个示例不能在云仿真里跑?

DataLogCreate/Write/Close 是 TIA Portal 的文件系统指令,依赖 CPU 的存储卡/Web 服务器环境;本地 SCL 仿真引擎只有硬件桩,不会真正写文件。因此本页只做 TIA 导入编译验证,不提供 scl_run_scenario 场景。

DataLogCreate 的 DATA 为什么必须放在 DB 里?

DataLogCreate 的 HEADER 和 DATA 参数要求指向 DB 中的结构,不能指向临时变量或局部变量;本页用 DB_DataLog 的 Header/Data 两个 STRUCT 提供列定义,FB 只负责在写入前把值填进 Data。

验证状态

项目结果日期证据
本地仿真场景未启用—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,DB_DataLog + FB_DataLog 编译 0 错误 / 0 警告,已保存。

本地仿真引擎验证语法、结构和逐扫描行为;TIA 编译验证在 Windows + TIA Portal + TiaLink 上完成。两者不能互相替代。

来源与许可

本页示例改编自 github.com/lopez-dev/Siemens-SCL-Source-Files 的 LogMsg/LogMsg.scl,原作者 Daniel Cáceres López,以 MIT 许可发布。中文注释、接口调整、仿真场景与页面讲解由 Learn-SCL 编写。

原文件是 49KB 的日志框架,依赖 DataLogCreate/Write/Close/Open/NewFile/Delete;本站抽取 create/write/close 主链路,整理成 DB + FB 的最小可编译版本。

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

把这个示例带进你的工程

TiaLink

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

了解 TiaLink →

PLC 云仿真

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

打开云仿真 →

Learn-LAD

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

浏览 LAD 示例 →