ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VC++与S7-200 PLC的PPI协议通讯实现:串口编程与报文详解

VC++与S7-200 PLC的PPI协议通讯实现:串口编程与报文详解 简介面向工业自动化上位机开发场景的一份实战代码包围绕VC与西门子S7-200 PLC通讯展开覆盖TCP/IP与MPI两种常见通信方式并结合具体工程演示了连接初始化、读写请求、数据类型映射及错误处理等关键环节。压缩包共38个文件大小2.44MB以C源代码.h/.cpp、工程与解决方案文件.dsp/.dsw/.sln/.vcproj为主同时包含调试生成的.exe、.pdb、.obj以及界面资源文件.rc/.res/.ico既可运行查看效果也可直接编译学习。目前已有277人学习使用。通过阅读源码可以掌握基于ModBus/串口通信的实现思路理解S7-200存储区读写、同步/异步通信模型以及常见异常处理流程为后续开发自己的上位机控制系统提供可复用的代码基底。1. 通讯方案选型与整体设计思路做上位机开发这些年VC与S7-200通讯是被问得最多的问题之一。S7-200虽然已经是西门子家族里的老将但在小型自动化设备、包装线、环保设备里依然大量服役而且它的通讯方式非常典型。拿VC这里指的是Visual C 6.0或更高版本的VC开发环境写上位机跟它打交道基本上是每个工控软件工程师的必修课。1.1 先搞清楚S7-200到底支持哪些通讯方式S7-200 CPU本体上自带一个或两个RS485口取决于具体型号这个口物理上走的是RS485电平但逻辑上可以跑好几种协议。最常用的有四种通讯方式协议类型上位机实现难度适用场景PPI协议西门子专有中等读写PLC变量、下载程序、在线监控自由口协议用户自定义低自定义报文格式、与第三方设备通讯Modbus RTU工业标准低与组态软件、其他品牌设备通讯通过OPC/组态软件中转标准接口最低快速交付项目、不关心底层细节对于VC开发者来说我的建议是如果只是做数据采集和简单的读写操作优先考虑PPI协议不依赖第三方组态软件一个串口类库加上协议封装就够了。如果你的PLC程序里已经写了MODBUS从站指令S7-200有对应的库指令那走Modbus RTU会更省事。自由口则适合传输自定义的、非标的业务数据。1.2 为什么首选PPI协议而不是其他方案我在实际项目中第一次调试PPI协议时也踩过不少坑但搞通之后你会发现PPI是S7-200原生支持的最佳通讯方式原因有三第一PPI不需要在PLC侧做任何配置。你不需要往PLC里下载额外的通讯程序只要PLC运行着PPI主站就可以直接发起通讯请求。对于维护存量设备来说这是巨大的优势因为你不需要修改PLC程序就能做上位机。第二PPI协议本身是面向连接的有完整的请求应答机制错误检测比自定义协议要严格抗干扰能力也更强。尤其是在现场有大功率变频器、伺服驱动器这类强干扰源的环境下PPI的容错表现比自由口要稳定得多。第三PPI协议里已经封装了读取和写入V区、M区、I区、Q区、T区、C区的标准报文格式你只需要按照协议格式拼报文不需要在PLC侧写任何数据交换逻辑。这也是它比自由口省事的地方。当然PPI也有缺点——最明显的就是它只能跟S7-200系列通讯不能用来连接S7-1200、S7-1500这些后续型号。另外它的传输速率上限是187.5Kbps实际项目中最常用的是9.6Kbps或19.2Kbps大数据量采集时会觉得有点慢。1.3 通讯硬件的选择与连接方式硬件连接上最常见的是通过USB转RS485串口线连接电脑和PLC的PPI口。选线缆时要特别注意必须选工业级的USB转485线不要用几块钱的USB转TTL模块直接怼PLC的485口电平不匹配会烧毁PLC通讯口。线材两端要做好屏蔽接地现场变频器启动瞬间的干扰能直接打穿劣质转换器的隔离。PC端安装驱动后在设备管理器里确认一下COM口号PPI协议默认波特率9600连接时串口参数选择9600,8,E,1数据位8位偶校验1个停止位。物理接线是USB转485的A端接PLC的D3号针脚B端接PLC的D-8号针脚如果需要握手信号再接1号和2号针脚。RS485是差分信号A/B不能接反接反了通讯直接没有任何响应。2. PPI协议核心细节解析2.1 PPI报文结构拆解PPI协议的精髓全在报文格式里。一条完整的PPI报文大致是这个样子SD LE LER SD DA SA FC DSAP SSAP DU FCS ED各字段含义如下SDStart Delimiter起始字节固定为0x68。LELength从DA到DU不含FCS和ED的字节长度。LERRepeat Length与LE完全相同是重复校验。SD第二个起始字节同样是0x68。DADestination Address目标站地址PLC站地址默认是2。SASource Address源站地址即上位机地址通常设置为0。FCFunction Control功能码常见的有0x6C读取数据、0x7C写入数据、0x72读取多项数据等。DSAP/SSAP服务访问点一般是0x00。DUData Unit数据单元包含具体的请求或响应数据。FCSFrame Check Sequence校验字节从第二个SD开始到DU结束所有字节的累加和取低8位。EDEnd Delimiter结束字节固定为0x16。提示FCS的算法很容易被忽略但它恰恰是通讯失败的头号原因。我记得很清楚有一次调试环境下来回排查了很长时间最后才发现是FCS计算时把第一个SD也加了进去导致校验始终对不上PLC一律不回帧。2.2 读取与写入的报文实例以读取PLC的VW100一个字为例完整的请求报文是68 09 09 68 02 00 6C 00 00 04 01 12 0A 10 02 00 01 00 00 55 16逐字节解释一下09是长度表示从DA到DU共9个字节后面重复的09是校验长度。02是PLC站地址00是上位机站地址6C是读取功能码。接下来的04 01 12 0A 10是读请求的参数段04表示之后还有5个字节01代表读取1个字12是读取V区的编码0A 10是VW100的偏移地址100被编码成0x0A10最后一个00表示数据类型字节。55是FCS16是结束符。写入VW100的报文结构与之类似只是功能码变为7C数据区里会多出要写入的数值。这里有一个细节写字的Data Unit里要区分写入值的类型和读取时略有不同写字节时参数段后的数据长度是实际数据长度1且必须带一个类型标志字节。2.3 站地址、数据区编码与偏移地址的换算S7-200的存储区在PPI协议里都有对应的编码数据区编码十六进制说明V区0x12保持寄存器区最常用M区0x03标志位区I区0x01输入映像区Q区0x02输出映像区T区0x1F定时器区C区0x20计数器区偏移地址的换算是另一个容易出错的地方。S7-200的V区地址在PPI协议里是用字节偏移来编码的例如VW100要读取的字节地址是100转换成十六进制是0x0064在报文里写成两个字节00 64。但有的资料上会写成0A 10那是因为它用的是字地址编码方式100除以2得50再转十六进制为0x32但PPI实际不是这样编码的。我测试下来V区读取时确实直接使用绝对字节地址0x0064即可写成00 64肯定能通。3. VC下的代码实现要点3.1 串口通信类的选择与封装VC下实现串口通讯有很多种方式从最底层的CreateFileReadFile/WriteFile到封装好的CSerialPort类再到第三方的Pcomm、VSPD虚拟串口选择面很广。我的经验是用Win32 API自己封装一个串口类最靠谱可控性强也便于后续扩展。一个简单的串口打开函数核心就这几步HANDLE hCom CreateFile(COM3, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hCom INVALID_HANDLE_VALUE) { // 串口打开失败检查是否被占用 } DCB dcb {0}; dcb.DCBlength sizeof(DCB); GetCommState(hCom, dcb); dcb.BaudRate 9600; dcb.ByteSize 8; dcb.Parity EVENPARITY; // PPI协议使用偶校验 dcb.StopBits ONESTOPBIT; SetCommState(hCom, dcb); COMMTIMEOUTS timeouts {0}; timeouts.ReadIntervalTimeout 50; timeouts.ReadTotalTimeoutConstant 100; timeouts.ReadTotalTimeoutMultiplier 10; SetCommTimeouts(hCom, timeouts);这里有个细节值得说一下ReadIntervalTimeout设置成50毫秒意思是接收字节之间如果超过50毫秒就认为一帧数据接收完毕这是处理PPI不定长响应的常用手段。因为PPI响应帧的长度不是固定的你不能指望通过固定字节数来判断帧结束只能依赖超时机制。3.2 PPI报文组帧与FCS校验的计算组帧逻辑建议写成独立的函数方便复用。下面是FCS计算的参考实现// 计算FCS校验data为帧数据从第二个SD开始到DU结束len为长度 unsigned char CalcFCS(const unsigned char* data, int len) { unsigned char fcs 0; for (int i 0; i len; i) { fcs data[i]; } return fcs; }组一帧完整的读取VW100请求可以这样写// 构建读取VW100的PPI请求帧 unsigned char szReq[32] {0}; int nLen 0; szReq[nLen] 0x68; // SD szReq[nLen] 0x09; // LE szReq[nLen] 0x09; // LER szReq[nLen] 0x68; // SD szReq[nLen] 0x02; // DAPLC站地址 szReq[nLen] 0x00; // SA上位机站地址 szReq[nLen] 0x6C; // FC读取功能码 szReq[nLen] 0x00; // DSAP szReq[nLen] 0x00; // SSAP szReq[nLen] 0x04; // DU长度 szReq[nLen] 0x01; // 读取字数 szReq[nLen] 0x12; // V区编码 szReq[nLen] 0x00; // 偏移地址高字节 szReq[nLen] 0x64; // 偏移地址低字节 100 szReq[nLen] 0x00; // 数据类型字节 // 注意FCS从第二个SD索引4开始计算到DU结束索引14 szReq[nLen] CalcFCS(szReq 4, nLen - 4); szReq[nLen] 0x16; // ED写完这帧之后用串口的WriteFile发送出去再等待ReadFile接收回应。收到响应后要做几个关键校验第一帧头必须是0x68最后一个字节必须是0x16FCS必须对得上。只有全部通过才能认为这次通讯成功。3.3 超时重试机制的设计PLC通讯和以太网通讯不一样串口是半双工的而且现场干扰可能导致偶发丢帧。一个健壮的上位机程序必须有完善的超时重试机制。我的做法是每条指令最多重试3次超时时间设为200毫秒。第1次超时后延时50毫秒再发第2次如果还是超时基本可以判断是通讯链路有问题了这时候再去更换站地址、检查波特率、确认PLC是否在RUN状态。代码上可以用一个循环包裹发送和接收BOOL SendAndWaitResponse(const unsigned char* pReq, int nReqLen, unsigned char* pResp, int* pRespLen, int nMaxRetry) { for (int i 0; i nMaxRetry; i) { PurgeComm(hCom, PURGE_RXCLEAR | PURGE_TXCLEAR); WriteFile(hCom, pReq, nReqLen, dwWritten, NULL); // 等待读取响应超时200ms if (ReadResponse(pResp, pRespLen, 200)) { return TRUE; } } return FALSE; }注意每次发送前先清空串口缓冲区否则上一次残留在缓冲区的脏数据会被当成响应帧解析导致状态错乱。3.4 多站轮询与数据刷新策略如果一条485总线上挂了多台PLC就需要用轮询的方式来采集数据。S7-200的PPI地址可以在STEP 7 Micro/WIN里设置默认站号为2。轮询时上位机依次向各站发送请求收到响应后再请求下一站。轮询周期要根据现场需要设置。一个常见的做法是每站发送完请求后等待响应响应超时后立即切到下一站不要卡在某一站上等待过久。总线上有3台PLC时就算其中一台掉线另外两台也要能正常刷新数据。有些现场对实时性要求不高可以把轮询周期设置在500毫秒左右CPU占用很低。4. 常见问题与调试心得4.1 通讯完全无响应的排查清单遇到通讯无响应的情况别急着改代码按下面的顺序排查检查项操作可能问题物理连接确认A/B线是否接反屏蔽层是否接地485接线反了导致无信号串口参数确认波特率9600、8位数据、偶校验、1停止位PPI对口参数设置错误PLC站地址STEP 7 Micro/WIN中查看系统块站号与报文DA不一致转换器驱动设备管理器中确认COM口号USB转485驱动未装好单独测试用串口调试助手发请求帧看是否回帧区分是软件问题还是硬件问题其中用串口调试助手发请求帧这个方法非常管用。你可以在VC程序里加一个调试开关把组好的帧以十六进制显示出来复制到串口助手里手动发送如果PLC有响应说明协议组帧没问题问题出在软件发送逻辑上如果PLC没响应那就从协议格式和物理链路上去找原因。4.2 偶发通讯失败的处理经验在现场最容易遇到的第二个问题是程序跑一阵子就通讯失败了重启后又好了。这种问题多半出在以下三个方面第一总线干扰。现场有变频器、伺服驱动器启动时会产生强烈的电磁干扰影响RS485通讯。解决办法是换用带屏蔽的双绞线屏蔽层单端接地485总线两端并联120欧终端电阻。如果条件允许在PLC的供电端加装隔离变压器。第二通讯帧间隔太短。PLC响应完上位机请求后需要一点时间处理内部逻辑。如果上位机发帧间隔太短PLC就可能来不及响应。我一般会在两次请求之间加10到50毫秒的延时牺牲一点速度换稳定性在工业现场是值得的。第三缓冲区脏数据导致状态机错乱。前面提到过串口是流式的如果接收缓冲区里有残留字节新的响应帧就会被错误解析。解决方法是每次发送前清空缓冲区响应的解析做成状态机遇到错误的帧头就跳过去重新找帧头。4.3 帧解析的细节处理收到响应后除了校验FCS之外还要检查响应中的数据单元长度是否跟请求匹配。PPI协议规定响应帧里的第一个数据字节表示DU长度这个值如果跟实际长度不符说明帧数据有丢失或错位。另外响应帧里还会带一个错误码字段通常在DU的末尾。当PLC返回错误码时并不是通讯有问题而是请求报文里的地址或者参数不合法。常见的错误码包括0x02地址越界。检查偏移地址是否超出了V区大小。0x03数据类型不匹配。读取字的时候用了字节的类型标志。0x04功能码不支持。这个PLC版本不支持你发的功能码。遇到错误码就按上面的提示去检查请求报文而不是怀疑通讯链路。4.4 自由口与Modbus RTU的备选方案最后再说一下备选方案。如果你的PLC程序里已经集成了Modbus从站指令S7-200有对应的库MBUS_INIT和MBUS_SLAVE那上位机可以直接用Modbus RTU协议来通讯。实现起来反而更简单标准Modbus帧结构CRC16校验03H功能码读保持寄存器06H写单个寄存器10H写多个寄存器。网络上CRC16的参考代码很多调通并不难。和PPI相比Modbus的好处是报文更短、帧间隔要求更低、轮询效率更高但坏处是你必须修改PLC程序把数据映射到指定的保持寄存器区。自由口通讯则适合对接自定义协议的第三方设备比如条码扫描枪、电子秤、RFID读卡器。S7-200的自由口功能用XMT和RCV两条指令就能实现收发上位机侧就按你自定义的帧格式解析即可。这一块就不展开说了但思路是通用的先约定帧头、长度、校验、结束符再按格式解析数据。5. 个人实操心得总结VC与S7-200通讯这件事看着简单真正做起来坑不少。我在实际项目里调试PPI通讯时最深的体会是协议细节比代码本身更重要。很多人上来就写代码结果收发逻辑写得很完整却不知道FCS要从第二个SD开始算也不知道偏移地址需要按绝对字节地址编码最后通讯调不通还以为是转换器坏了。如果你是从零开始做这个项目我的建议是先把报文格式在纸上画清楚用串口调试助手跑通一帧再写代码。磨刀不误砍柴工这步走顺了后面就是水到渠成的事了。另外工程现场环境复杂通讯线缆的选择和接地工艺直接决定通讯稳定性这点绝不能省。PCB级的USB转485模块和隔离型转换器差价不小但现场维护的成本差距更大。本文还有配套的精品资源点击获取
返回列表