简介:这是一份面向工业自动化工程师与PLC编程初学者的技术文档,聚焦TIA博途中S7-1200与上位机通信时浮点数高低字节、高低字顺序相反导致读数异常的问题,系统整理出四种可实现字节序调换的编程思路。压缩包共1个文件,为docx格式,大小约747KB,以图文并茂的方式讲解间接寻址与片段访问、SWAP指令、AT变量覆盖、循环左移16位这四种方法,并对WORD级调换所需的循环左移8位作了说明。文档按步骤给出FC块接口定义、SCL程序编写及在OB1中调用并连接数据块变量的完整过程,读者可对照示例验证16#11112222与16#22221111的互换效果,同时获得变量显示格式的设置提示与排错参考。目前已有2308人学习下载,适合需要解决跨系统数据存储差异、希望比较不同方案可读性与适用场景的工程人员参考借鉴。
1. 从一次 Modbus TCP 读数说起:S7-1200 里的高低字节为什么总错位
现场常见的一幕:S7-1200 通过 Modbus TCP 去读第三方仪表的累计流量,寄存器地址没错、功能码没错、连接也建起来了,可读回来的 REAL 是 1.2e-38 这种明显不合理的值。把四个寄存器单独拎出来看,每一位都对,只是顺序被翻了个面。
这类问题在 TIA 博途里排查不费劲,解决也不费劲,难的是先搞清楚该翻字节还是该翻字。S7-1200 内部按大端存放数据,高字节在低地址;而上位机、脚本语言和部分小端控制器按相反顺序解析。Modbus 协议规定寄存器是大端,但 32 位数据跨两个寄存器时先高字还是先低字,各家实现并不统一,于是就有了 WORD 内字节颠倒、DWORD 内高低字颠倒、四字节整体反转这三种典型错位。
下面按从现成指令到手工拼装、再到结构体覆盖和批量循环的顺序,给出四种可复现的调换做法,并说明各自适用的数据规模与坑在哪。
2. 方法一:用 SWAP 指令直接调换高低字节
TIA 博途的指令树里,SWAP 归在「基本指令 → 移动操作」下面,是 S7-1200 上最省事的字节调换手段:不占中间变量、不写逻辑,拖一个盒子上来,输入输出一接就完事。代价是它的能力被数据类型卡死,只做字节层面的重排,做不了字层面的重排,这个边界后面单独说。
2.1 SWAP 指令对不同数据类型到底做了什么
很多人把 SWAP 当成万能翻转器,接上去发现 DWORD 的结果还是不对,就是因为没看清它按什么粒度动作。它只认输入引脚上标注的类型,按该类型的字节长度做整段反转:
| 输入类型 | 输入示例 | 输出 | 实际动作 |
|---|---|---|---|
| BYTE | 16#12 | 16#12 | 单字节,无变化 |
| WORD | 16#1234 | 16#3412 | 高低字节互换 |
| DWORD | 16#11223344 | 16#44332211 | 四字节整体反转 |
注意 DWORD 那一行:它反的是字节序,不是高低字。16#11223344 的高低字调换结果应该是 16#33441122,而 SWAP 给的是 16#44332211。把这两个数写在一起对比一次,比记十遍定义管用。
2.2 在 LAD/FBD 里接一个 SWAP 的最小步骤
操作顺序如下,每一步都可以在离线状态下完成,下载前先编译一遍确认没有类型告警:
- 在 OB1 或自建 FC 里,从右侧指令树把 SWAP 拖到网络中间。
- IN 端接要处理的变量,例如某个 DWord 类型的 DB 变量或 M 区双字。
- OUT 端接结果变量,类型必须与 IN 一致。
- 点击指令盒上方的数据类型标注,明确选 WORD 还是 DWORD,别依赖默认值。
SWAP 是纯组合逻辑,没有使能端,只要所在网络被扫描到就执行。所以不要把它放在会被条件跳过的分支后面,还指望结果保持上一次的值——扫描不到就不会更新,这个行为和带 EN 的指令完全不同。
2.3 在 SCL 里调不到 SWAP 时怎么绕
不同固件版本、不同编辑语言下,SCL 编辑器里不一定能直接敲出 SWAP 这个函数名。稳妥做法是把 LAD 版本的逻辑封装进一个 FC,接口声明成 Word 进、Word 出,然后在 SCL 里当普通函数调用:
// FC "ByteSwap16":内部由一个 SWAP 指令实现 // 接口声明:IN : Word ; OUT : Word // SCL 侧调用 #wResult := "ByteSwap16"(IN := #wRaw);这样逻辑只有一份,LAD 和 SCL 两种语言都能复用;代价是多一层调用开销,对扫描周期极敏感的高频循环里不如直接用手工移位。
参数说明:IN 与 OUT 的类型必须严格一致,WORD 进 WORD 出,DWORD 进 DWORD 出。如果 IN 直接接 REAL 变量,编译会报类型不匹配。想把 REAL 交给 SWAP 处理,先把它 MOVE 给一个 DWORD 中转变量,交换完再 MOVE 回 REAL——这两个 MOVE 都是按位拷贝,不做数值转换,千万不能误用 CONV 之类的转换指令,否则浮点格式会被改掉,数值直接失真。
3. 方法二:用移位和逻辑运算手工拼装高低字节与高低字
SWAP 解决不了的场景很具体:一是不方便调用外部 FC 的场合,二是需要同时做字节与字两种调换的场合,三是输入类型不确定、需要在同一段逻辑里参数化处理的场合。这时用手工移位拼装,逻辑完全展开,可控性最强。
3.1 拆字原理:掩码取位、移位归位、按位或拼合
用移位做调换,思路是把一个字拆成两半,各自移到对面的位置上再拼回去。以 16 位数据为例:低字节 = 原值 AND 16#00FF;高字节 = 原值右移 8 位;结果 = 低字节左移 8 位 OR 高字节。掩码的作用是隔离,移位的左右是归位,OR 是拼合,三步缺一不可。省掉掩码会让高字节的残留位混进低字节位置,结果看着像对、实则多出几个 bit。
3.2 SCL 里实现 16 位高低字节调换
FUNCTION "SwapBytes16" : Word VAR_INPUT inVal : Word; // 待调换的 16 位数据 END_VAR VAR_TEMP loByte : Word; // 临时存放低字节 hiByte : Word; // 临时存放高字节 END_VAR BEGIN loByte := inVal AND 16#00FF; // 取出低 8 位 hiByte := SHR(IN := inVal, N := 8); // 高 8 位右移到低位 loByte := SHL(IN := loByte, N := 8); // 低 8 位左移到高位 "SwapBytes16" := loByte OR hiByte; // 按位或拼成结果 END_FUNCTION逻辑说明:SHR 和 SHL 都是无符号移位,移出的位直接丢弃,不循环回绕,所以右移之后高位自然补 0,不需要再做额外掩码清除。
参数说明:N 的取值范围是 0 到数据位宽,取 16 或更大在编译阶段就会告警。输入如果声明成 Int 而不是 Word,右移会带着符号位一起移动,负数输入的结果会完全错误,这是这一段最容易踩的坑,类型栏里务必选无符号。
3.3 扩展到 32 位高低字调换
FUNCTION "SwapWords32" : DWord VAR_INPUT inVal : DWord; END_VAR VAR_TEMP loWord : DWord; hiWord : DWord; END_VAR BEGIN loWord := inVal AND 16#0000FFFF; // 取出低 16 位 hiWord := SHR(IN := inVal, N := 16); // 高 16 位下移 loWord := SHL(IN := loWord, N := 16); // 低 16 位上移 "SwapWords32" := loWord OR hiWord; // 拼合 END_FUNCTION关键是把 16 位当成整体来搬,掩码 16#0000FFFF 保证高字不会被带进来。如果要同时做「字内字节交换 + 字间交换」,先调 SwapWords32,再把结果送进方法一的 SWAP,两步串起来就能覆盖四字节的全部排法。
3.4 两种调换的组合与 Modbus 常见排法对照
抓到的四个寄存器属于哪种排法,先手算一遍再决定调哪个函数,比在程序里反复试快得多。以 16#11223344 为基准,常见排法对应关系如下:
| Modbus 常见排法 | 字节顺序 | 相对基准需要做的操作 |
|---|---|---|
| ABCD | 高字高字节在前 | 不做处理 |
| DCBA | 整体反转 | SWAP(DWORD 类型) |
| CDAB | 交换高低字 | SwapWords32 |
| BADC | 每个字内交换字节 | 对两个字分别做字节交换 |
这张表可以直接当排查清单:先按基准手算目标值,再用在线监控比对,两种操作都不会白试。
4. 方法三:用 DB 里的 AT 覆盖把 DWORD 拆成字节数组重排
前两种方法都在做位运算,处理 REAL 时需要先转成整数类型再转回来,容易出错。AT 覆盖换了个思路:不做任何运算,只是让同一段内存用两种类型去看,改完位置后按原始位模式读出来,浮点值自然正确。
4.1 AT 覆盖的声明方式与前提条件
声明格式是基础变量在前,紧跟一行 AT 叠加视图,两者占用空间必须严格相等:
// 在全局 DB "dbComm" 中声明,该 DB 需关闭"优化的块访问" rawValue : DWord; // 基础视图,4 字节 rawBytes AT rawValue : Array[0..3] OF Byte; // 覆盖视图,4 字节 rawWords AT rawValue : Array[0..1] OF Word; // 覆盖视图,4 字节AT 覆盖要求 DB 关闭「优化的块访问」。在 DB 属性里取消该选项后,变量才有固定偏移,叠加视图才对得上。这是 S7-1200 上的硬约束,不关就是编译报错,没有绕过去的写法。
4.2 用 AT 覆盖调换 REAL 的高低字
// 前提:dbComm 已关闭优化访问,rawValue 中是 Modbus 读回的原始位模式 #tmpW := "dbComm".rawWords[0]; // 暂存低地址处的字 "dbComm".rawWords[0] := "dbComm".rawWords[1]; // 高字写入低字位置 "dbComm".rawWords[1] := #tmpW; // 原低字写入高字位置 // 此时 rawValue 作为 DWord 已经是调换后的位模式 #rResult := "dbComm".rawValue; // 按 DWord 取出,赋给 REAL 变量逻辑说明:整个过程没有做任何数值运算,纯粹按内存位置搬字节,所以对 REAL 完全安全,不会因为浮点格式被改动而丢精度。
参数说明:数组下标从 0 开始,rawWords[0] 位于低地址。S7-1200 是大端存储,低地址放的是高字,所以第 0 个元素在数值上是高字。这个对应关系搞反了,结果就是原样不动或者双重调换,看着像没生效。
4.3 AT 覆盖的类型匹配规则与三个坑
叠加视图的类型不是随便配的,长度必须完全相等:
| 基础类型 | 合法覆盖类型 | 非法示例 |
|---|---|---|
| DWord | Array[0..3] OF Byte、Array[0..1] OF Word | Array[0..7] OF Byte |
| Real | 同上,也可覆盖成 DWord | Array[0..3] OF Word |
| Word | Array[0..1] OF Byte | Array[0..3] OF Byte |
第一个坑:AT 声明必须紧跟在被覆盖变量之后,中间插入别的变量会导致偏移错位。第二个坑:优化访问的 DB 不支持 AT。第三个坑:数据类型长度必须严格相等,差一个字节直接编译失败。
提示:项目里大量使用优化访问 DB,又不想为了调换把优化关掉,那就放弃 AT 覆盖,改用方法二的移位或方法四的循环处理,代价是多写几行位运算。
5. 方法四:用 FOR 循环批量调换高低字节与高低字
前面三种都是针对单个变量的。报文一旦有几十上百个 16 位数据要调换,逐个调函数既臃肿又难维护,用 FOR 循环遍历数组是唯一现实的做法。
5.1 用 FOR 循环处理整个 Word 数组
// 入参:awData 为 Word 数组,iUpper 由 UPPER_BOUND 计算得到 FOR #i := 0 TO #iUpper DO #wTmp := #awData[#i]; #awData[#i] := SHL(IN := #wTmp AND 16#00FF, N := 8) OR SHR(IN := #wTmp, N := 8); END_FOR;逻辑说明:循环体那一行做的事和方法二完全相同,只是对每个元素重复一遍,原地写回,不额外占用存储区。
参数说明:循环上界不要写死数字,用 LOWER_BOUND 和 UPPER_BOUND 指令取当前数组的边界,数组长度改了下界也会跟着变。元素是 DWord 时,循环体换成调用SwapWords32。循环次数大时把这段放在主循环里执行,别塞进对实时性要求高的循环中断 OB,否则会拉长扫描周期,影响其他周期任务。
5.2 用监控表和强制表验证调换结果
写完别急着下结论,先拿一组已知位模式验证。在监控表里强制一个 DWord 变量为 16#11223344,观察调换后是不是 16#33441122。如果在线修改的值立刻被程序覆盖,就把它放进只在首次扫描时执行的 MOVE,或者临时注释掉调用。
接着看 REAL 的显示值。调换正确时应该落在合理量程内,比如 25.6、1234.5 这类;如果显示成 1.2e-38 或一串 NaN 开头的内容,说明排法还没对上,回到第 3.4 节的表格重新对照一次。批量场景下更省事的做法是抽头尾两个元素单独比对,确认循环边界没写错——常见的低级错误是上界写成元素个数,结果多处理了一个不存在的元素,触发数组越界。
5.3 四种方法的选型对照
| 方法 | 适用数据量 | 能否处理 REAL | 依赖优化访问设置 | 典型场景 |
|---|---|---|---|---|
| SWAP 指令 | 单个 | 需 DWord 中转 | 否 | 单个寄存器字节反转 |
| 移位逻辑 | 单个到中等 | 可以,按 Word/DWord 处理 | 否 | SCL 内联处理,需要参数化 |
| AT 覆盖 | 单个 | 直接处理,不丢精度 | 是,需关闭优化 | DB 中现成的 REAL 字段调换 |
| 循环加数组 | 大批量 | 可以 | 否 | 整段报文重排 |
选型时优先看两点:数据是一批还是零星几个,所在的 DB 有没有开优化访问。批量的走循环,零星的走 SWAP 或移位,DB 里现成的 REAL 字段且不介意关优化,就用 AT 覆盖。三种调换动作和四种实现方式交叉组合,基本能覆盖 S7-1200 上会遇到的全部字节序问题。
本文还有配套的精品资源,点击获取