简介:这份文档面向工业自动化工程师与TIA博途初学者,聚焦S7-1200与上位机通信时浮点型数据高低字、高低字节顺序相反导致读数异常的问题,系统整理了4种可直接落地的调换方案,技能层级覆盖入门到进阶。压缩包内含1个docx文件,约747KB,以图文并茂的教程文档形式组织,逐条讲解间接寻址与片段访问、SWAP指令、AT变量覆盖、循环左移16位(字节交换则左移8位)四种实现路径,并给出FC块接口定义、SCL程序写法及OB1调用连接数据块变量的完整示例,inputData赋值为16#11112222时输出16#22221111的对照结果便于快速验证。文档同时提示监控调试中正确设置变量显示格式查看浮点数据的技巧,读者可据此按项目需求与代码可读性选型,直接复用示例完成数据字节序转换,减少通信调试排错时间。已有2308人学习参考。
1. 大小端错位那次排查:S7-1200 的 REAL 为什么读成天文数字
上位机画面上的转速显示成 4.7e-38,PLC 在线监视里同一个 REAL 变量却明明白白写着 1450.0。这种错位十有八九不是通信断了,而是字节序对不上:S7-1200 按大端存放数据,一个 REAL 占 4 字节,最高有效字节落在最低地址;不少 PC 端上位机、称重仪表和视觉控制器按小端解释,读到 16#11112222 会当成 16#22221111。数据本身没错,错的是读它的顺序。下面把 TIA 博途里能做高低字或高低字节调换的四条路子拆开讲——SCL 片段访问、SWAP 指令、AT 变量覆盖、循环移位,每条都落到块接口怎么定义、程序怎么写、OB1 里怎么连管脚,以及哪条路在什么条件下不该走。
2. 片段访问与间接寻址:在 SCL 里给 DWORD 切片
2.1 先把 REAL 的 4 个字节摆清楚
REAL 和 DWORD 在存储层面完全等价,都是 4 字节位串,区别只在解释方式。S7-1200 是大端机器:一个 DWORD 的最低地址 B0 放的是最高有效字节,B3 放最低有效字节;W0 是高字,W1 是低字。上位机小端读的时候会把 B0 当成最低字节,于是整个 4 字节的顺序反过来。
所以“调换”这件事其实分两档——只换两个字的顺序,或者把 4 个字节整体反转。这两档在很多设备上给出不同结果,后面选方法全靠这个区别。先把 16#12345678 在两种处理下的分布列出来对照。
| 字节偏移 | %B0% | %B1% | %B2% | %B3% |
|---|---|---|---|---|
| 原始值 16#12345678 | 12 | 34 | 56 | 78 |
| 只换高低字 | 56 | 78 | 12 | 34 |
| 4 字节整体反转 | 78 | 56 | 34 | 12 |
两行结果不一样,这点在调试现场最容易翻车:设备明明只要换字,你给了整体反转,数值照样是乱的,而且乱得毫无规律。
2.2 用片段访问写一个高低字互换的 FC
在 TIA 博途里新建一个 FC,语言选 SCL,接口区按下面这张表声明变量。
| 区域 | 名称 | 数据类型 |
|---|---|---|
| Input | inVal | DWord |
| Output | outVal | DWord |
程序本体只有两行赋值:
// FC:SwapHiLoWord_Slice // 作用:把输入 DWORD 的高字与低字互换后输出 // 前提:该 FC 未勾选"优化的块访问" // 输入的高字(偏移 0 起的字)写到输出的低字位置 #outVal.%W1% := #inVal.%W0%; // 输入的低字写到输出的高字位置 #outVal.%W0% := #inVal.%W1%;逻辑说明:%W0%表示从字节偏移 0 开始的 16 位,%W1%是从偏移 2 开始的 16 位,两者互为镜像。输入 16#12345678 时,W0 是 16#1234,W1 是 16#5678,两行执行完输出就是 16#56781234。
参数说明:这个写法本身没有可调参数,字和字节的选取完全由%Wn%、%Bn%的下标决定。把下标换成字节级就是字节操作,比如#outVal.%B3% := #inVal.%B0%是把最高字节搬到最低字节位置上。
提示:如果目标是要写回原变量做就地互换,先把源数据读进一个 TEMP 变量再写,否则第一行赋值就把第二行要读的内容覆盖了,结果会退化成两个字节相同。
2.3 片段访问跑不起来时先查这三项
片段访问报错的原因相当集中,按下面的顺序排查基本能定位。
- 块有没有启用优化访问。FC/FB 属性里的“优化的块访问”必须取消勾选,用到的 DB 同样要关,否则编译直接提示不支持部分访问。
- 数据类型是不是定长类型。DWORD、WORD、BYTE、INT、DINT 这类可以切片,STRING、Variant、数组整体不行。
- 固件和软件版本。S7-1200 从 V4.0 起才支持片段访问,老固件上这段程序连编译都过不去。
注意:把 FC 的形参声明成 DWord、而 DB 里的变量声明成 Real 时,实参类型对不上是编译报错的高频原因。要么两边统一成 DWord,监视时切变量格式看浮点;要么在 DB 里给 Real 变量用 AT 覆盖出一个 DWord 别名,具体做法在第 3 章展开。
2.4 OB1 里的调用与监视格式切换
在 OB1 里从空框拖入刚才的 FC,管脚 IN 连数据块变量 inputData,OUT 连 outputData,下载后就能在线看结果。项目正文给的验证值 inputData = 16#11112222 出来 16#22221111,成立。但要注意这个测试值两个字节内部是重复的,区分不出“换字”和“整体反转”,第 5 章会专门说验证数据怎么挑。
看结果的时候,右键变量选“变量格式”,可以在 Hex、Real、Binary 之间来回切。变量格式只改变解释方式,不改变存储内容,所以把一个 DWord 声明按 Real 显示,是最省事的调试姿势——省掉一次类型转换的代码。
3. SWAP 指令与 AT 覆盖:两条贴着存储结构的路线
3.1 SWAP 指令的适用面比名字窄
S7-1200 指令树里“基本指令—移动操作”下面有一个 SWAP,入参和出参都是 WORD,功能是把一个 16 位字的高字节和低字节对调。它不认 DWORD,也不认 REAL。拿它处理一个 REAL 时,实际动作是拆成两个字各做一次字节交换,再把结果按反序拼回去,最终得到的是 4 字节整体反转,而不是单纯的高低字调换。
| 输入 | 两次 SWAP 后 | 只换高低字后 |
|---|---|---|
| 16#11112222 | 16#22221111 | 16#22221111 |
| 16#12345678 | 16#78563412 | 16#56781234 |
第一行两种做法结果相同,纯属测试数据凑巧对称。如果只拿这个值验证就把“字节反转”当成“字调换”上线,真实数据进来照样是错的。
// FC:ByteReverse_SWAP // 作用:把输入 DWORD 的 4 个字节整体反转后输出 // 输入 inVal : DWord 输出 outVal : DWord // 临时变量 wHi, wLo : Word // 高字内部字节对调 SWAP(IN := #inVal.%W0%, OUT => #wHi); // 低字内部字节对调 SWAP(IN := #inVal.%W1%, OUT => #wLo); // 交换两个字的位置后写回 #outVal.%W0% := #wLo; #outVal.%W1% := #wHi;逻辑说明:前两行分别处理原高字和原低字,把每个字内部的字节顺序翻过来;后两行把两个字的位置对调,叠加起来才是完整的 4 字节反转。输入 16#12345678 依次得到 3412、7856,最终输出 16#78563412。
参数说明:SWAP 没有可调参数,行为和数据类型强绑定——给它 WORD 就是字节互换,给它 BYTE 没有意义,编辑器会直接报类型错误。
提示:SCL 编辑器不接受 SWAP 过程调用形式时,可以改用 LAD/FBD 里拖指令框,或者直接换成第 4 章的移位写法,结果完全一致。
3.2 AT 覆盖:给同一段存储起两个名字
AT 覆盖的本质是让编译器把一个变量名映射到另一个变量占用的地址上,不额外占内存,也不做数据搬运。用它把 DWord 看成 Array[0..1] of Word,高低字自然就是数组的两个元素。
| 区域 | 名称 | 数据类型 |
|---|---|---|
| Input | inVal | DWord |
| Output | outVal | DWord |
| Temp | tmpDw | DWord |
| Temp | tmpW | AT tmpDw : Array[0..1] of Word |
注意最后一行在 TIA 变量声明表里的填法:名称栏只写 tmpW,“数据类型”栏填完整的AT tmpDw : Array[0..1] of Word。
// FC:SwapHiLoWord_AT // 作用:用 AT 覆盖把 DWord 当两个 Word 用,再反序写回 // 输入位串原样搬进临时双字 #tmpDw := #inVal; // tmpW[0] 落在低地址,即原高字;tmpW[1] 是原低字 #outVal.%W0% := #tmpW[1]; #outVal.%W1% := #tmpW[0];逻辑说明:AT 覆盖不做任何转换,tmpW 和 tmpDw 共享同一段 4 字节空间,写 tmpDw 等于同时写了 tmpW 的两个元素。下标 0 对应低地址里的高字,下标 1 对应低字,所以反着写回去就是调换。
参数说明:这里踩坑最多的是长度匹配。DWord 是 4 字节,覆盖成 Array[0..1] of Word 正好 4 字节;写成 Array[0..3] of Byte 也可以;写成 Array[0..2] of Word 就会编译失败。
3.3 AT 覆盖编译报错的排查顺序
| 报错信息 | 大概率原因 | 处理 |
|---|---|---|
| 不允许使用 AT 覆盖 | 块启用了优化访问 | 块属性里取消勾选 |
| 覆盖变量与被覆盖变量长度不同 | 类型长度不匹配 | 用 Array[0..1] of Word 对 DWord |
| 不能覆盖该变量 | 覆盖对象是形参或常量 | 把覆盖放到 Temp 或 DB 里 |
AT 覆盖和片段访问有同一层限制,都依赖绝对地址,优化访问必须关。两者的分工也清楚:AT 覆盖能拿到一个带下标的数组,要按字节循环处理、或者一次覆盖出多种视图时写法更干净;片段访问不用多声明变量,一次性换个字更省事。
3.4 在 OB1 里把两个 FC 都挂上去对比
在 OB1 里再插一段,同时调用 SWAP 版和 AT 版,输入管脚接同一个 DB 变量 inputData,输出接两个不同的输出变量,在线对比结果。给 16#12345678 时,SWAP 版出 16#78563412,AT 版出 16#56781234,差异一眼可见。这一步别省,很多现场问题就是把两种调换搞混引起的。
4. 循环移位法:ROL 16 位换字,ROL 8 位换字节
4.1 为什么移位等价于调换
从二进制角度看,DWORD 是 32 位,循环左移 16 位让低 16 位整体跑到高 16 位,高 16 位绕回低 16 位,一次操作等于交换两个字。WORD 只有 16 位,循环左移 8 位等价于高字节低字节互换,效果和 SWAP 一致。
必须用循环移位,不能用普通左移。普通 SHL 移出去的位直接丢掉,高位补 0,高字会被清零而不是转到底部,结果完全不是调换。指令树上对应的是 ROL,不是 SHL。
4.2 SCL 里的 ROL 写法
// FC:SwapHiLoWord_ROL // 输入 inVal : DWord 输出 outVal : DWord // 32 位循环左移 16 位 = 高低字互换 #outVal := ROL(IN := #inVal, N := 16);逻辑说明:ROL 把移出的位补回另一端,不丢数据,所以 16 位这个整数正好完成半圈旋转,等价于两个字互换。整个 FC 一行搞定,不用声明任何中间变量。
参数说明:N 是 UINT 类型,写 16 就行,写成 16.0 会报类型错误;N 的合理取值是 0 到数据位宽之间,超出这个范围的结果不要依赖,不同固件表现不一致。要处理字节互换就换数据类型和 N,对 WORD 写 N = 8,对 DWORD 写 N = 8 得到的是 4 字节循环左移一位的效果。
4.3 N 取不同值的对照表
| 数据类型 | N | 输入 | 输出 | 等价操作 |
|---|---|---|---|---|
| Word | 8 | 16#1234 | 16#3412 | 高低字节互换 |
| DWord | 16 | 16#12345678 | 16#56781234 | 高低字互换 |
| DWord | 8 | 16#12345678 | 16#34567812 | 4 字节整体循环左移 1 字节 |
第三行说明一个边界:循环移位只能做“整段旋转”,做不了任意位置的字节自由组合。真要把 B0 和 B2 对调、B1 和 B3 对调这种不规则排列,还得回到片段访问或 AT 覆盖。
4.4 移位法的边界和适合它的场景
移位法的优势很实在:一行指令、不声明中间变量、不依赖非优化访问,块可以一直保持优化访问,扫描周期也最省。对那种通信负荷高、这段转换每秒要被调用几百次的场合,它是性价比最高的选择。
它的短板同样明确。第一,只能做整段循环,做不了自定义的字节映射;第二,REAL 变量不能直接参与移位,得先转成 DWord 位串,处理完再转回去,多两步;第三,可读性上,N := 16这种写法如果不加注释,接手的人未必看得懂这是在换字。
// 带类型转换的完整写法:REAL 进出,内部走位串 #tmpDw := REAL_TO_DWORD_BIT(#inVal); // REAL 位串转 DWord #tmpDw := ROL(IN := #tmpDw, N := 16); // 高低字互换 #outVal := DWORD_TO_REAL_BIT(#tmpDw); // DWord 位串转回 REAL逻辑说明:这里要的是位串重解释转换,不是数值取整转换。用错指令的话,1450.0 会被取整成 1450,小数部分直接没了,问题反而更隐蔽。选指令时认准位串转换那一组。
5. 四种方法的选型对照与在线调试验证
5.1 横向对照表
| 方法 | 程序行数 | 依赖非优化访问 | 可读性 | 适合的场景 |
|---|---|---|---|---|
| 片段访问 | 2 | 是 | 高 | 只换字或只换字节,逻辑一目了然 |
| SWAP | 4 | 间接依赖 | 中 | 需要 4 字节整体反转 |
| AT 覆盖 | 4 至 5 | 是 | 中 | 要按字节下标循环处理多组数据 |
| 循环移位 | 1 | 否 | 高 | 追求最少指令、块保持优化访问 |
SWAP 那一行写“间接依赖”,是因为用 SWAP 时通常要配合%W0%切片取字,那一步就要求关掉优化访问。如果改用两个中间 Word 变量做位串搬运,可以绕开这个限制,代价是多几条指令。
5.2 验证数据别再用 16#11112222
项目正文给的验证值 16#11112222 在四种方法下都能得到 16#22221111,看起来四路通吃,实际上它区分不出任何东西——两个字节内部重复,换字和整体反转结果相同。上机验证时换成 16#12345678,四种方法的结果才各自暴露。
| 方法 | 输入 16#12345678 | 输出 |
|---|---|---|
| 片段访问换字 | 16#56781234 | |
| SWAP 两次 | 16#78563412 | |
| AT 覆盖换字 | 16#56781234 | |
| ROL 16 位 | 16#56781234 |
另外,真实设备上跑之前,拿一个已知的真值做闭环验证更稳妥:在 PLC 里写 1450.0,用上位机读一次,看读回来的是不是 1450.0;如果读回来的是 4.7e-38 这种量级离谱的数,就是字节序问题;如果读回来的是 1.4e3 附近的数但不对,那多半是数据类型选错了。
5.3 一个可以直接抄的 FC 模板
把四种处理合成一个 FC,用模式字切换,调试不同设备时不用反复改程序。
// FC:DataOrderSwap // 输入 inVal : DWord 原始数据 // mode : Int 0=原样 1=换高低字 2=每字内字节互换 3=4字节整体反转 // 输出 outVal : DWord // 临时变量 wHi, wLo : Word CASE #mode OF 0: // 原样透传,用于现场快速排除是否是字节序问题 #outVal := #inVal; 1: // 高低字互换 #outVal := ROL(IN := #inVal, N := 16); 2: // 每个字内部高低字节互换 SWAP(IN := #inVal.%W0%, OUT => #wHi); SWAP(IN := #inVal.%W1%, OUT => #wLo); #outVal.%W0% := #wHi; #outVal.%W1% := #wLo; 3: // 4 字节整体反转 #outVal.%B0% := #inVal.%B3%; #outVal.%B1% := #inVal.%B2%; #outVal.%B2% := #inVal.%B1%; #outVal.%B3% := #inVal.%B0%; END_CASE;逻辑说明:mode 1 用移位最省事,不依赖非优化访问;mode 2 和 mode 3 用了片段访问,所以这个 FC 本身要取消优化访问才能编译通过。四种模式覆盖了现场绝大多数设备的字节序组合,现场调试时只要改 mode 管脚的值就能试出对方要的是哪一种。
参数说明:mode 建议在 DB 里做成可写的 Int 变量,方便在线改值验证;确认之后把值固化进程序,避免误改。调用时注意 inVal 和 outVal 不要接同一个变量,mode 1 和 mode 3 在读取过程中会逐字节写入,源和目标重叠会污染中间结果。想就地处理,先把输入拷进一个 TEMP 再传给 inVal。
本文还有配套的精品资源,点击获取