TIA 博途 SCL AT 覆盖:DWORD 按位与结构体报文映射实战

发布时间:2026/9/17 8:00:33
TIA 博途 SCL AT 覆盖:DWORD 按位与结构体报文映射实战 简介该资源是一份面向西门子 S7-1200/S7-1500 编程人员的技术文档聚焦 TIA 博途中利用关键字「AT」实现变量覆盖这一进阶用法适合已掌握基本 FC/FB 编程、希望深入理解接口参数区数据映射的自动化工程师与调试人员。文档说明了 AT 覆盖在 S7-1200 与 S7-1500 上所需的软件与固件版本条件并给出从新建 FB、设置保持性为「在 IDB 中设置」到逐行声明被覆盖变量与覆盖变量、选择 AT 关键字并声明为 Array[0..15] Bool 的完整流程同时详解 BoolToWordAT 与 WordToBoolAT 两个示例 FB前者把 16 个位组合成 1 个字后者从 1 个字中提取最多 16 个布尔信号并在 OB1 中调用参数化可帮助读者掌握按位访问字变量的通用思路。资源包为单个 docx 文档压缩后约 49KB数量精简但内容集中。目前已有 500 余人学习便于快速查阅与对照实践。1. 一块 DWORD 要同时按位和按字看AT 覆盖省掉的是几百行掩码代码通信里状态字、报警字、控制字清一色是 DWORD 或 WORD。整字读回来容易难的是拿单个位做判断某品牌变频器状态字位 0 就绪、位 3 故障、位 15 方向用掩码写就是一堆AND 16#0008改一次位定义就要回去翻一遍手册。TIA 博途里 SCL 关键字 AT 提供的变量覆盖本质上是给同一段内存挂第二块牌子一个 DWORD 变量旁边再声明一个 BOOL 数组两者共用同一片字节写其中一个另一个立刻跟着变。适合经常处理通信状态字、想把结构体整块映射成字节数组做报文收发、又不想写序列化函数的工程师。代价是所在块必须放弃优化的块访问这一点在下一章重点讲。2. AT 覆盖的原理、生效条件与不该用的场合2.1 AT 覆盖不是赋值编译期就把两个名字绑在同一偏移上StatusBits AT StatusWord : ARRAY[0..31] OF BOOL;这一行里没有任何数据搬运。它的作用发生在编译期编译器把 StatusBits 的起始偏移设成和 StatusWord 完全一致长度取 4 字节然后在符号表里记下两条别名。运行期读StatusBits[3]读的就是 StatusWord 第 3 位所在的那一个字节位。指令层面和直接写StatusWord.%X3一样短没有中间变量也没有类型转换函数调用。正因为它是编译期绑偏移覆盖关系一旦建立两个变量必须能被同一段存储容纳。32 位的 BOOL 数组去覆盖 DWORD 合法40 位的数组去覆盖 DWORD 就会报长度不符。同样的道理覆盖变量的长度不能超过被覆盖变量方向不能反。另一个常被忽略的点覆盖关系不产生新存储。所以在背景 DB 里你看到的还是原来那些字节别名只是从另一个角度切同一块数据。这决定了后面监视和排错的很多现象。2.2 关掉“优化的块访问”是绕不过去的前提TIA 博途默认给新建块勾上“优化的块访问”。这个选项的意义是编译器可以自由调整变量的存储顺序、插入填充对界甚至把没被引用的变量直接优化掉。变量没有固定偏移AT 覆盖自然无从谈起。必须做的操作在项目树里右键该 FB/FC进属性在属性页里取消“优化的块访问”的勾选或者在新建块那一步就不勾。改完建议把块完整编译一次让编译器按新规则重建偏移表而不是只做增量编译。代价要说清楚。关掉优化之后这个块里所有变量都回到按声明顺序、按固定偏移排列跨块按绝对地址访问变得可行但块本身的访问速度略降。更麻烦的是维护性以后在接口区中间插入一个变量后面所有变量的偏移都会改变如果别处存在按绝对地址引用这个块的代码那就一起乱。提示只把真正需要 AT 覆盖的那一个 FB 关掉优化访问就够了不要图省事把整个项目所有块都关掉。2.3 接口区、数据类型和长度这三道约束覆盖变量和被覆盖变量必须位于同一个接口区。Input 覆盖 InputStatic 覆盖 Static跨区覆盖编译器直接拒绝。常见做法是都放进 Static因为输入输出变量通常不希望块外的人改动它的存储结构。支持的类型组合不算多实际能跑通的集中在下面几类被覆盖变量覆盖变量典型用途DWORDARRAY[0..31] OF BOOL状态字逐位判断WORDARRAY[0..15] OF BOOL报警字逐位判断BYTEARRAY[0..7] OF BOOL字节位展开自定义 PLC 数据类型ARRAY[0..N] OF BYTE结构体整块转字节流ARRAY[0..N] OF BYTE自定义 PLC 数据类型收到报文后按字段解析长度必须严格对齐。用 UDT 覆盖字节数组时尤其要留意 UDT 里有没有 BOOL 成员。S7-1200/1500 在非优化块里相邻的多个 BOOL 默认仍然按位打包进同一个字节单个孤立的 BOOL 也占一个字节具体占用要看前后邻居的类型。你在接口区数出来的字节数往往和编译器实际分配的字节数对不上。稳妥写法是让 UDT 里所有成员都用 BYTE、INT、DINT、WORD、DWORD 这类整字节类型需要位操作的地方在读取侧再做。什么时候不该用结构体字段还在频繁变更的场合。每改一次结构体覆盖过来的字节数组长度和字段偏移都跟着变下游解析代码和上位机报文定义全部要同步。这种场景宁可老实写序列化把报文格式固定成一份对外约定也不要用 AT 覆盖图省事。3. 在 SCL 里写出第一个 AT 覆盖块从建块到在线验证3.1 建块时先决定“优化的块访问”新建一个 FB命名FB_StatusWord语言选 SCL。在弹出的属性对话框里取消“优化的块访问”。这一步做在前面比建完块再回头改要稳因为回头改会触发整块重新编译容易顺带暴露出别的地址引用问题。3.2 接口区里 AT 覆盖的声明写法FUNCTION_BLOCK FB_StatusWord VAR StatusWord : DWORD; // 被覆盖的原始状态字 StatusBits AT StatusWord : ARRAY[0..31] OF BOOL; // 同一段内存的按位视图 Ready : BOOL; // 就绪位 0 Fault : BOOL; // 故障位 3 END_VAR BEGIN // 位视图直接参与逻辑判断不需要任何类型转换 #Ready : #StatusBits[0]; #Fault : #StatusBits[3]; END_FUNCTION_BLOCK逻辑说明AT写在别名和原变量中间顺序是“别名 AT 原变量 : 类型”。这里没有任何语句把 StatusWord 搬到 StatusBits 里两者从块下载那一刻起就是同一段内存。位 0 和位 3 的读取编译出来就是把那个字节里的对应位取出来送进 Ready、Fault指令数和直接写位访问一致。参数说明ARRAY[0..31]的下标范围必须刚好覆盖 DWORD 的 32 位多一位少一位都编译不过。原变量必须写在覆盖变量之前写反了会报“变量未定义”。至于索引 0 对到哪一端见 4.2 节。3.3 位序验证用在线修改值把映射关系钉死不要猜位序。下载块之后在监控表里把StatusWord修改为16#00000008也就是只有第 3 位为 1然后观察三个位视图。// 在线修改 StatusWord 16#00000008 之后 // 若 StatusBits[3] 为 TRUE、其余为 FALSE → 索引 0 是最低有效位情形 A // 若 StatusBits[28] 为 TRUE、其余为 FALSE → 索引 0 落在最高有效字节情形 B // 其余任何情况说明数组下标和数值位的换算关系需要按 4.2 节重算逻辑说明16#00000008的二进制只有位 3 是 1。如果位视图符合“索引 0 是最低位”的直觉那么只有StatusBits[3]会亮。如果亮的是StatusBits[28]说明编译器把数组下标 0 对到了最高有效字节的最低位两者的下标差是 25很容易区分。这一步只要做一次把结论以注释形式写在块头后续所有接手的人都不用再猜。养成这个习惯能省掉很多“现场对不上但查不出原因”的扯皮。3.4 结构体覆盖成字节数组做通信报文的收发先定义一个不含 BOOL 的 PLC 数据类型TYPE UDT_MotorData VERSION : 1.0 STRUCT Speed : INT; // 2 字节偏移 0 Current : INT; // 2 字节偏移 2 Torque : INT; // 2 字节偏移 4 AlarmCode : WORD; // 2 字节偏移 6 END_STRUCT; END_TYPE然后在 FB 里做整块映射FUNCTION_BLOCK FB_PackMotorData VAR MotorData : UDT_MotorData; // 8 字节结构体 TxBuffer AT MotorData : ARRAY[0..7] OF BYTE;// 同一段内存的字节视图 END_VAR BEGIN #MotorData.Speed : 1500; // 单位 rpm #MotorData.Current : 32; // 单位 0.1A #MotorData.Torque : 88; // 单位 0.1Nm #MotorData.AlarmCode : 16#0000; // 到这里 TxBuffer[0..7] 已经同步变化可直接整块送入发送逻辑 // 例如逐字节写入通信模块的发送寄存器或交给 BLKMOV END_FUNCTION_BLOCK逻辑说明UDT 里四个成员都是整字节类型总长 8 字节所以字节数组开到ARRAY[0..7]。非优化块里结构体成员按声明顺序连续排列没有填充字节偏移分别是 Speed0、Current2、Torque4、AlarmCode6。往结构体里写值之后TxBuffer[0..7]立刻就是完整的 8 字节报文帧不需要逐字段拼装。参数说明如果 UDT 里混进了 BOOL长度就不再等于成员字节数之和这一步会直接卡在编译期。解决办法是在 UDT 里把 BOOL 全部换成 BYTE再在读取侧按位操作。另一种办法是把位定义单独拆成另一个 UDT用位视图覆盖不要混在报文结构里。3.5 覆盖变量的长度对照原变量类型字节长度可覆盖的取值范围BYTE1ARRAY[0..7] OF BOOLWORD2ARRAY[0..15] OF BOOLDWORD4ARRAY[0..31] OF BOOL8 字节 UDT8ARRAY[0..7] OF BYTE与数组等长的 UDTNARRAY[0..N-1] OF BYTE这张表里唯一需要自己算的是最后一行。数组长度必须是原变量字节数减 1写成ARRAY[0..N] OF BYTE覆盖 N1 字节的变量是常见笔误编译期会直接拦下来。4. AT 覆盖的编译报错对照与位序、监视排错4.1 编译期报错怎么对着改AT 覆盖的报错信息都比较直白按关键字对号入座就行。报错关键字原因处理办法块中不允许变量覆盖块还是优化访问取消“优化的块访问”完整编译覆盖的数据类型不允许类型组合不在支持范围换成 BYTE/WORD/DWORD 与 BOOL 数组的组合长度不匹配覆盖变量超出原变量字节数调整数组上下界变量未定义原变量声明在覆盖变量之后把原变量挪到前面接口区不允许覆盖变量跨了接口区两者放进同一个接口区第一条和第五条最容易踩。第一条的现象是代码看起来完全正确但一编译就报“不允许变量覆盖”很多人会去反复检查语法其实只要点开块属性看一眼勾选状态就行。第五条则是想在 Static 里覆盖一个 Input 变量编译器不会帮你自动搬存储必须自己拆开。4.2 字节序和位序索引 0 到底落在哪一位S7 的存储是高字节在前。一个16#12345678的 DWORD 按字节展开是12 34 56 78数值最低位在最后一个字节。AT 覆盖到 BOOL 数组之后数组下标 0 落在哪一位取决于编译器的映射约定。所以不要凭直觉写代码用 3.3 节的方法测一次然后按测出来的结论固化// 情形 A索引 0 数值最低有效位多数版本如此 #Fault : #StatusBits[3]; // 情形 B索引 0 最高有效字节的最低位 #Fault : #StatusBits[28];逻辑说明写这两行的目的是把“物理第 3 位”翻译成“数组的哪个元素”。两种情形的下标差 25好识别。确定答案之后别让下标散落在各处。做法是在接口区再加一层符号常量或者干脆封一个 FC 出来FUNCTION FC_GetStatusBit : BOOL VAR_INPUT Word : DWORD; BitNo : INT; // 物理位号0 起 END_VAR VAR_TEMP Bits AT Word : ARRAY[0..31] OF BOOL; END_VAR BEGIN FC_GetStatusBit : #Bits[#BitNo]; END_FUNCTION这样做的好处是如果以后换平台或者换版本导致映射方向变了只需要改这一处。注意 VAR_TEMP 里的变量也可用于 AT 覆盖但 Temp 是临时区每次调用时内容不保证保留不适合做长期记忆用途的状态字。4.3 在线监视时看不到覆盖变量的几个原因下载之后如果监控表里只有原变量显示出来、别名那一行是灰的或者压根没出现先按下面三条排查。第一刷新机制。TIA 的“监视全部”对非优化块是按绝对地址刷新的覆盖变量和原变量共享地址有时只刷新其中一个。逐个点开变量的监视小图标或者单独建一张监控表把两个变量都拖进去。第二版本没下载。块虽然已经改成非优化了但变更没有下载到 PLC设备里跑的仍是旧版本界面上看到的和实际运行的对不上。先完整编译再下载再看。第三路径不对。FB 的 Static 变量真正存在对应的背景 DB 里监视时要从背景 DB 的路径进去找覆盖变量而不是在 FB 的接口界面里找。这一点在排查“为什么原变量能改、别名不跟着变”时特别关键。5. 把 AT 覆盖接到块传送指令上一个整帧打包与校验的写法5.1 结构体和字节视图并存配合 BLKMOV 整块发出前面 3.4 节的结构体映射单独用只能省掉拼装代码。真正好用的写法是把字节视图接给块传送指令一次把多个周期数据推出去。FUNCTION_BLOCK FB_FrameOut VAR Frame : UDT_MotorData; // 8 字节 FrameBytes AT Frame : ARRAY[0..7] OF BYTE; Crc : BYTE; i : INT; END_VAR BEGIN // 先把字段填好 #Frame.Speed : 1500; #Frame.Current : 32; // 在数据区末尾追加校验示意逐字节异或 #Crc : 16#00; FOR #i : 0 TO 5 DO #Crc : #Crc XOR #FrameBytes[#i]; END_FOR; #Frame.AlarmCode : 16#0000 OR #Crc; // 此时 FrameBytes[0..7] 已是完整帧可整块送入发送 END_FUNCTION_BLOCK逻辑说明这里把结构体的前三个字段当成数据区用字节视图逐字节算出校验再塞回最后一个字段。整段代码只操作字节视图和结构体视图两套名字没有一次手动移位和拼字节。校验值放在结构体的最后一个成员里字节视图自动同步不需要额外的拷贝步骤。参数说明FOR #i : 0 TO 5的边界要和“数据区覆盖哪些字段”一致。这道边界写错了不会报编译错误只会得到一个校验永远对不上的报文而且现场很难查。建议把数据区边界也定义成常量别写魔数。5.2 用一个好认的值验证字节序和字段偏移打包逻辑写完之后先别急着接通信。给字段设一个好识别的值然后在线看字节视图检查项设置值期望字节视图Speed16#1234FrameBytes[0]16#12FrameBytes[1]16#34Current16#5678FrameBytes[2]16#56FrameBytes[3]16#78逻辑说明16#1234展成两个字节高位在前所以FrameBytes[0]是16#12。如果在线看到的是FrameBytes[0]16#34说明你的字节视图方向和预期相反此时要么调整上位机侧的解析要么在发送前做一次字节交换别两边都改。验证完之后还有一步值得做在块的属性里确认“优化的块访问”确实没被谁重新勾上。整个项目里其他块可以保持优化访问唯独这个块不行。这一条建议写进项目的编码约定文档比事后逐个块点开检查要省事得多。本文还有配套的精品资源点击获取