简介:面向西门子 S7-1200/S7-1500 自动化工程师的文档型资料包,聚焦 TIA 博途软件中利用关键字 AT 实现变量覆盖的具体方法,适用于控制字位拆分/合并及接口区变量复用的场景。文档按完整操作流程介绍了 AT 覆盖的前提条件、FB 块标准访问与保持性设置、接口区声明技巧,并配有 Bool 数组与 Word 变量互转的说明。包内共 1 个文件,为 docx 格式,整体大小仅 49KB,便于在 STEP 7 环境下对照查阅。资源现有 502 人学习浏览,适合初次接触变量覆盖或希望优化 FB/FC 接口写法的工程师参考。内容还包含 BoolToWordAT、WordToBoolAT 两个示例 FB 块的使用思路,演示如何将多个位信号组合成控制字或从控制字中提取信号,具备较强现场参考价值。
1. 报文解析凌晨两点翻车之后,我决定把 AT 变量覆盖彻底用熟
做现场设备通讯的人大多经历过这种场面:设备侧按协议发来 32 字节报文,你要从里面取出状态字、4 个通道值、CRC 和厂商自定义区。最直接的写法是用偏移量一个个取字节,再做字的高低位拼装;协议一改,偏移量全线漂移,注释写得不到位,第二天自己都看不懂。TIA 博途里的 AT 关键字解决的就是这件事:它允许你在同一块 DB 存储区上声明两个名字,一个按字节数组解释,一个按结构体解释,读写任何一边,另一边同步可见。这就是标题里“变量覆盖”的本质——不是拷贝,不是转换,而是给同一段内存换个类型视图。
AT 覆盖最典型的收益是让报文解析代码从“移位指令 + 偏移量注释”变成“直接访问结构体成员”。通讯缓冲区在 DB 里声明成Array[0..31] of Byte,再覆盖一个 UDT 类型变量,状态字就是Msg.Status,通道值就是Msg.Channels[0],校验和运算时又切回字节视图逐字节累加。代码量少一半不止,协议字段增减也只改 UDT 定义。这篇文章不讲空理论,直接说清楚 AT 的存储逻辑、声明步骤、能抄的示例程序,以及我用下来遇到的那几个让新手反复翻车的坑。
2. AT 的存储对齐逻辑:为什么一块地址能有两个名字,哪些场景值得用它
2.1 覆盖的本质是重解释,不是值拷贝
AT 变量在 TIA 博途里的定义方式是:先声明一个源变量(比如RawData : DWord),再声明一个目标变量并指定它的存储位置覆盖在源变量上。编译后这两个名字指向同一个起始地址,长度由各自的数据类型决定。源变量叫RawData,覆盖变量叫BytesView AT RawData : Array[0..3] of Byte,那你往RawData写入16#12345678,BytesView[0]到BytesView[3]立刻能读到对应的 4 个字节;反过来改BytesView[1],RawData的对应字节也跟着变。
这带来一个关键结论:AT 没有方向性。很多人第一次用会以为“AT 变量是源变量的影子,只能读”,这是错的。它和源变量是同一块内存的两种解释方式,读写完全双向。真正的限制在于,AT 变量不允许带初始值,因为存储区的归属权在源变量手里;源变量赋了什么初值,AT 视图打开就是什么。
另外要区分 AT 和指令层的转换。TIA 里有ROUND、MOVE、WORD_TO_BYTE这些转换指令,它们是运行时的值拷贝,消耗扫描周期,而且类型不匹配时要写一堆中间变量。AT 是编译期的地址重解释,不生成任何运行时代码,也不占 PLC 资源。代价是需要一块明确的、非优化的 DB 存储区,不能对逻辑块接口区的局部变量做覆盖。
2.2 高频适用场景:协议解析、模拟量打包、与 UDT 互访
从实际项目来看,AT 真正值得用、用了能省大功夫的场景有三类。
第一类是通讯报文解析和组包。这是最典型的用法。接收缓冲区在 DB 里是Array[0..N] of Byte,协议结构是一个 UDT,两边用 AT 连起来。解析时直接读结构体成员,发数据时给成员赋值再整包交给通讯指令,中间不再需要任何手动偏移。
第二类是模拟量通道的字节拆分。比如第三方设备传过来一个 DWord,里面打包了 4 个通道的 0-255 原始值,你要分别做工程量变换。用 AT 覆盖成Array[0..3] of Byte,每个通道就是数组的一个元素,配合 FOR 循环处理,比手写四次移位和取模干净得多。
第三类是不同类型视图的互访。比如一段存储区,既要当Word数组做数值运算,又要当Byte数组做校验和累加,还需要按 UDT 做语义解析。一个源变量,两个 AT 视图,三份名字共存于同一片地址,谁都不影响谁。
2.3 什么时候不要用 AT:复杂类型与跨系统边界
AT 不是万能的。STRING、WSTRING 这类变长类型不能作为 AT 目标类型,因为长度不确定,编译器无法算出覆盖边界。同样,AT 变量不能再被别的 AT 变量覆盖,想多层视图就改成 UDT 套 UDT,在 UDT 内部组合基本类型。还有一点,AT 变量不能在函数块接口区声明,它只能出现在全局 DB 或背景 DB 的变量表里;函数内部的临时需求,还是得用传统指令。
我曾经在一个项目里试图把 PROFINET 从站设备返回的数据区直接 AT 到 UDT 上,发现通信驱动对缓冲区有对齐要求,UDP 接收区起始地址必须是偶数偏移,而 UDT 里恰好有个 BOOL 成员,导致整个结构体的字节对齐全乱。最后做法是在 DB 里单独开一段字节数组作为中转,再用 AT 覆盖 UDT,彻底绕开对齐问题。这个经验后面在避坑章节还会细说。
| 场景 | 是否推荐 AT | 推荐理由 / 不推荐原因 |
|---|---|---|
| 报文接收缓冲区 → 协议 UDT | 强烈推荐 | 省掉全部手动偏移,协议改动只改 UDT |
| DWord / Word 拆分成字节 | 推荐 | 配合数组和 FOR 循环,代码可读性高 |
| 同段数据既要数值计算又要校验和 | 推荐 | 多个 AT 视图并存,互不干扰 |
| STRING 类型覆盖 | 不推荐 | 变长类型无法确定覆盖边界 |
| 函数块局部变量覆盖 | 不推荐 | TIA 不支持,编译直接报错 |
| 跨 PLC 或跨通信协议的内存映射 | 不推荐 | 对齐和字节序不可控,风险大 |
3. 在 DB 里声明 AT 覆盖变量:两种声明路径与一个可抄的报文解析示例
3.1 图形化声明:右键添加 AT 变量
动手之前先记住一个前提:目标 DB 必须是“非优化访问”。新建 DB 时默认是优化访问,右键 DB 属性,在“属性 → 属性 → 寻址”里把“优化的块访问”取消勾选。这是 AT 能用的先决条件,后面避坑章节会专门讲。
第一步,在 DB 变量表里先声明源变量。比如做一个 12 字节的接收缓冲区,名称填Buffer,数据类型填Array[0..11] of Byte。第二步,选中Buffer这一行,右键选择“添加 AT 变量”。TIA 会自动在下面生成一行新变量,这行的起始地址会被锁定为源变量的地址。第三步,在新行的“数据类型”列填写你要的覆盖类型。如果已经建好了协议 UDT,直接输 UDT 名称,注意名称要用引号括起来。
我这里用一个实例说明。假设协议结构是:2 字节状态字、4 个 INT 通道值(每个占 2 字节)、2 字节 CRC,总共 12 字节。先在 PLC 数据类型里建一个 UDT,名字定为"UDT_DeviceData":
TYPE "UDT_DeviceData" VERSION : 0.1 STRUCT Status : Word; // 2 字节:设备运行状态 Channels : Array[0..3] of Int; // 8 字节:4 个通道的原始值 CRC : Word; // 2 字节:校验和 END_STRUCT END_TYPE这个 UDT 定义了一个 12 字节的固定结构,成员顺序、类型长度都和通讯协议逐一对应。Word占 2 字节,Int在 S7-1200/1500 里占 2 字节,数组 4 个就是 8 字节,最后CRC再占 2 字节,总长正好和Buffer数组一致。
回到 DB 里继续。右键Buffer添加 AT 变量,名称填DevData,数据类型列输入"UDT_DeviceData"。保存编译后,打开“监视”视图,展开Buffer和DevData,你会看到两个变量占用的起始字节地址完全相同。到此声明完成,一句代码都没写,地址映射已经生效。
3.2 手写声明:直接在数据类型列输入 AT 表达式
如果你用的是比较新的 TIA 版本,还有一种更快的写法:在 DB 变量表的新行里,名称留空或直接填目标变量名,数据类型列手敲AT "Buffer" : "UDT_DeviceData"。注意AT和源变量名之间要有空格,源变量名用引号包住,冒号后面才是目标类型。敲完回车,TIA 会自动解析并把这个变量挂到Buffer的地址上。
这种方式适合对语法已经熟悉的场景,但有一件事必须遵守:AT 变量的声明行必须排在源变量之后。TIA 编译器按变量声明顺序分配地址,AT 引用的是“前面已经声明好的变量”;你把 AT 行放在源变量之前,编译直接报“未知变量”或“循环引用”。所以图形化右键添加是最稳的,它自动帮你把位置排对了,手写时务必自己注意行位置。
声明完成后的 DB 结构大概长这样:
| 名称 | 数据类型 | 偏移地址 | 注释 |
|---|---|---|---|
| Buffer | Array[0..11] of Byte | 0.0 | 通讯接收缓冲区 |
| DevData | "UDT_DeviceData" | 0.0 | AT 覆盖:协议结构视图 |
偏移地址是 0.0 开始,说明DevData完全覆盖在Buffer上。这个偏移由编译器自动计算,不需要手动填。
3.3 示例程序:用两个视图完成报文解析与校验
声明做完,业务代码写起来就很直观了。新建一个 FC,比如叫"FC_ParseTelegram",在 OB1 里调用。FC 内部不需要任何偏移计算,也不需要WORD_TO_BYTE之类的转换指令,直接访问"DB_Com".DevData就是结构化数据,访问"DB_Com".Buffer就是原始字节。
// 解析接收报文:读取状态字和 4 个通道值,并做累加校验 // 数据来源:通信驱动已将接收报文写入 "DB_Com".Buffer VAR i : Int; // 循环下标 CalcCRC : Word; // 按字节累加的校验结果 ChValue : Int; // 通道工程量转换后的临时值 StatusWord : Word; // 状态字 END_VAR // 直接从结构视图读取成员,无需计算偏移 StatusWord := "DB_Com".DevData.Status; IF StatusWord = 16#1000 THEN // 设备正常,逐通道做工程量转换 FOR i := 0 TO 3 DO ChValue := "DB_Com".DevData.Channels[i]; // 代入标定公式,比如 0-27648 换算成 0-100% // ChValue := ChValue * 100 / 27648; END_FOR; END_IF; // 校验和计算:切回字节视图逐字节累加 // 协议规定:前 10 个字节累加,结果等于最后 2 字节 CRC CalcCRC := 0; FOR i := 0 TO 9 DO CalcCRC := CalcCRC + "DB_Com".Buffer[i]; END_FOR; IF CalcCRC <> "DB_Com".DevData.CRC THEN // 校验失败,置一位故障标志 // "DB_Com".CommError := TRUE; END_IF;这段代码的逻辑说明:读StatusWord直接用结构体成员,Channels[i]是数组元素访问,完全符合 SCL 的常规语法;校验和那段切回Buffer视图,初衷是规避 UDT 里Int的字节序问题,逐字节累加时只关心每个字节的数值,不关心高低位排列。
参数说明:CalcCRC用Word类型,因为 12 个字节累加的最大值 12*255=3060,仍在 Word 范围内;协议里的 CRC 如果按“累加和”设计,直接用这个值比较就可以。如果协议要求的是 CRC16 多项式校验,那就不适用这个累加算法,需要在 FC 里用查表法或位运算实现,AT 本身不解决校验算法问题。
最后一个值得留意的点:"DB_Com".DevData.CRC和"DB_Com".Buffer[10]、Buffer[11]是同一片地址的三种访问方式。你在监控表里修改Buffer[10],DevData.CRC的高字节或低字节会跟着变;反过来写DevData.CRC,也会看到Buffer里对应两个字节变化。这种双向可见性,正是 AT 和普通拷贝指令最大的区别,写业务代码时不用再考虑“同步”问题。
4. AT 变量使用中的五个高频坑:现象、原因、解决
4.1 优化访问的 DB 里声明 AT,编译直接报错
现象:你在新建的默认 DB 里加 AT 变量,TIA 报“对象当前不支持该操作”或类似的访问模式错误。
原因:S7-1200/1500 的 DB 默认使用优化的块访问,变量只有符号名、没有固定的物理偏移地址。AT 要求在编译期把一个变量的地址硬绑定到另一个变量上,优化访问模式下地址是由编译器动态规划的,不支持这种硬绑定。这是 TIA 为 S7-1200/1500 引入的机制,跟 S7-300/400 时代完全不同。
解决:选中 DB,打开属性,在“属性 → 属性 → 寻址”页面取消勾选“优化的块访问”。改完保存编译一次,AT 声明就能用了。注意:切换访问模式后,这个 DB 里所有变量的地址会变成可见的显式偏移地址,如果之前已经下载运行过,下载时可能触发 DB 重新初始化。现场操作前先确认是否允许数据丢失,或者提前把初始值备份出来。
4.2 给 AT 变量填初始值,编译报“AT 变量不允许使用起始值”
现象:你在 DB 里给DevData的某个成员填了起始值,编译报错,说 AT 变量不允许有启动值。
原因:AT 变量自己不拥有存储区,它只是源变量存储区的第二个名字。如果允许 AT 变量写初始值,等于有两个变量同时对同一段地址声明不同的上电初值,编译器无法裁决。
解决:初始值统一写在源变量上。以 3.3 节的例子来说,Buffer数组的每个字节填上你希望的初始值,DevData视图上电后看到的就是这些字节。如果初始值要在程序里动态计算,常见做法是在启动组织块 OB100 里写一段初始化逻辑,对源变量整体赋值。我习惯把这类初始化放到一个单独的 FC 里,避免 OB100 越来越臃肿。
4.3 源变量是 STRING 或 UDT 里带 STRING,AT 编译报边界错误
现象:想把一段String变量覆盖成字节数组,或者 UDT 成员里有String,编译报“数据类型不允许”或“无法确定长度”。
原因:String是变长类型,标准结构是 4 字节头 + 实际字符区,最大长度由声明决定。AT 需要在编译期确定覆盖区域的精确边界,变长类型无法静态计算。
解决:不用String参与覆盖。通讯协议里的字符串字段,在 UDT 里用固定长度的Array[0..N] of Char或Array[0..N] of Byte表示,需要转成String时在程序里用CONCAT、Mid之类的指令处理。字符集和补零规则尽量在注释里写清楚,避免下一任维护者误解。固定长度数组的边界是确定的,AT 可以正常覆盖。
4.4 AT 声明行放在源变量之前,报“未知变量”或“循环引用”
现象:手写AT "Buffer" : "UDT_DeviceData"时报错,提示找不到源变量,或者提示存在循环依赖。
原因:TIA 编译器按 DB 变量表的从上到下顺序分配地址。AT 变量必须引用一个已经完成声明的变量,引用后面的变量等于在编译时向前引用,这在当前声明机制里不做支持。你有没有试过把引用反过来写?它连编译都过不了。
解决:调整声明顺序,源变量必须在 AT 变量之前。右键“添加 AT 变量”的路径不会出这个问题,因为 TIA 会自动把新行插到源变量紧下方。手写声明时,把 AT 行的位置提到源变量之后即可。另外,源变量不能也是 AT 变量,不能对 AT 变量再做覆盖,这个限制同样属于声明顺序问题的一种极端形态。
4.5 字节序没确认就写死数组下标,拆出来的通道值和仪表显示对不上
现象:设备显示通道值是 1500,你从Channels[0]读到的是 0x05DC 被拆开后的两个字节,但组合出来的数值却是 0xDC05,也就是 56325,差得离谱。
原因:S7-1200/1500 的数据存储是多字节大端排列,高字节在前、低字节在后。如果你的 UDT 里用Int直接映射协议里的整数,TIA 的Int解释方式和设备侧的字节序可能一致,也可能恰好相反。这是通讯协议最常见的翻车点,跟 AT 本身没关系,但 AT 把“字节视图”和“结构视图”放在一起用,把这层差异暴露得更直接了。
解决:拿到协议文档先确认字节序,再决定 UDT 的数据类型。协议里明确“高字节在前”就用Int/Word;明确“低字节在前”有两种处理方式:一是把 UDT 成员改成字节数组,手动做高低位拼装;二是在接收后对整个缓冲区或单个字做字节交换。更省事的做法是在 DB 里多加一个 AT 字节数组视图,先用数组把接收到的原始字节列出来,跟设备侧仪表逐个核对,确认无误后,再写正式解析代码。
5. 把 AT 封装成可复用模板:三视图 DB、通用校验函数与验证方法
5.1 一个 DB 里放三种视图,业务函数只管结构体
用 AT 用久了,我发现最顺手的工程模板是“一个源缓冲区 + 两个 AT 视图”的三视图结构。源缓冲区是Array[0..N-1] of Byte,这是通讯驱动直接读写的区域;第一个 AT 视图是协议 UDT,供业务代码读写语义字段;第二个 AT 视图还是字节数组,或者按需要覆盖成Word数组,专门做校验和、打印、报文追踪这类逐字节操作。
以 12 字节报文为例,DB 里可以这样组织:Buffer : Array[0..11] of Byte作为源变量,DevData AT Buffer : "UDT_DeviceData"作为结构视图,TraceView AT Buffer : Array[0..11] of Byte作为追踪视图。注意后面两个 AT 都挂在同一个Buffer上,三者在物理上完全重叠。业务 FC 只接收UDT_DeviceData类型的参数,不直接碰 DB,这样函数可以做到协议无关,换协议时只改 DB 和 UDT,函数内部不动。
通用校验函数这样设计:
// 通用累加校验:逐字节累加 ByteView,结果与结构视图 CRC 比较 // 参数:无,直接访问模板 DB VAR i : Int; Sum : DWord; // 累加结果用 DWord,避免溢出 END_VAR Sum := 0; FOR i := 0 TO 9 DO Sum := Sum + DWORD_TO_DINT("DB_Template".TraceView[i]); END_FOR; IF Sum MOD 65536 <> "DB_Template".DevData.CRC THEN // 巡检报警 END_IF;这里的参数说明:TraceView是字节追踪视图,逐字节取值时不用担心Int的字节序问题;Sum用DWord是为了覆盖 12 字节累加的最大值,最后用MOD 65536和 2 字节 CRC 对齐。如果你的协议 CRC 算法不同,只需要替换这一段算法逻辑,AT 视图结构完全不需要动。
5.2 验证 AT 覆盖是否生效:一套不做字节序假设的检查方法
AT 声明完,很多人在下载后就急着写业务代码,结果被字节序坑了一次才回头验证。我习惯在第一次下载后做一次不依赖字节序假设的验证,用 OB100 加一段临时自检代码:
// 自检逻辑:向源变量写入已知值,读回所有视图并比较 // 若比较结果不符合预期,将故障码写到一个保持变量 "DB_Template".Buffer[0] := 16#5A; "DB_Template".Buffer[1] := 16#A5; // 此时 DevData.Status 对应的两个字节应变更为 5A A5 // 下一行只是读取,不做运算,配合监控表观察 #ReadBack := "DB_Template".DevData.Status; // 反过来,改结构视图,观察字节视图变化 "DB_Template".DevData.Status := 16#1234; // 监控 TraceView[0] 和 TraceView[1] 是否为 12 34这段代码的逻辑说明:先写Buffer的两个已知字节,再去监控表看DevData.Status读出来的值;如果看到Status = 16#5AA5,说明 TIA 的Word解释是高字节在前,和写入顺序一致。随后反过来写结构视图,再看字节视图,验证双向覆盖。这个过程不预设任何字节序正确,只看实际观察结果,然后把这个结果记在 DB 的注释里,作为后续解析的字节序依据。
验证通过之后,把临时自检代码删掉或用一个开关变量关掉,然后才进入业务功能测试。毕竟自检逻辑每次上电都跑一遍没有意义,还会覆盖通讯数据;实际运行中它不应该持续存在。这个习惯帮我避开过好几次“看着 AT 没生效,其实是字节序和协议文档不一致”的乌龙。
5.3 模板化之后,我踩过的最后一个细节:下载时 DB 被重新初始化
模板化意味着 DB 里变量数量变多,下载时稍不注意就会触发“DB 重新初始化”对话框。AT 变量本身没有初始值,但源变量和其他普通变量有。在 TIA 里修改 DB 结构后下载,默认行为可能用当前程序里的初始值覆盖实际值,这在现场意味着通讯缓冲区清零、设备状态字丢历史值。
我的习惯是:下载前对比一下“块快照”,把需要保留的变量勾选为“保持”,或者下载时选择“仅下载更改内容”并关闭“覆盖快照”。调试阶段无所谓,但设备已经投产后再动 DB,务必先导出备份。还有一点,AT 视图里所有成员的地址都是跟随源变量的,结构变更后重新编译,先看偏移表再下载,别一看编译通过就急着点下载按钮。
总结下来,AT 这个关键字本身不复杂,真正的难度在于字节序、访问模式、声明顺序这些外围约束。把这三视图模板和双向验证方法固化到自己的标准做法里,之后再做任何通讯协议对接,都可以把解析代码压缩到最小,把精力留给校验算法和故障处理逻辑。这也是我后来每次看到有人在协议解析里写偏移量注释时,都会劝他试试 AT 的原因。希望这篇实战笔记帮到你,少熬几个凌晨两点的夜。
本文还有配套的精品资源,点击获取