S7-1200串口自由口协议异或校验功能块实现与调试指南
简介西门子1200PLC异或校验程序是一份面向工业自动化工程师及PLC编程人员的实用示例用于解决串口或以太网通信中的数据完整性问题。通过西门子S7-1200实现异或BCC校验程序包含数据输入、逐位异或运算、校验码生成及错误检测等完整逻辑并可能包含梯形图或结构化文本源码便于直接移植或复用。压缩包共40个文件主要包含TIA Portal工程文件cfs、xml、prx等、配置文档及数据文件整体大小仅584KB结构紧凑适合快速查阅与导入。已有457人学习下载适合具有一定PLC基础、正在着手通信协议调试的开发者参考学习。通过该程序读者可以快速掌握在博途环境中搭建异或校验功能块的方法理解BCC校验码的计算与比对流程并能基于示例调整数据长度和通信协议提升实际项目中的通信可靠性。 干过串口通讯的工程师肯定绕不开“校验”这两个字。早几年我做环保数据采集现场一台老式电磁流量计通讯手册上写着“自由口协议校验方式异或”。看到这句话的时候我心里就咯噔一下不走标准Modbus意味着报文组帧、解析、校验全部要自己在PLC里写。当时在网上翻了半天梯形图版本、STL版本、各种残缺例程都试过要么版本对不上要么封装得稀烂拿回来还得从头改。后来我干脆基于S7-1200和TIA Portal博图自己写了一套异或校验功能块测试通过后一直沿用到现在的项目里。今天把它完整整理出来正在做1200串口通讯、自定义自由口协议、或者被各种奇怪校验折磨的朋友这篇应该能帮你省下不少时间。1. 异或校验到底是个什么东西1.1 从一次校验失败说起先讲我第一次栽跟头的经历。那台流量计的通讯手册里写得很简单报文最后加一个字节数值等于前面所有字节“异或”的结果。我第一版程序用梯形图写PLC能收到数据但报警一直在报“校验错误”。用串口助手抓包一看设备返回的报文跟我PLC算出来的校验字节差了一个固定值。最后反复翻手册才发现对方定义的校验范围是“从帧头开始到数据区结束不含校验字节”而我写的是“从地址开始到数据区结束不含帧头”。就这一个字节的范围差异折腾了我整整一个下午。这个案例很典型。异或校验本身不复杂但“校验范围”这个定义每个设备厂家都可能不一样。你写程序之前第一件事是把通讯手册里的报文结构画出来明确三个问题帧头算不算、地址算不算、校验字节自身算不算。这三个问题不搞清楚后面全是白干。1.2 异或校验的运算规则异或运算的逻辑很简单两个位相同为0不同为1。用十六进制表示就是按位逐个异或。举个例子有下面这一帧报文帧头地址功能码数据高字节数据低字节0xAA0x010x030x000x64那么校验字节的计算过程就是0xAA XOR 0x01 0xAB0xAB XOR 0x03 0xA80xA8 XOR 0x00 0xA80xA8 XOR 0x64 0xCC最终校验字节就是 0xCC整帧报文发送为 AA 01 03 00 64 CC。接收方收到后对前5个字节做同样的异或运算如果结果等于第6个字节就认为这一帧有效。在S7-1200里用SCL写这个逻辑核心就是一个循环bXor : 16#00; FOR i : 0 TO iLen - 1 DO bXor : bXor XOR aData[i]; END_FOR;1.3 为什么自由口协议喜欢用异或很多人会问校验手段那么多为什么很多仪表厂家偏偏选异或原因有三个算法简单计算开销小。一个循环就能算完PLC扫描周期几乎无感知。代码量少实现成本低。厂家写固件时也方便不需要引入复杂查表逻辑。对于数据量小的报文异或已经能覆盖大部分传输错误足够用。异或、LRC、CRC三种校验方式的区别这里顺便做个对比校验方式校验字节数计算原理应用场景XOR异或1字节所有字节逐位异或自定义自由口协议、简易仪表LRC纵向冗余1字节所有字节累加后取补码Modbus ASCIICRC162字节多项式除法Modbus RTU、较重负载通讯注意不要把LRC和异或混为一谈。LRC是“累加和的补码”跟异或很像但完全不是一回事。我见过不少朋友在Modbus ASCII协议里写异或校验结果跟从站设备永远对不上就是被资料里的说法带偏了。用哪种校验以设备手册为准不要自己发挥。2. 程序整体设计与方案选型2.1 压缩包里到底有什么我分享的这个压缩包解压后里面有三个文件一个TIA Portal全局库文件XOR_Lib.zal14一个示例项目ExampleProject.zap16一份使用说明PDF。全局库文件是核心里面封装好了几个功能块拿到手直接“从库拖到项目”就能用不需要重新敲代码。示例项目里带了一个已经调好的OB1调用逻辑你可以直接打开看它的调用方式再套到你自己的项目里。PDF说明文档则是把所有接口定义和计算边界都写清楚了避免你拿到手还要对着函数接口猜半天。这种“库 示例工程 说明文档”的组合是我个人比较推荐的项目交付形式。它既保留了功能块的复用性又降低了使用门槛。很多工程师喜欢把代码直接写在OB1里确实能跑但换项目时又要重新复制粘贴、删改地址长期来看非常不划算。2.2 功能块接口怎么定整套程序拆成了三个FC函数每个FC负责一件独立的事功能块名称作用关键接口FC_XOR_Sum计算异或校验值输入数据数组、数据长度输出校验字节FC_XOR_Check接收侧校验整帧报文输入报文数组、报文长度输出校验结果BOOLFC_ASCII_TO_HEXASCII字符还原为十六进制字节输入高位字符、低位字符输出还原后的字节接口设计上我统一使用Byte数组作为数据容器长度参数单独输入。为什么不把长度固死在数组里因为不同项目的报文长度差异很大写成可变参数一个功能块就能通吃所有场景。这也是我强烈建议你写通讯类功能块时养成的好习惯数据区用数组长度用参数千万别在块内部写死具体地址或固定长度。2.3 用SCL而不是梯形图的三个理由再聊聊编程语言选型。我见过很多老工程师拿到通讯程序第一反应是用梯形图这能理解。但校验这类算法逻辑我建议尽量用SCL结构化控制语言。理由有三个第一循环和数组是SCL的天然表达梯形图做大数组循环要写一整屏的临时变量和跳转逻辑读起来痛苦改起来更痛苦。第二SCL的代码可读性强一个FOR循环就能看出“我在逐字节异或”梯形图则要顺着网络一个个数别人接手时理解成本很高。第三SCL功能块在博图里可以做在线监控和断点调试出问题时能直接看到是哪一轮循环把结果算偏了。梯形图虽然也能监控但遇到循环体里某个字节异常定位效率远不如SCL直观。这个观点可能会被一些坚持梯形图的同行反驳但我的态度是通讯算法用SCL逻辑控制用梯形图两者结合项目代码既清晰又稳定。没必要非得分个高下。3. 核心代码实现与逐段拆解3.1 发送侧计算校验值并拼装报文发送侧的核心是FC_XOR_Sum完整代码我用SCL写在下面你直接复制到TIA Portal里新建的FC中即可FUNCTION FC_XOR_Sum : Byte VAR_INPUT aData : Array[0..255] of Byte; // 待计算的报文数组 iLen : Int; // 参与计算的字节个数 END_VAR VAR_TEMP i : Int; bXor : Byte; END_VAR bXor : 16#00; FOR i : 0 TO iLen - 1 DO bXor : bXor XOR aData[i]; END_FOR; FC_XOR_Sum : bXor;这段代码的逻辑很简单但有一个边界条件必须提醒iLen是“参与计算的字节个数”不是数组下标。比如前5个字节参与校验iLen就填5循环体实际执行的是aData[0]到aData[4]。如果填成6就把校验字节自身也算进去了结果必错。调用示例以刚才那帧 AA 01 03 00 64 为例在OB1里可以这样写// 组帧 DataDB.aSend[0] : 16#AA; // 帧头 DataDB.aSend[1] : 16#01; // 地址 DataDB.aSend[2] : 16#03; // 功能码 DataDB.aSend[3] : 16#00; // 数据高字节 DataDB.aSend[4] : 16#64; // 数据低字节 // 计算校验 DataDB.bXor : FC_XOR_Sum(aData : DataDB.aSend, iLen : 5); // 填到报文末尾 DataDB.aSend[5] : DataDB.bXor; // 调用TSEND_C或自由口发送指令发送 aSend[0..5]这里有一个细节aSend数组我定义的是Array[0..255]但实际发送时只需要发前6个字节。TIA Portal的自由口发送指令里有一个LEN参数填6就行这样不会把数组里残留的脏数据一起发出去。3.2 接收侧ASCII还原与校验接收侧的校验稍微复杂一点因为很多仪表返回的校验值不是直接一个十六进制字节而是两个ASCII字符。比如异或结果是0x1B发送端会发两个字节0x31字符1和0x42字符B。你在PLC侧必须先做ASCII还原再和计算值比较。先看ASCII转十六进制字节的FC_ASCII_TO_HEXFUNCTION FC_ASCII_TO_HEX : Byte VAR_INPUT cHigh : Char; // 高四位ASCII字符 cLow : Char; // 低四位ASCII字符 END_VAR VAR_TEMP bHigh : Byte; bLow : Byte; END_VAR // 高半字节转换0-9 - 0x30-0x39 IF cHigh 0 AND cHigh 9 THEN bHigh : CHAR_TO_BYTE(cHigh) - 16#30; ELSIF cHigh A AND cHigh F THEN bHigh : CHAR_TO_BYTE(cHigh) - 16#37; ELSIF cHigh a AND cHigh f THEN bHigh : CHAR_TO_BYTE(cHigh) - 16#57; END_IF; // 低半字节转换逻辑相同 IF cLow 0 AND cLow 9 THEN bLow : CHAR_TO_BYTE(cLow) - 16#30; ELSIF cLow A AND cLow F THEN bLow : CHAR_TO_BYTE(cLow) - 16#37; ELSIF cLow a AND cLow f THEN bLow : CHAR_TO_BYTE(cLow) - 16#57; END_IF; FC_ASCII_TO_HEX : SHL_BYTE(IN : bHigh, N : 4) OR bLow;这里的逻辑是把高位字符转成数值后左移4位再和低位字符转换值做按位或就能拼成完整字节。注意我特意加了对小写字母a-f的处理因为有些设备默认返回小写少了这一段你会莫名奇妙地校验失败。接收侧整体校验函数FC_XOR_Check的写法如下FUNCTION FC_XOR_Check : Bool VAR_INPUT aFrame : Array[0..255] of Byte; // 收到的完整报文帧 iLen : Int; // 报文总字节数含校验字节 END_VAR VAR_TEMP i : Int; bCalc : Byte; bRecv : Byte; END_VAR // 计算校验值范围是第0字节到倒数第2字节 bCalc : 16#00; FOR i : 0 TO iLen - 2 DO bCalc : bCalc XOR aFrame[i]; END_FOR; // 取出报文中收到的校验值 // 如果设备直接返回二进制校验字节 bRecv : aFrame[iLen - 1]; FC_XOR_Check : (bCalc bRecv);如果设备返回的是ASCII形式的两个字符把“取校验值”这一段替换成bRecv : FC_ASCII_TO_HEX(cHigh : CHAR_TO_BYTE(aFrame[iLen - 2]), cLow : CHAR_TO_BYTE(aFrame[iLen - 1]));同理得到FC_XOR_Check : (bCalc bRecv)。到底是哪种形式还是回到通讯手册确认不要想当然。3.3 把功能块接进OB1和串口通信组态功能块写好后要接入实际的通讯逻辑。S7-1200的自由口通讯和200系列不太一样不是直接XMT/RCV而是基于开放式通讯指令TSEND_C和TRCV_C。大致流程分三步第一步在设备组态里把通讯口的协议设置为自由口或者不选Modbus协议然后在OB100初始化时调用MB_COMM_LOAD配置波特率、数据位、停止位、校验方式。这里要特别注意MB_COMM_LOAD的“奇偶校验”参数和“数据位”是关联的8数据位只能配无校验或偶校验7数据位才能配奇校验。第二步建立TSEND_C发送连接和TRCV_C接收连接配置好本端和远端的IP/端口如果是串口就是硬件标识符。很多新手在这里卡住其实TSEND_C/TRCV_C不仅支持以太网也支持串口硬件标识符选用你组态的那个通讯口即可。第三步在OB1里做一个接收完成标志的边沿检测检测到一帧数据接收完成后把收到的报文数组传给FC_XOR_Check校验结果为TRUE才把数据解析并投入使用。千万别把FC_XOR_Check放在每个扫描周期都无条件执行一次那样会重复解析未完成的帧数据很容易出逻辑混乱。另外接收缓冲区建议放在一个独立的DB里并且关闭这个DB的“优化的块访问”选项。为什么因为优化访问的DB在在线监控和HMI访问时地址不固定调试时看不到数组里具体收到的字节排查问题非常不方便。反正我在现场调试时吃过这个亏后来一律用非优化DB保存通讯数据。4. 联调测试、常见问题与避坑指南4.1 用串口助手做模拟联调程序写完后不要直接接现场设备先用串口助手在电脑上做模拟联调这是排查问题最高效的方式。我的做法分四步第一步用USB转485串口模块连接PLC的通讯口和电脑确认两端接线正确。注意RS485的A/B线不要接反接反的典型现象是能收到数据但全是乱码。第二步打开串口助手按现场仪表的参数设置波特率、数据位、停止位、校验方式。然后在PLC侧组好一帧报文发送比如AA 01 03 00 64 CC看串口助手收到的数据是否完全一致。这一步验证的是发送链路和校验算法。第三步用串口助手主动回发一帧带正确校验的报文观察PLC内部接收完成标志和校验结果是否置位。然后再发一帧校验字节错误的报文确认校验结果为FALSE。这一步验证的是接收链路和校验判断逻辑。第四步如果有条件把DB里的aFrame数组和bCheck结果通过博图的在线监控直接拉出来比对串口助手发出的原始报文和PLC实际收到的字节逐字节核对基本能把问题定位到具体环节。4.2 常见问题速查表实际调试中遇到最多的问题我整理成了一张速查表现象可能原因排查思路和解决办法校验结果始终不对差值固定校验范围多算或漏算了一个字节重新核对协议手册的校验范围定义帧头、地址、校验字节本身是否纳入逐一确认通信偶尔丢帧但校验能过接收完成判定不准确TRCV_C调用过于频繁改成用接收完成中断或接收完成位边沿触发不要每个扫描周期都重新启动接收校验字符合并后结果仍错误只处理了大写A-F设备返回小写a-f在ASCII转换函数里同时兼容大写小写在线监控看不到数组数据接收缓冲区DB启用了优化访问右键该DB取消“优化的块访问”重新编译下载串口收到数据但全是乱码波特率、数据位、停止位配置不一致用串口助手扫描波特率确认设备手册参数核对MB_COMM_LOAD设置4.3 三个最容易踩的坑第一个坑是循环边界写错。FOR i : 0 TO iLen - 1和FOR i : 0 TO iLen看起来只差一点点实际计算结果差了一个字节。我在现场见过有工程师排查了一整天最后发现就是多循环了一次把校验字节自己也异或进去了。解决办法是每次写完代码拿一组已知报文手算一遍再对着程序跑出来的结果比对。第二个坑是异或初值。绝大多数协议的异或初值是0x00但个别厂商的协议会从0xFF开始运算或者对最终结果取反后才发送。这是通讯手册里最容易一带而过、也最容易让人栽跟头的地方。你就算完全复制了我的代码如果设备手册写了“初值0xFF”或“结果取反”依然会失败。一切以手册为准。第三个坑是把异或用在Modbus RTU协议上。西门子1200的MB_MASTER、MB_SLAVE指令自带CRC16校验你不需要也绝不应该再自己写异或校验。只有走自由口协议跟非标准设备通讯时才需要自己组帧并计算校验。把这两件事分清楚能省掉很多没有意义的排查。最后再分享一个我自己的调试习惯。每次写这类校验程序我不会一上来就接PLC调而是先用串口助手把完整报文算明白、验证透彻再往PLC里写。这样出了问题能用二分法快速判断是PLC侧组帧的问题还是设备返回报文本身就不对。这个习惯帮我少熬了好几个夜。后来我还把这套功能块做了个小扩展增加了帧头识别和超时计数这样在多台从站轮询时能直接判断哪台设备没回帧。如果你也经常和各种自由口协议打交道建议也沉淀一套自己的通讯工具库换项目时直接拖出来用省下的时间远超你写它的那半天。本文还有配套的精品资源点击获取