简介:面向工业自动化工程师、PLC通讯调试人员的技术文档,聚焦西门子S7-1200、S7-1500与DASSIDirect通讯时的配置差异、兼容性限制及数据语法问题。压缩包内含1个docx文件,大小约44KB,内容精炼,适合在项目现场快速查阅。文档梳理了TIA博途中的相关设置要点、DASSIDirect对S7-1200和S7-1500不支持的特性,以及LREAL、DTL等数据类型的项名语法,并对块项目、计数器与定时器访问、周期服务和SAPI等限制做了说明。对于正在搭建自动化系统、判断通讯方式是否可行的读者,文档可作为配置前的核对清单和现场排错时的参考依据,内容侧重实际约束与语法说明,便于快速理解两者配合使用时的边界,帮助减少配置遗漏与调试反复,提升现场调试效率。已有2885人学习下载,适合需要处理西门子PLC与DASSIDirect通讯问题的技术人员参考。
1. 两个默认开关,决定了后面所有地址是否白写
把 S7-1200 或者 S7-1500 接到 DASSIDirect 上,最常见的翻车现场不是数据类型对不上,也不是地址拼错,而是博途工程里两个默认值:CPU 保护属性里的「允许来自远程伙伴的 PUT/GET 通讯访问」默认没勾,数据块属性里的「优化的块访问」默认勾上。前者让 DAServer 的读请求在协议层直接被拒,后者让 DAServer 拿到的字节偏移与 PLC 实际内存布局完全错位。更麻烦的是,这两个失败不会在博途里报错,PLC 照常运行,HMI 照常刷新,只有 DASSIDirect 侧看到一片坏质量或者读回一堆毫无意义的值,排查方向很容易被带偏到网卡、防火墙和 DCOM 上。搞懂这两个开关背后各自对应的一层机制,等于拿到了 S7-1200/1500 与 DASSIDirect 通讯的入口钥匙。
2.1 PUT/GET 连接机制:一个勾选框背后的协议分工
要理解那个勾选框,先得区分 S7 通讯里两条完全不同的路径。一条是「配置连接」,在博途的硬件组态里显式建一个 S7 连接,双方都知道对端、知道连接号、知道 TSAP,HMI、上位 SCADA、PLC 之间的 S7 通讯多走这条;另一条是「非配置连接」,客户端不需要在 PLC 里留下任何组态痕迹,直接对 PLC 的 ISO-on-TCP 端口发起 S7 协议请求,用 Read Var(功能码 0x04)和 Write Var(功能码 0x05)按地址读写字区。DASSIDirect 走的是后者:它扮演主动客户端,PLC 是被动服务器,DAServer 只需要知道 IP、机架槽位和一段绝对地址,就能发起读写。
问题在于 S7-1200 和 S7-1500 把这条路径默认关掉了。厂商在固件层面做了权限收敛,CPU 的保护属性里有一个「连接机制」区域,只有勾上「允许来自远程伙伴的 PUT/GET 通讯访问」,CPU 才会在 S7 协议层响应外部的 Read Var / Write Var 请求;不勾,请求照样能到达 TCP 层,但会在 S7 层被拒绝,返回的典型错误是「功能不可用」或者「访问被拒绝」这一类,客户端拿到的是一个明确的否定应答,而不是超时。这两种失败在日志里的表现差别很大:超时说明网络或者端口不通,否定应答说明网络通了、协议也握上手了、只是权限没开。分不清这两者,就会在网线和防火墙上耗掉半天。
还有一个容易被忽略的细节是连接资源。PUT/GET 连接在 CPU 里是占用连接资源的,S7-1200 这类小型 CPU 的连接资源本就不宽裕,一个 DAServer 下挂多个 Device Group、每个 Group 又各自建连接时,很容易把配额吃满,表现是前面几个 Group 正常、后面新加的 Group 一律连不上。常见的做法是把同一台 PLC 下的点位尽量收敛到少数几个 Device Group,而不是按工艺段无节制地拆。
提示:勾选 PUT/GET 之后需要重新下载硬件组态并重启 CPU,只下载程序块不生效。
2.2 优化块访问:符号寻址把绝对地址藏了起来
第二个开关更隐蔽,因为它影响的是「地址」这个概念本身。S7-1200 和 S7-1500 的数据块默认启用「优化的块访问」,这个模式下编译器拥有很大的自由度:成员可以按类型重新排序以减少填充,位变量可以打包进同一个字节,结构体内部不保证声明顺序就是内存顺序,最关键的是——它不生成稳定的绝对字节偏移,访问全靠符号名。这套设计对 PLC 内部程序是好事,访问更快、内存更省、改结构体不用重新算地址。
但 DASSIDirect 的 item 语法是彻头彻尾的绝对地址寻址。D<B,I>d,Bx里的 d 是数据块号,x 是起始字节偏移,读一个字节;D<B,I>d,Wn的 n 是 2 字节数据的起始偏移;D<B,I>d,Dm的 m 是 4 字节数据的起始偏移。DAServer 把这些数字原封不动塞进 S7 报文的地址字段:区域标识 0x84 表示数据块,后面跟 DB 号,再跟一个以位为单位的字节地址(即 x 乘以 8)。PLC 收到后按这个偏移去内存里取字节,取到什么就是什么,它不会、也没法替你按符号名去找。
于是矛盾就出现了:优化块里实际布局和你想的偏移不是一回事,DAServer 按老地址去读,读到的是别的变量、填充字节,或者干脆越界。取消「优化的块访问」之后,数据块退回到标准布局:成员按声明顺序排列,按各自类型的对齐要求插入填充,偏移可预测、可计算,这才让D<B,I>d,Bx这套语法重新变得有意义。
/* 标准(非优化)DB 布局:偏移由声明顺序和对齐规则决定,可预测 */ struct DB100_Standard { uint8_t start_flag; /* offset 0 */ /* 3 字节填充:下一个成员是 4 字节对齐 */ uint32_t total_count; /* offset 4 */ float temperature; /* offset 8 */ uint16_t status_word; /* offset 12 */ /* 2 字节尾部填充,结构体按 4 字节对齐 */ }; /* size = 16 */ /* 优化 DB:编译器可重排、可省填充、可不暴露绝对偏移 同样的成员,实际偏移可能是 0 / 1 / 5 / 9,且随编译变化 DASSIDirect 里写死的 D100,D4 读到的就不再是 total_count */上面这段对比说明的是同一件事:标准布局下total_count永远在偏移 4,temperature永远在偏移 8,这些数字可以写进 DAServer 的点表并且长期稳定;优化布局下这些数字没有任何契约保证,改一次结构体、换一个固件版本,偏移就可能漂移。实际工程里我一般会在数据块里保留一小段注释,把每个成员的偏移和类型标出来,跟 DAServer 点表一一对应,改结构体时两边一起改,避免出现「程序改完了、画面还在读旧地址」的静默错误。
2.3 从 item 名到报文:一次 Read Var 的地址是怎么拼出来的
把上面两件事串起来看,DAServer 的一个点位名经过解析后,最终变成 S7 报文里的一组字段。以D100,B4为例:区域标识取 0x84(数据块),DB 号取 100,起始地址取 4 乘以 8 等于 32 位,长度取 1 字节。D100,W8则是同一区域、同一 DB 号、起始地址 8 乘以 8 等于 64 位、长度 2 字节。带数组后缀的D100,B4,10表示从偏移 4 开始连续读 10 个元素,长度字段随之变化。
# 把 DASIDirect 风格的 item 名翻译成 S7 Read Var 的地址三元组 def parse_db_item(item): # 形如 D100,B4 / D100,W8 / D100,B4,10 head, *rest = item.split(",") db_num = int(head[1:]) # D 后面是 DB 号,1..65535 kind = rest[0][0] # B/W/D/REAL/LREAL/DTL... offset = int(rest[0][1:]) # 起始字节偏移 count = int(rest[1]) if len(rest) > 1 else 1 # v:元素个数 size = {"B": 1, "W": 2, "D": 4}.get(kind, 1) return { "area": 0x84, # 0x84 = 数据块区 "db": db_num, "bit_addr": offset * 8, # 报文里地址以位为单位 "length": size * count, # 字节数 } print(parse_db_item("D100,B4")) # {'area':132,'db':100,'bit_addr':32,'length':1} print(parse_db_item("D100,W8")) # {'area':132,'db':100,'bit_addr':64,'length':2}这段代码的意义不在于真的要自己发报文,而在于说明「偏移」这个概念在链路两端必须以同一套规则存在。博途那边取消优化块访问,是在生产端把绝对偏移固定下来;DASSIDirect 这边拼地址,是在消费端按固定偏移去取。两端都遵守同一份布局契约,通讯才成立。中间任何一端换了规则,报文本身不会报错,只会安静地读回错误的字节——这是最难查的一类故障。
3.1 Block Items 的支持矩阵:为什么 1500 行、1200 不行
DASSIDirect 提供了一类叫 Block Items 的点位,官方描述里把它分成只读块项目和只写块项目两组,本质上就是把一段连续的内存区域当成一个大对象整体读写,而不是一个点位一个点位地发单条请求。这在 S7-300、S7-400 上是很成熟的用法:一次请求拉回一整块 DB,DAServer 在本地再按偏移切片,省掉大量往返。
原文给的支持矩阵很明确:块项目支持 S7-300、S7-400 和 S7-1500,不支持 S7-200 和 S7-1200。这个差异不是随意的,它跟两个系列固件对「多变量读」和「跨区连续读」的实现完整度有关。S7-1500 的通讯栈在这一层做得更完整,允许一次请求覆盖较大范围的连续区域;S7-1200 的固件在整块读取上有额外约束,DAServer 干脆不给它开这个能力,退化成逐点请求。
这个限制的实际后果是性能模型变了。用 1500 时,一百个连续点位可以压成一两次请求;用 1200 时,同样一百个点位就是一百次请求起步,每次请求还要受 PDU 大小限制。常见的实现思路是:在 1200 上尽量把要采集的变量在 DB 里连续排布,用数组型 item(带v参数)一次读一段,而不是散落在不同 DB、不同偏移上各发一条。这本质上是在应用层手工模拟块读取,虽然不如原生的 Block Items 优雅,但能把请求数压下来一个数量级。
3.2 计数器与定时器:IEC 背景 DB 取代了独立的 C/T 区
S7-300/400 那一代 CPU 有独立的定时器区和计数器区,在 S7 协议里是独立的区域标识,点位名可以写成Tx或Cx,后面再跟不同的后缀去解释原始值:TREAL当浮点秒数读,BCD当 BCD 码读,KTS5T当 S5 时间格式读,KT当 BCD 时间读。这套语法在 300/400 上很好用,因为那块区域是 CPU 硬件层面真实存在的。
S7-1200 和 S7-1500 换了模型。它们用的是 IEC 定时器和计数器,一个 TON 指令实例化出来的东西,本质上是某个背景数据块里的一个结构体,里面有 IN、PT、ET、Q 这些成员,没有一块独立于 DB 之外的「定时器区」可供按 Cx/Tx 寻址。原文对此的表述是:不能用 S7-300/400 那套Cx、Tx及其后缀去订阅 1200/1500 的计数器定时器值,但可以通过数据块点位间接读出关联的内部 IEC 计数器定时器值。
翻译成操作层面就是:想在 DASSIDirect 里看某个定时器的当前值,得先知道这个定时器的背景 DB 号,再知道 ET 成员在这个 DB 里的字节偏移,然后写成D<B,I>d,REALm或D<B,I>d,Dm去读。这里有两个坑。第一,背景 DB 的布局同样受「优化的块访问」影响,不取消优化,偏移根本算不出来;第二,不同指令类型的背景 DB 结构不一样,TON 和 TOF 的成员顺序、TP 的成员顺序都有差别,不能照抄偏移。我一般会先在博途里把背景 DB 打开,切到「非优化」后看每个成员的偏移列,抄下来再写点位。
3.3 循环服务与 SAPI:PLC 侧订阅表的资源账
循环服务(Cyclic Services)是 S7 协议里一个很划算的机制:客户端向 PLC 注册一批点位,PLC 在自己的扫描周期里检测这些点位,值变了就主动推一条报文上来,客户端不用轮询。代价是 PLC 内部要维护一张订阅表,这张表是有限资源,原文里管它叫 credits,可以按 Device Group 分配,可配范围是 1 到 150,默认值是 1。
DASSIDirect 的 Device Group 参数里有一项「Disable S7 cyclic service」,勾上就完全禁用循环服务、全部改成轮询;不勾,DAServer 会尽可能多地把点位注册进循环服务,注册不进去的剩余点位再退化成轮询报文。原文明确写了 S7-1200 和 S7-1500 不支持循环服务和 SAPI——注意这里说的是这两个系列在 DASSIDirect 的语境下不支持这套机制,并不等于 S7 协议本身没有类似能力。
这个限制直接决定了采集架构。在 300/400 上,几百个点位挂循环服务、PLC 主动推、总线占用很低,是标准做法;在 1200/1500 上,这条路走不通,只能轮询。轮询的核心成本是请求数乘以往返时间,而请求数又受 PDU 大小约束:一次 Read Var 能带回的字节数有上限,点位分散就得拆成多条。所以在这两个系列上做优化,方向不是调扫描周期,而是把点位在地址空间上聚拢,让一条请求覆盖尽可能多的有效数据,剩下的零散点位单独处理。
注意:原文提到循环服务对更新频率有可靠保证且总线访问更少,但在 1200/1500 上这条路被关掉之后,更新频率的稳定性就取决于轮询周期和请求耗时之和,评估实时性时要按最坏情况算。
4.1 LREAL:64 位浮点在 item 语法里的表达方式
S7-1200 和 S7-1500 支持一个叫 LREAL 的数据类型,遵循 ANSI/IEEE 754-1985 的 64 位双精度浮点。取值范围原文给得很清楚:负向从 -1.7976931348623158e+308 到 -2.2250738585072014e-308,包含正负零,正向从 +2.2250738585072014e-308 到 +1.7976931348623158e+308。位数是 64 位。
DASSIDirect 为它准备了一整套语法,按存储区区分:
| 存储区 | 语法 | 数组语法 |
|---|---|---|
| 数据块 | D<B,I>d,LREALm | D<B,I>d,LREALm,v |
| 标志位区 | FLREALm/MLREALm | FLREALm,v/MLREALm,v |
| 输入区 | ILREALm/ELREALm | ILREALm,v/ELREALm,v |
| 输出区 | OLREALm/ALREALm/QLREALm | OLREALm,v等 |
| 外设输入 | PILREALm/PELREALm | PILREALm,v等 |
| 外设输出 | POLREALm/PALREALm/PQLREALm | POLREALm,v等 |
其中 d 是数据块号,范围 1 到 65535;m 是 LREAL 变量的起始偏移;v 是数组元素个数,范围 1 到 65535。字母 B 和 I 在D<B,I>d里表示两种 DB 编号写法,实际使用中按工程习惯选一种即可。
这里真正要留心的是对齐。64 位类型在标准布局里通常要求 8 字节对齐,如果前面有个奇数字节的成员,编译器会插填充把偏移推到 8 的倍数上。算偏移时如果只按成员大小累加、忘了填充,读回来的就是错位的数据,而且浮点错位解出来的值往往是个看似合法但完全不对的数,不会报错。原文也提到 1200/1500 并非在所有存储区都支持 LREAL 类型,选点位前最好先在博途里确认目标区确实能放这个类型。
4.2 DTL 的 12 字节结构:一个大端打包的日历
DTL 是这两个系列里另一个值得单独拎出来讲的类型,全称 Date Time Long。它在 PLC 里是一个 12 字节的结构体,逐字节的布局是这样的:
| 字节 | 字段 | 类型 | 取值范围 |
|---|---|---|---|
| 0,1 | Year | UINT | 1970 到 2554 |
| 2 | Month | USINT | 1 到 12 |
| 3 | Day | USINT | 1 到 31 |
| 4 | Weekday | USINT | 1(周日)到 7(周六) |
| 5 | Hour | USINT | 0 到 23 |
| 6 | Minute | USINT | 0 到 59 |
| 7 | Seconds | USINT | 0 到 59 |
| 8,9,10,11 | Nanoseconds | UDINT | 0 到 999,999,999 |
DAServer 读到这 12 个字节后,会根据 item 名的后缀把它转换成不同的规范类型。不带后缀时转成VT_BSTR,字符串形式,保留到纳秒级分辨率,范围是 1970-1-1-0:0:0.000000000 到 2554-12-31-23:59:59.999999999。带TREAL后缀时转成VT_DATE,范围不变但分辨率降到 1 秒。如果点位是数组,还会再叠一层VT_ARRAY,变成VT_ARRAY | VT_BSTR或VT_ARRAY | VT_DATE。原文还提到VT_DATE的字符串写法允许YYYY/MM/DD、MM/DD/YYYY和YYYY-MM-DD三种格式。
import struct from datetime import datetime def parse_dtl(payload: bytes) -> datetime: """解析 S7-1200/1500 的 12 字节 DTL。 S7 是大端序:UINT/UDINT 都是高字节在前。""" if len(payload) < 12: raise ValueError("DTL 至少需要 12 字节") year = struct.unpack(">H", payload[0:2])[0] # 大端 UINT month = payload[2] day = payload[3] _wd = payload[4] # 星期,1=周日 hour = payload[5] minute = payload[6] second = payload[7] nsec = struct.unpack(">I", payload[8:12])[0] # 大端 UDINT # datetime 只到微秒,纳秒部分要自己保留或截断 return datetime(year, month, day, hour, minute, second, nsec // 1000) # 示例:2024-03-15 08:30:00.123456789 raw = bytes([0x07, 0xE8, 3, 15, 6, 8, 30, 0]) + struct.pack(">I", 123456789) print(parse_dtl(raw)) # 2024-03-15 08:30:00.123456这段代码里最容易踩的是字节序。S7 系列在协议层是大端,Year 的两个字节是高字节在前,Nanoseconds 的四个字节同理。如果按小端解,年份会变成一个离谱的数字,纳秒更是一团乱。第二个点是分辨率损失:Python 的 datetime 只到微秒,纳秒那三位在转换时会丢掉,如果业务上真需要纳秒,得单独把 nsec 原值存下来。原文里 DAServer 不带后缀时用字符串保留纳秒,走的正是这个思路——用字符串承载,绕开宿主类型精度不足的问题。
4.3 后缀驱动的类型转换:BCD、S5T、TR、DT 这些老朋友的风险
原文用了一张很长的表,把数据格式、点位、后缀、数据类型和范围列了一遍。这张表里有一类东西值得单独警惕:BCD、S5T、TR、DT、KT这些后缀。它们的作用是让 DAServer 把一段原始字节按特殊格式去解释,比如WORDn带BCD后缀就按 BCD 码解,WORDn带S5T就按 S5 时间格式解,WORDn带TR就按 0.0 到 999.3 的浮点秒数解。
问题在于原文自己加的那条注释:DAServer 提供这些后缀是为了把原始字节解释成特殊格式,但并非所有处理器内部都有对应的梯形图逻辑用同样的方式解释这些格式,例如 S7-1200。这句话的意思是,同一段字节在 300/400 上按S5T解出来的时间和在 1200 上解出来的可能不是一回事,因为两边对这些格式的底层处理并不一致。
常见的做法是:在 1200/1500 上尽量不用这些带格式转换后缀的点位,把原始值直接用整型或浮点读上来,格式解释放到上位侧做。整数就是整数、浮点就是浮点,链路两端对字节的解释完全一致,不会因为固件差异产生歧义。真要用后缀,就先用一个小点位做对照实验,确认读出来的值和博途在线监视里看到的值能对上,再批量铺开。
字符串是另一个高频坑。语法上D<B,I>d,Sx,v和D<B,I>d,STRINGx,v是 S7 字符串,D<B,I>d,S7Sx,w和D<B,I>d,S7STRINGx,w是带净长度的写法,其中 w 是净字符串长度(字符数),报文里的尺寸是 w 加 1,PLC 里字符串表示占 w 加 2。这几个数字差一,读出来的字符串就会多一个乱码字符或者少一位。另外原文明确写了两条硬限制:循环服务里能读的最长字符串或数组是 PDU 大小减 32 字节;上位监控软件能处理的最长字符串是 131 字节;能写入的最长字符串是 256 字节或 PDU 大小减 28 字节,取两者中较小的那个。写长字符串之前先算这几个上限,比写完再查为什么被截断要省事得多。
5.1 坑一:PUT/GET 没勾,报错却是「超时」
现象:DASSIDirect 里所有点位质量都是坏,日志里只有连接超时,网络能 ping 通,端口也通。
原因:TCP 层通了不代表 S7 层放行。CPU 保护属性里的 PUT/GET 开关没勾,PLC 会拒绝 Read Var 请求,但客户端在某些版本里会把否定应答归并为连接失败,表现出来更像是超时。排查时如果只 ping 和 telnet 端口,会误判成网络问题。
解决:进博途,CPU 属性 → 保护 → 连接机制,勾上「允许来自远程伙伴的 PUT/GET 通讯访问」,重新下载硬件组态并重启 CPU。验证方法是先只配一个点位试读,通了再批量铺。
5.2 坑二:优化块访问没关,读回的值「看起来正常但是错的」
现象:通讯正常建立,质量是好的,但读回来的数值和博途在线监视对不上,或者一个浮点数解出来是另一个量纲的数。
原因:DB 还是优化布局,实际偏移和点表里的偏移不是一回事。DAServer 按老偏移取字节,取到的可能是相邻变量甚至填充区,PLC 不会校验你取的是哪个变量,取到什么给什么。
解决:对每个被访问的 DB,右键属性 → 属性 → 取消「优化的块访问」,重新编译下载。改完之后点表里的偏移要重新核对一遍,尤其是结构体里夹着不同宽度成员的地方,填充位置会变。
注意:取消优化之后,DB 里如果有字符串或数组,它们的内部布局也会变,之前按优化布局算出来的偏移全部作废。
5.3 坑三:数组 v 参数超范围,点位直接被判非法
现象:单个元素点位正常,加了,v变成数组点位就报无效地址或者读回空。
原因:数组后缀 v 是元素个数,原文给的范围是 1 到 65535,但实际可用上限还要受 PDU 大小约束——循环服务里最长只能到 PDU 减 32 字节,轮询虽然没这么严格的表述,但一条请求带太多字节同样会被拒。另外原文在数据块表格下方的说明里提到,v作为数组元素长度的范围是 0 到 65534,跟 LREAL 语法里写的 1 到 65535 并不完全一致,两个数字混用很容易越界。
解决:按字节数反算元素个数。一个D100,REAL0,100是 400 字节,远超一条 PDU 能承载的量,要拆成多条。稳妥的做法是先算「PDU 大小减 32」再除以单元素字节数,向下取整,作为单次请求的元素上限。
5.4 坑四:大数值被静默截断,质量位显示 Clamp High
现象:某个 DWORD 或 UDINT 点位读回来的值总是 2147483647,不管 PLC 里的实际值多大。
原因:原文在表格脚注里写得很清楚——对 DDE/SuiteLink 这类上层出口,这个值被限制在 0 到 2147483647 之间,超过的值会被钳到 2147483647,此时点位的质量显示为 Clamp High。这是上层数据通道的类型限制,不是 PLC 或 DAServer 读错了。
解决:看到 Clamp High 就不要再去查 PLC 侧,直接确认上位侧的数据通道类型。要么换成能承载 32 位无符号整数的通道,要么在 PLC 侧把大数值拆成高低两个 16 位分别读,上位侧再拼回去。
5.5 坑五:Poke 模式选错,写入顺序乱了套
现象:批量下发配方参数时,偶尔出现某几个值没写进去,或者最终值对但中间过程错乱,用滑块调值时画面还抖。
原因:Device Group 的 Poke Mode 有三档,默认是 Transition 模式。这档的特点是保留写入顺序,但允许折叠——同一个点位收到多次写入时,只保留第一次、第二次和最后一次,中间的丢掉。对普通设定值没问题,但对有严格顺序要求的控制类写入就会出问题。Control 模式不折叠、严格保序,Full optimization 模式则连顺序都不保、只写最后一次,性能最好但一致性最弱。
解决:控制类客户端用 Control 模式;配方下载这种顺序无所谓、量大优先的场景用 Full optimization;普通画面设定值用默认的 Transition。改完要重新验证一遍写入链路,不能只看最终值对不对,要看中间过程有没有依赖顺序的逻辑。
6.1 三档 Poke 模式与循环服务配额,怎么选才不后悔
把 Device Group 的参数摊开看,真正需要动脑的其实就三处:Poke Mode、Cyclic Services 配额、Peripheral Access。Poke Mode 的三档取舍可以用一张表说清楚。
| 模式 | 写入顺序 | 是否折叠 | 适用场景 | 代价 |
|---|---|---|---|---|
| Control | 严格保序 | 不折叠 | 批次控制、联锁类写入 | 总线流量最大 |
| Transition | 保序 | 保留首、次、末值 | 画面设定值、滑块 | 中间值会丢 |
| Full optimization | 不保序 | 只写最后一次 | 配方下载、大批量写入 | 一致性最弱 |
原文的注释说得很直白:Full optimization 性能最高,但要自己权衡数据一致性对写入顺序和折叠的要求。这句话反过来读就是——性能不是白来的,你放弃的是中间过程的可见性和顺序保证。我在项目里一般把控制类 Device Group 和画面类 Device Group 分开建,前者固定 Control,后者用 Transition,配方下载单独一个 Group 开 Full optimization,互不干扰。混在一个 Group 里,就只能按最严格的那个来,白白牺牲性能。
循环服务配额是第二个取舍点。原文给的规则是:如果知道远端 PLC 能扛多少条循环服务,就在这个 Device Group 里限制用量,把配额分给同连接下的其他 Group;不知道就用最大可用。可配范围 1 到 150,默认 1。这里有个反直觉的地方——默认值 1 意味着开箱状态下几乎等于没开循环服务,大量点位会退化成轮询。在 300/400 上这会让性能明显低于预期,所以拿到工程先检查这个数字。但在 1200/1500 上,因为这两个系列本身不支持循环服务,这个参数怎么调都不会生效,理解这一点能省掉很多无效调试。
原文还列了两种应该主动禁用循环服务的场景:一是某个 Device Group 的更新周期很长,不该占着循环服务这种稀缺资源;二是需要逼着 DAServer 以快于 100 毫秒的节奏去取数。第二种场景值得多说一句,循环服务的更新节奏由 PLC 侧控制,想更快只能轮询,这时候主动关掉循环服务反而是对的做法。
Peripheral Access 这一项相对简单:勾上「Read contiguous IO」会强制 DAServer 读取输入输出块(包括外设区),即使它们的地址空间不连续。什么时候需要勾?PLC 的输入地址或输出地址空间里有空洞的时候。比如组态里配了 0 到 7 字节和 16 到 23 字节两段输入,中间 8 到 15 是空的,按连续读就会撞上空地址,勾上这一项让 DAServer 按实际分布去读,能避开这类空洞导致的读失败。
6.2 怎么验证一套配置真的成立了
验证顺序比验证方法更重要。我的习惯是从下往上逐层确认,每一层都有明确的通过标准,不通过就不往下走。
第一层,协议层。只配一个D<B,I>d,B0这样的单字节点位,确认能读到值且质量是好的。这一层过了,说明 PUT/GET 开了、网络通了、DB 号存在。
第二层,地址层。配一个已知值的点位,比如在 DB 里放一个固定常量,读回来和博途在线监视比对。这一层过了,说明优化块访问关了、偏移算对了。
第三层,类型层。配 LREAL、DTL、字符串各一个,确认解析出来的值和 PLC 里显示的一致。DTL 重点看纳秒位和字节序,字符串重点看长度是不是差一。
第四层,批量层。把完整点表铺开,观察请求数和采集周期。如果请求数远超预期,说明点位在地址空间上太分散,回去重新排布 DB。
第五层,写入层。按业务类型分别验证 Control、Transition、Full optimization 三种模式下的写入行为,特别是中间过程有依赖的逻辑。
这套流程走一遍大概半小时,但能挡掉后面几天的反复排查。我印象最深的一次是给一条产线做数采,前期只测了单点读取,全部正常,就直接铺了三百多个点位上线;结果运行两周后发现某个配方参数偶尔写不进去,查了很久才定位到 Device Group 用的是默认的 Transition 模式,中间值被折叠掉了。从那以后我每次配完 Device Group,都会先把 Poke Mode 和循环服务配额这两项单独过一遍,再让点位表上线,不管项目多小都不省这一步。
这套东西的门道不在语法表有多长,而在几个开关和几个默认值背后的机制——PUT/GET 决定了协议层放不放行,优化块访问决定了地址契约存不存在,Block Items 和循环服务的支持矩阵决定了性能天花板在哪,DTL 和 LREAL 的字节布局决定了类型解析会不会静默出错。把这些串起来,再回头看那份配置说明和语法表,每一条限制都能对上它背后的原因,而不是一堆需要死记的规则。希望帮到你。
本文还有配套的精品资源,点击获取