资讯动态

西门子1200PLC异或校验程序:SCL实现BCC校验与通信组帧

发布时间:2026/10/18 23:44:13 来源:尧图企业网站定制
简介这份资源面向工业自动化与PLC编程学习者提供西门子S7-1200 PLC实现异或校验BCC校验的完整程序实例可用于数据通信中的错误检测与完整性验证。压缩包共40个文件约584KB包含cfs、xml、prx、fdx、tvd、tvx、fdt、frq、tvf、tis等工程与配置文件以及pma15_0、gen、plf、idx、ap15、db、options、segments_1t、fnm、tii等TIA Portal项目相关数据覆盖程序块、系统配置与交叉引用等模块便于直接打开参考或移植复用。资源围绕数据输入、逐位异或运算、校验码计算、输出与接收端比对等环节展开展示了梯形图或结构化文本中XOR指令的用法并涉及自定义函数块封装思路有助于理解校验逻辑并提升程序可读性与复用性。目前已有461人学习下载适合从事数据通信、系统集成及PLC编程的工程师与进阶学习者参考借鉴。1. 西门子1200PLC异或校验程序从通信报文到校验字节的落地实现做西门子1200PLC上位机通信的开发者大概率遇到过这个场景PLC通过自由口协议或Modbus RTU往外发数据上位机收到的报文偶尔会多一个字节或少一个字节数据内容看着也对但就是解析出来是乱码。排查半天发现是校验环节没做对——发送端算了BCC校验接收端没校验或者校验算法不一致。这份西门子1200PLC异或校验程序解决的就是这个问题在TIA Portal环境下用SCL语言实现BCC异或校验的完整逻辑覆盖发送端生成校验字节和接收端验证校验字节两个方向。适合正在做PLC与上位机、PLC与PLC之间串口通信的工程师尤其是需要自己组帧、自己定义协议格式的场景。它不依赖任何第三方库纯SCL代码直接嵌入现有OB块或FC中就能用。2. 异或校验的底层逻辑从字节序列到校验值的计算链路2.1 BCC校验的数学本质与适用边界BCCBlock Check Character校验的核心操作是逐字节异或。给定一串数据字节D1, D2, …, Dn校验值C D1 ⊕ D2 ⊕ … ⊕ Dn。异或运算有三个性质决定了它适合做校验自反性A ⊕ A 0、交换律A ⊕ B B ⊕ A、结合律(A ⊕ B) ⊕ C A ⊕ (B ⊕ C)。自反性意味着接收端把收到的所有字节含校验字节再异或一遍结果应该是0这就是验证逻辑的数学基础。但BCC校验有个硬伤它检测不出字节顺序颠倒的错误也检测不出两个字节同时出错且异或值恰好抵消的情况。所以它适合短报文一般不超过256字节且对误码率要求不极端的场景。在PLC通信中串口本身的CRC校验已经处理了物理层误码BCC更多是应用层的完整性确认防止组帧逻辑出错导致的字节错位。2.2 在TIA Portal中建立校验功能块在TIA Portal中新建一个FC比如命名为FC_BCC语言选SCL。接口区定义如下FUNCTION FC_BCC : Void { S7_Optimized_Access : TRUE } VERSION : 0.1 VAR_INPUT dataBuffer : Array[*] of Byte; // 待校验的数据区变长数组 dataLength : Int; // 实际参与校验的字节数 END_VAR VAR_OUTPUT bccResult : Byte; // 计算出的校验字节 END_VAR VAR_TEMP i : Int; // 循环索引 tempXor : Byte; // 异或累加器 END_VAR BEGIN // 初始化累加器为00与任何字节异或都等于该字节本身 tempXor : 0; // 边界保护长度非法时直接返回0 IF dataLength 0 OR dataLength 256 THEN bccResult : 0; RETURN; END_IF; // 逐字节异或 FOR i : 0 TO dataLength - 1 DO tempXor : tempXor XOR dataBuffer[i]; END_FOR; bccResult : tempXor; END_FUNCTION这段代码的关键点在于dataBuffer用了Array[*]变长数组声明这样同一个FC可以处理不同长度的报文不需要为每种报文长度单独建块。dataLength参数让调用方指定实际参与校验的字节数因为发送缓冲区往往比实际报文长后面可能有未使用的填充字节。边界保护那一段是血泪经验——如果传入的dataLength是负数或者超过数组实际长度在PLC里会直接触发区域长度错误导致CPU停机加个判断能避免很多现场事故。2.3 发送端组帧把校验字节拼到报文尾部发送端的典型流程是先把有效数据填入发送缓冲区调用FC_BCC算出校验值再把校验值写到数据末尾最后更新发送长度。假设发送缓冲区是DB块中的一个Array[0..255] of Byte有效数据从索引0开始// 假设 SendDB.buffer 是 Array[0..255] of Byte // SendDB.dataLen 是实际数据长度不含校验字节 // 第一步计算校验值 FC_BCC(dataBuffer : SendDB.buffer, dataLength : SendDB.dataLen, bccResult #tempBCC); // 第二步把校验字节追加到数据末尾 SendDB.buffer[SendDB.dataLen] : #tempBCC; // 第三步发送长度 数据长度 1校验字节占1字节 SendDB.sendLen : SendDB.dataLen 1; // 第四步触发发送调用通信指令如TSEND_C或自由口发送这里有个容易翻车的地方SendDB.dataLen作为数组下标使用时如果它等于256buffer[256]就越界了。所以实际项目中我会把发送缓冲区开到Array[0..257] of Byte留出校验字节和可能的结束符位置。另外sendLen和dataLen必须分开维护混用会导致接收端把校验字节当成数据的一部分。2.4 接收端验证用异或归零判断报文完整性接收端的逻辑和发送端对称收到报文后对包含校验字节在内的所有字节做异或结果应该为0。如果不为0说明传输过程中有字节出错或帧边界错位。// 假设 RecvDB.buffer 是接收缓冲区 // RecvDB.recvLen 是实际接收到的字节数含校验字节 FC_BCC(dataBuffer : RecvDB.buffer, dataLength : RecvDB.recvLen, bccResult #verifyResult); IF #verifyResult 0 THEN // 校验失败丢弃报文置错误标志 RecvDB.bccError : TRUE; // 可选记录错误计数用于诊断 RecvDB.errCount : RecvDB.errCount 1; ELSE // 校验通过有效数据长度 recvLen - 1 RecvDB.bccError : FALSE; RecvDB.validDataLen : RecvDB.recvLen - 1; END_IF;验证逻辑比发送端更简单因为不需要额外拼接字节直接把整个接收缓冲区丢进去算就行。但要注意recvLen必须准确反映实际接收到的字节数如果通信指令返回的长度不包含校验字节这里就要手动加1。不同通信指令RCV_PTP、TSEND_C的接收回调等对长度的定义不一样这个坑后面会细说。3. 工程集成与调试把校验块嵌入实际通信流程3.1 在OB1或循环中断中调用校验逻辑校验功能块本身不涉及通信它只是一个纯计算FC。实际集成时发送端的调用时机是在组帧完成之后、触发发送指令之前接收端的调用时机是在接收完成中断或轮询检测到新数据之后。如果用的是自由口通信RS232/RS485模块接收通常靠RCV_PTP指令配合接收完成中断OB。在中断OB里先做BCC验证通过了再置一个“新报文就绪”标志给主循环处理。这样做的原因是中断OB里不适合做耗时操作而BCC计算是O(n)的256字节也就256次异或在1200PLC上大概几十微秒放中断里完全没问题。如果用的是Modbus RTU主站/从站指令情况特殊一些Modbus协议本身有CRC校验BCC是额外的应用层校验。这时候BCC校验的数据区应该只包含PDU协议数据单元不包括Modbus的地址域和CRC域。很多开发者在这里搞混把整个Modbus帧拿去做BCC结果永远对不上。3.2 用监控表和变量跟踪调试校验过程TIA Portal的监控表是调试校验逻辑最直接的工具。把发送缓冲区、接收缓冲区、bccResult、verifyResult这些变量加到监控表里在线运行时能实时看到每个字节的值。调试时我一般会构造一组已知数据来验证比如发送数据是16#01, 16#02, 16#03手动算一下异或结果应该是16#01 ⊕ 16#02 16#0316#03 ⊕ 16#03 16#00。如果PLC算出来不是0那说明代码逻辑有问题。这个手工验算的步骤看起来笨但能快速区分是校验算法写错了还是通信本身有问题。另一个实用技巧是在OB里加一个“强制发送测试帧”的触发位用监控表手动置位发一组固定数据出去然后用串口调试助手在PC端看收到的字节。这样能把PLC端的组帧逻辑和通信链路分开验证。3.3 不同通信场景下的参数适配场景数据区范围校验字节位置注意事项自由口自定义协议帧头之后到帧尾之前帧尾最后一字节帧头帧尾不参与校验Modbus RTU应用层PDU部分功能码数据PDU末尾不含地址和CRCPLC间PUT/GET不适用不适用走S7协议有底层校验与上位机自定义串口整个应用层报文报文最后一字节需双方约定一致这张表里的“数据区范围”是最容易出分歧的地方。我遇到过上位机开发者把帧头也纳入校验而PLC端没纳入两边算出来的BCC永远不一样。解决办法是在协议文档里明确写清楚从第几个字节开始、到第几个字节结束参与校验。没有文档就双方各写一个测试用例用同一组数据对结果。4. 避坑与排查异或校验在实际项目中翻车的几种典型情况4.1 校验结果始终不为零现象接收端每次校验都失败但用串口助手看数据内容是对的。原因最常见的是recvLen不包含校验字节。比如RCV_PTP指令返回的LENGTH参数只统计了有效数据长度校验字节在缓冲区里但没被计入长度导致FC_BCC只算了数据部分没算校验字节异或结果自然不是0。解决在调用FC_BCC之前把长度参数手动加1。或者更稳妥的做法是定义一个totalLen变量在接收完成时明确赋值totalLen : recvDataLen 1后续所有涉及长度的计算都用totalLen。4.2 发送端和接收端校验不一致现象PLC发给上位机的数据上位机校验通过上位机发给PLC的数据PLC校验失败。原因双方对“哪些字节参与校验”的约定不一致。典型情况是上位机把帧头比如0xAA 0x55也算进去了而PLC端从帧头之后开始算。解决在协议文档中明确校验范围并且双方各写一个固定的测试帧用相同的输入验证输出是否一致。测试帧要覆盖边界情况最短帧、最长帧、包含0x00字节的帧。4.3 数据中出现0x00导致校验“看似通过”现象偶尔校验通过但解析出来的数据明显不对。原因如果接收缓冲区的未使用区域恰好是0x00而recvLen又设大了多算进去的0x00不影响异或结果因为A ⊕ 0 A校验照样通过但有效数据长度错了。解决接收缓冲区在每次接收前清零或者严格根据通信指令返回的长度来设置recvLen不要用固定长度。另外可以在协议里加一个长度字段接收端先解析长度字段再用这个长度去校验双重确认。4.4 变长数组在FC调用时编译报错现象FC_BCC编译时报“数组参数类型不匹配”。原因Array[*] of Byte要求实参必须是同类型的变长数组如果传入的是固定长度数组如Array[0..255] of Byte在某些TIA Portal版本中需要显式转换或使用Array[0..255] of Byte作为形参类型。解决如果项目里所有报文的缓冲区都是固定256字节直接把形参改成Array[0..255] of Byte更省事。变长数组的好处是通用性强但代价是调用时的类型匹配更严格。我一般会在项目初期就确定缓冲区最大长度用固定长度数组避免后期类型不匹配的麻烦。4.5 校验计算耗时影响通信实时性现象加了BCC校验后通信周期变长偶尔丢帧。原因如果校验逻辑放在主循环OB1里而OB1的扫描周期本身就不稳定加上BCC计算的时间可能导致接收缓冲区被覆盖之前没来得及处理。解决把接收和校验放在接收完成中断OB里中断的优先级高于主循环能保证数据被及时处理。另外BCC计算本身很快256字节的异或循环在1200PLC上大概几十微秒不太可能是性能瓶颈更多是调用时机的问题。5. 二次开发与扩展从异或校验到通用校验框架5.1 把BCC校验改造成可配置的校验框架实际项目中不同通信协议可能用不同的校验方式BCC异或、CRC16、LRC纵向冗余校验、和校验等。与其为每种校验写一个FC不如做一个统一的校验接口用枚举区分校验类型。FUNCTION FC_Checksum : Void { S7_Optimized_Access : TRUE } VERSION : 0.1 VAR_INPUT dataBuffer : Array[0..255] of Byte; dataLength : Int; checkType : Int; // 0BCC异或, 1和校验, 2LRC END_VAR VAR_OUTPUT checkResult : Byte; END_VAR VAR_TEMP i : Int; tempSum : Int; // 用于和校验 tempXor : Byte; tempLRC : Byte; END_VAR BEGIN CASE checkType OF 0: // BCC异或校验 tempXor : 0; FOR i : 0 TO dataLength - 1 DO tempXor : tempXor XOR dataBuffer[i]; END_FOR; checkResult : tempXor; 1: // 和校验取低8位 tempSum : 0; FOR i : 0 TO dataLength - 1 DO tempSum : tempSum BYTE_TO_INT(dataBuffer[i]); END_FOR; checkResult : INT_TO_BYTE(tempSum AND 16#00FF); 2: // LRC纵向冗余校验本质是取反的BCC tempLRC : 0; FOR i : 0 TO dataLength - 1 DO tempLRC : tempLRC XOR dataBuffer[i]; END_FOR; checkResult : NOT tempLRC 1; // 取补码 ELSE checkResult : 0; END_CASE; END_FUNCTION这个框架的好处是调用方只需要改checkType参数就能切换校验方式不用改调用逻辑。扩展新的校验类型也只需要在CASE里加一个分支。注意和校验那里用了INT做累加再取低8位因为多个字节相加可能超过255用Byte类型会溢出。5.2 用SCL写校验逻辑的几个实用技巧第一个技巧是循环边界用dataLength - 1而不是直接写dataLength因为SCL的FOR循环是闭区间FOR i : 0 TO dataLength - 1等价于遍历0到dataLength-1共dataLength个元素。这个和C语言的习惯不同写错了会多算一个字节。第二个技巧是善用XOR的交换律来简化验证逻辑。接收端不需要单独算一遍再比较直接把校验字节也纳入异或判断结果是否为0。这样代码更短也少一个比较操作。第三个技巧是在FC的注释里写清楚每个参数的单位和范围。PLC代码的维护周期很长半年后回头看没有注释的代码基本等于黑匣子。我一般会在接口区用//标注每个参数的含义在代码关键行标注“为什么这么做”而不是“做了什么”。5.3 验证改造后的校验框架改造完成后用一组覆盖所有校验类型的测试用例来验证。构造数据16#01, 16#02, 16#03BCC异或01 ⊕ 02 ⊕ 03 00和校验01 02 03 06LRCNOT (01 ⊕ 02 ⊕ 03) 1 NOT 00 1 FF 1 00在监控表里逐个切换checkType看checkResult是否和手工计算一致。三个都对了说明框架逻辑没问题。然后拿实际通信报文再跑一遍确认端到端流程通畅。从那以后我每次写完校验逻辑都会先用这组固定数据在监控表里验一遍再接入实际通信。这个习惯帮我省了很多现场排查的时间——校验算法本身出错其实很少见大部分问题出在长度参数和调用时机上但先用固定数据排除算法问题能把排查范围缩小一半。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑