ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

西门子S7-1500自由口实现Modbus RTU主站:从组帧到状态机

西门子S7-1500自由口实现Modbus RTU主站:从组帧到状态机 前阵子接手了一个项目现场有十几台仪表只支持Modbus RTU 从站协议主站是西门子1500。按理说最省事的做法是直接用TIA Portal里的官方Modbus RTU指令库但我还是选择了用CM PtP RS422/485 BA模块的自由口功能自己组报文、自己算CRC16把整个Modbus RTU主站程序一点点写出来。这个决定在当时看起来有点“绕路”等项目做完回头看反而是最值得的一步。这篇文章我把完整思路、硬件接线、报文帧结构、CRC16算法、轮询状态机和调试验证过程都整理出来。适合正在用西门子1500做串口通信、被各种非标设备通信协议折腾过的同行参考。如果你手头设备比较“规矩”直接用官方库就行但如果你需要适配特殊设备、想彻底吃透Modbus RTU报文或者遇到过官方库在某些场合不好用的坑这篇内容会很对胃口。1. 方案选型为什么用1500自由口自己写Modbus RTU1.1 现场需求与三套备选方案项目现场的工况说简单也简单PLC做主站若干台带RS485接口的仪表做从站通信数据量不大主要是读取测量值和少量写参数。但有一个特殊情况——这些仪表并不全是标准Modbus RTU协议其中一部分的报文格式被厂家改动过寄存器地址映射也和我们拿到的说明书有出入。换句话说现场存在“半标准、半私有”的通信报文。面对这种情况当时摆在我面前有三条路。第一条路直接用TIA Portal西门子官方的Modbus RTU指令库也就是Modbus_Comm_Load和Modbus_Master这两个指令。这个方案在标准Modbus设备上非常好用调用简单底层驱动、CRC校验、超时管理都帮你做好了拖两个指令块就能跑起来。但它的问题也很明显——程序内部封得比较死报文结构、校验方式、轮询逻辑都被固定住。如果要对某一条报文做“非标处理”反而要在外面套更多逻辑甚至出现指令库不支持的情况。第二条路用第三方协议转换网关比如485转Profinet网关让网关去和仪表通信PLC侧走Profinet IO。这条路稳定性不错等于把Modbus这块的脏活外包出去但成本上去了而且每台设备都要配映射表现场十几台设备配置量不小。最关键的是项目周期不允许我再等网关采购到货。第三条路就是标题里写的方案——用CM PtP RS422/485 BA模块的自由口功能在1500里自己实现Modbus RTU主站。S7-1500的CM PtP模块本身支持自由协议模式通过SEND_RECV、PORT_CFG这些系统指令我可以把每一帧报文完整地控制起来。发送的字节、接收的字节、CRC16、超时时长全部由我的程序说了算。对非标仪表我可以灵活调整请求帧内容对标准仪表我就按标准Modbus RTU报文格式组帧两边都能通。最终选了第三条路。主要原因有三个一是现场的非标兼容需求二是不想增加额外成本三是我个人对自由口这种“什么都自己管”的方式有把握这也是一个工控老手该有的基本功。1.2 自由口方案的技术边界与优点搞过S7-200 SMART自由口通讯的同行应该知道200 SMART里用XMT和RCV指令自己拼报文、控制超时思路已经比较成熟。到了S7-1500虽然指令名字变了变成PORT_CFG和SEND_RECV但骨子里的思想完全一样串口上的每一个字节都由应用层来决定PLC不关心也管不着一帧数据里到底是Modbus报文还是别的什么协议。用自由口实现Modbus RTU相当于把原来Modbus库帮你做的事全部接管过来。你需要自己解决四件事组帧、校验、时序、异常处理。这四件事听起来负担重但换来的是三个实打实的好处。第一个好处是对报文的绝对控制权。你可以发送任意字节组合的请求帧不受官方库的限制。比如某些国产仪表的功能码不是标准的03而是厂商自定义的64、65官方库很可能根本不会让你发这样的报文自由口没有任何问题。第二个好处是超时和轮询策略完全自定义。官方Modbus_Master里的超时管理、重试逻辑虽然够用但不一定贴合你的现场节奏。自由口下我可以精确控制每个从站的超时时间、站间切换延时、出错重试次数甚至遇到某个从站连续超时N次就把它跳过这种调度逻辑写起来非常顺手。第三个好处是排查问题更直观。自由口程序里报文是自己拼的接收到的原始帧也在自己缓冲区里放着用监控表或者上位机串口助手一对照马上就能定位是组帧错了、CRC错了、还是从站根本没回复。不像库函数出问题是个黑盒只能拿着状态码去翻手册。当然自由口方案并不是没有代价。它要求你对Modbus RTU协议有足够的理解CRC16算法、报文格式、字节序这些都要自己扛。如果设备全是标准仪表用官方库确实更省事。但如果是像我这样既想省钱、又想彻底弄懂协议本质的情况自由口是条值得走的路。2. 硬件接线与工程组态2.1 CM PtP 541-1AB00接线细节先说一下模块本身。S7-1500 CM PtP RS422/485 BA模块的订货号是6ES7541-1AB00-0AB0属于Basic版本适用于绝大多数点到点通信场景。和HF版本比起来BA版本没有电气隔离价格更便宜。如果你的设备之间电位差比较大、现场电磁干扰严重建议选HF版本。我做这个项目时设备都在同一个配电柜附近电位差不大用BA版本完全够用。模块正面有一个9针D-sub接口标号是X50。在RS485两线制模式下实际只用到两个针脚3号针脚是RxD/TxD-P也就是通常说的A或者8号针脚是RxD/TxD-N也就是B或者-。这里我吃过一次亏现场工人按习惯把红色线接到标着A的端子上、绿色线接到B上结果通信怎么调都不通。后来用万用表量了一遍才发现模块的3号脚和8号脚和仪表侧定义有细微差别重新核对说明书后把A接A、B接B才对上。所以接线前务必去看模块手册里X50的引脚定义不要凭颜色猜。如果传输距离超过几十米或者现场有变频器、接触器这种干扰源屏蔽双绞线必须用起来屏蔽层在PLC侧单端接地就够。两个终端电阻的问题我一并说一下如果只带一台设备、距离短终端电阻可开可不开如果总线上有超过3台设备或者距离超过100米建议在总线两端各加一个120欧终端电阻。但阻值要算得很准反而容易出问题实际项目中我更倾向于在从站侧通过设备菜单里的“终端电阻”选项打开PLC侧保持关闭保证总线末端有且只有一处终端匹配。模块上电前还要确认供电。CM PtP模块通过背板总线供电不需要额外接电源但CPU必须正常供电否则模块完全没反应。模块正面的几个指示灯里最有用的是TxD和RxD分别表示发送数据和接收数据。联调阶段我会盯着这两个灯看用手动触发一次通信如果TxD闪而RxD不闪基本可以判断是PLC能发出去但收不到回复问题大概率出在接线、从站地址或者从站本身。2.2 TIA博途组态与参数设置的三个关键点在TIA Portal中组态CM PtP模块不复杂但有几个细节值得单独拿出来讲。以TIA Portal V17为例项目树里添加设备后从硬件目录中找到“通信模块 点对点 CM PtP RS422/485 BA”直接拖到机架上。双击模块在“常规 接口 端口”里做三项关键设置。第一项是通信协议必须选成“自由协议”也就是Freeport。如果你在组态里选成了Modbus RTU协议那模块在硬件层面就被绑定到Modbus模式了程序里SEND_RECV的自由收发功能就不好使了。有些初学者这里选错了后面程序怎么调都怪怪的。第二项是工作模式选RS485。CM PtP模块同时支持RS422和RS485如果你只接两根线做半双工必须选RS485。选成RS422虽然也能收发但接线逻辑完全不一样新手很容易在这个选项上栽跟头。模块在RS485模式下会自动控制发送方向和接收方向的切换不用你在程序里额外控制RTS。第三项是通信参数。波特率、数据位、校验位、停止位要和从站设备完全一致这没啥好说的。但我要提醒一点Modbus RTU标准其实要求8个数据位校验位一般是无校验或者偶校验停止位1位。很多仪表默认是“8位数据、无校验、1停止位”但如果你现场设备要求偶校验程序里用的CRC算法并不受影响影响的是报文的最后两个字节位置之外的那些物理层校验。这些参数不只是组态里填一下就完事后面PORT_CFG指令里也要保持一致否则程序跑起来后模块实际生效的参数会和你的预期不一致。组态完成后打开PLC变量表里的“系统常量”标签页找到和CM PtP模块对应的硬件标识符通常叫“Local~CM PtP_1~Port”。这个标识符是个十六进制数比如269之类的后面写程序时PORT_CFG和SEND_RECV都要用到它。我建议把它复制出来放到PLC用户常量里起个名字叫“HwID_CMPtP_Port”这样程序可读性好很多也不容易把标识符抄错。3. 自由口程序实现从报文到状态机3.1 Modbus RTU报文帧结构与03功能码详解既然要自己写Modbus RTU主站报文帧结构就必须吃得很透。这里我用最常用的03功能码读保持寄存器来拆解。标准的Modbus RTU请求帧格式如下从站地址1个字节范围1到2470是广播地址248以上是扩展功能码区段。功能码1个字节03读保持寄存器。起始寄存器地址2个字节高字节在前表示要读取的第一个寄存器地址。寄存器数量2个字节高字节在前表示要连续读取多少个寄存器。CRC16校验2个字节低字节在前高字节在后。举个例子要读1号从站、从寄存器地址0开始、连续读10个寄存器请求帧就是01 03 00 00 00 0A C5 CD这里01是从站地址03是功能码00 00是起始地址高字节和低字节00 0A是寄存器数量C5 CD是前面5个字节01 03 00 00 00 0A算出来的CRC16低字节C5在前高字节CD在后。从站正常响应帧格式从站地址1字节回显请求帧中的地址。功能码1字节03。字节数1字节后面数据的总字节数等于寄存器数量乘以2。寄存器数据N个字节每两个字节对应一个寄存器高字节在前。CRC16校验2字节同样低字节在前。比如从站回01 03 14 00 01 00 02 ... A1 2B01是站号03是功能码14是16进制的20表示后面有20个字节数据也就是10个寄存器每个寄存器占2字节。数据部分前4个字节00 01 00 02就表示寄存器0的值是1寄存器1的值是2。这里有个容易弄混的事情是寄存器地址和协议地址的偏移。很多仪表说明书里说“寄存器地址是40001”对应Modbus报文里的协议地址是0x0000说“寄存器地址是40011”对应协议地址是0x000A。所以组请求帧时要把说明书地址减1再转成十六进制填入报文。我见过不少工程师卡在这里在PLC里把40001直接填成16#40001发出去从站根本不理你。如果请求的地址或者寄存器数量超出从站允许范围从站会回异常帧。异常帧结构是从站地址 功能码最高位置1比如0x83 异常码 CRC。异常码常见的有01表示非法功能码02表示非法数据地址03表示非法数据值。我在程序里遇到异常帧之后会先把原始帧存下来再报警后面排查非常方便。如果你还想深入理解其它功能码比如06写单个寄存器、10写多个寄存器报文结构也是同理功能码换成对应数值、数据段变成要写入的值即可。掌握了03的拆解方法其它的基本上能举一反三。3.2 CRC16计算原理与SCL实现CRC16是Modbus RTU最容易写错、也最容易排查的部分。网上流传着各种版本的CRC算法查表法、位运算法、还有各种C语言代码。这里我给出直接能在S7-1500的SCL里跑的位运算版本逻辑最清晰也最好验证。Modbus RTU使用的CRC16多项式是0xA001初始值是0xFFFF。计算过程一句话概括把每个字节和当前的CRC值按位异或然后逐位向右移位每移一位如果最低位是1就再和0xA001异或处理完8位后换下一个字节所有字节处理完得到的16位值先发低字节再发高字节。SCL实现我写成独立的函数块方便在主站程序里复用FUNCTION_BLOCK FB_CRC16 VAR_INPUT data : ARRAY[0..255] OF BYTE; // 待校验数据 len : INT; // 数据长度 END_VAR VAR_OUTPUT crc : WORD; // 计算结果 END_VAR VAR i : INT; j : INT; temp : WORD; tmpByte : BYTE; END_VAR BEGIN temp : 16#FFFF; FOR i : 0 TO len - 1 DO tmpByte : data[i]; temp : temp XOR WORD(tmpByte); FOR j : 0 TO 7 DO IF (temp AND 16#0001) 16#0000 THEN temp : SHR(temp, 1); temp : temp XOR 16#A001; ELSE temp : SHR(temp, 1); END_IF; END_FOR; END_FOR; crc : temp; END_FUNCTION_BLOCK这段代码里的核心是WORD(tmpByte)类型转换。SCL里WORD和BYTE不能直接异或必须先用WORD()把BYTE转成WORD再运算否则编译会报类型不匹配。这个细节坑过我一次当时编译报错我还以为是函数块结构问题查了半天。实际使用时把请求帧的字节先放进一个BYTE数组然后调用FB_CRC16返回的crc就是要填入报文的校验值。发送时要把低字节放在前面、高字节放在后面。比如算出来crc 16#CDC5那帧末尾就填C5 CD顺序不能反。如果你做项目讲究效率可以用查表法预先算好256个CRC表常量用查表代替逐位循环速度会快一些。但对于9600波特率的串口通信每个请求帧只有8个字节位运算法的这点CPU开销完全可以忽略。我在这个项目里就用位运算法图的就是逻辑直白、容易看懂。另外要强调一点CRC计算的范围是除了CRC本身的全部字节。比如请求帧01 03 00 00 00 0ACRC算的是这5个字节响应帧01 03 14 后面跟20个数据字节CRC算的是从01到数据最后一个字节的整段不包括CRC两字节本身。3.3 主站轮询状态机与SEND_RECV指令调用自由口Modbus RTU主站的核心是时序控制。串口是半双工物理链路一问一答的顺序必须严格不能同时发送又接收。我采用一个简单的状态机来管理整个轮询过程状态迁移如下空闲准备下一轮请求帧找到下一个要通信的从站地址。发送触发SEND_RECV发送请求帧等待发送完成。等待接收发送完成后启动接收使能同时启动超时定时器。解析成功校验CRC、解析响应帧把数据写入映射区然后回到空闲状态准备下一站。超时/失败记录错误计数器跳过当前站回到空闲状态准备下一站。SEND_RECV指令在TIA Portal里的路径是“指令 通信 其他 点对点”拖到OB1或者FB里之后系统会要求关联一个背景DB。这里我把PORT_CFG和SEND_RECV放在同一个OB100初始化块里先调PORT_CFG配置参数再在循环OB里调SEND_RECV发收发收。注意SEND_RECV的输入引脚里有一个PORT参数要填CM PtP模块的硬件标识符别填成CPU自带的PROFINET接口标识符这个我见过有人填错。下面是一段简化后的SCL状态机框架CASE step OF 0: // 组装下一站的请求帧 build_frame(actAddr, 16#03, 0, 10, sendBuf); step : 1; sendReq : FALSE; recvEnable : FALSE; 1: // 发送请求 sendReq : TRUE; recvEnable : FALSE; IF sendDone THEN sendReq : FALSE; step : 2; waitTimerStart : TRUE; END_IF; 2: // 等待从站响应 recvEnable : TRUE; IF recvDone THEN // 校验接收长度、CRC然后解析 step : 3; ELSIF waitTimeout THEN errCount[actAddr] : errCount[actAddr] 1; step : 0; // 超时跳下一站 END_IF; 3: // 解析响应 parse_response(); actAddr : NEXT_ADDR; step : 0; END_CASE;实际程序里我不会写得这么简单有几个细节值得说一下。首先是发送和接收的使能切换。SEND_RECV组合指令里有两个使能端EN_R控制接收EN_S控制发送。我参考S7-200 SMART时代XMT和RCV配合使用的老经验发送时把接收使能关掉发送完成后再开接收使能。这样做的原因是半双工RS485上模块虽然会自动切换方向但如果发送还没结束就把接收使能打开有可能把自己发出去的回波收进来。尽管CM PtP模块比老200 SMART智能得多但养成这个习惯能避免很多莫名其妙的问题。其次是超时定时的位置。这里不能用一个落后的TON定时器拖着因为轮询是周期性的状态机每次扫描都可能在切换。我直接用程序里的循环时间累加加上数据块里的触发标志简单可靠。超时时间一般设200毫秒。现场有些仪表响应慢如果设太短明明能通也误报超时我把时间设长到500毫秒做了一次测试稳定后再改回200毫秒。第三是从站切换策略。我的做法是轮询所有从站如果某个从站连续超时5次就把它从轮询列表里暂时剔除放到故障表里报警让主站集中精力去轮询健康的从站每过30秒再尝试一次离线站。这个策略在现场很实用一台仪表掉线整条总线卡住的问题靠这种调度就能避免。还有一个工程细节是数据区规划。我在FB的背景DB里定义了两个BYTE数组一个发送缓冲区、一个接收缓冲区长度都设256字节。Modbus RTU单帧最大报文就是256字节左右这个长度足够大多数场景。每个从站读取到的寄存器值我统一存到一个二维数组或结构体数组里按从站号索引这样触摸屏或者上位机读取时非常方便。4. 联调实录与问题排查4.1 实验室模拟从站与报文验证这套自由口程序写完之后最忌讳的事情就是直接拿现场设备调试。万一程序有Bug不仅耽误时间还容易把从站设备搞出问题。我习惯先在实验室里用电脑搭一套完整的仿真调试环境验证每个细节都正确了再上现场。第一步在电脑上启动Modbus Slave模拟软件。这个工具可以模拟一个标准的Modbus RTU从站设置好从站地址、功能码、寄存器数据。我用的是Modbus Slave 9.x设置从站地址1选03功能码然后在寄存器表里填几个测试值。第二步把CM PtP模块的RS485接口通过USB转485适配器连接到电脑。USB转485适配器一定要买支持自动收发切换的正规产品那种用跳线手动切换收发模式的适配器调起来非常痛苦。插上后在电脑设备管理器里记住分配的COM口号然后在Modbus Slave里配置成对应的COM口、9600波特率、8数据位、无校验、1停止位。第三步在PLC程序里做一个手动触发变量。我在OB1里加了一个“手动发送请求”BOOL变量置位一次就发一次03请求帧。触发后在TIA Portal监控表里查接收缓冲区的原始字节。如果发过去的请求是01 03 00 00 00 0A C5 CDModbus Slave里能看到接收计数增加同时PLC接收缓冲区里应该收到01 03 14后面20个字节再加CRC。这个方法的好处是能同时验证三个方面PLC发送的请求帧格式是否正确、CRC值是否算对、从站模拟器返回的响应帧能不能被正确接收和解析。只要这三步全通程序的核心逻辑就是可靠的。第四步是加干扰测试。我会故意改错CRC把代码里的CRC取反模拟发送坏帧看看从站模拟器会不会回异常帧或者不回。这种测试对验证程序的异常处理分支很重要能确认超时和错误计数逻辑确实在工作。4.2 现场调试常见故障速查表联调过程中踩过的坑我收集成了一个速查表。现场通信有问题时按这个表一项项排查基本都能解决。常见故障现象和对应原因PLC发送灯闪、接收灯不闪从站无任何响应大概率是从站地址和请求帧里的地址不一致或者PLC侧A/B线接反了。发送灯不闪程序里状态卡在发送步骤查SEND_RECV的硬件标识符是否正确查PORT_CFG有没有在OB100里成功执行过查模块组态是否选成自由协议。有响应但程序一直报CRC错误检查CRC在帧里的字节顺序是不是低字节在前检查接收到的响应帧长度是否包含了完整的数据段特别是字节数、数据长度是否匹配。偶发性超时重试后能通看现场有没有变频器干扰检查屏蔽层和接地检查波特率是不是太低跟不上检查总线终端电阻是否配置正确。仪表上报文内容正确但PLC解析出的数值完全不对多半是寄存器字节序问题。有的仪表数据是大端有的仪表是小端需要按实际结果调整解析时的高低位组合。这里我多说一句字节序的问题。西门子PLC的WORD在内存里本身就是高字节在前、低字节在后和Modbus协议里寄存器数据高字节在前是天然一致的。但如果你用BYTE数组接收了响应帧然后把两个BYTE手动拼成一个WORD必须按“高字节左移8位加低字节”的方式拼顺序拼反了数值就会差得离谱。我在解析函数里写了一句value : SHL(BYTE_TO_WORD(recvBuf[i]), 8) OR WORD(recvBuf[i 1]);这一句依赖数据在数组里连续存放实际项目中要根据你的接收缓冲区偏移量调整。4.3 几条保命的工程经验文章写到这把压箱底的几条工程经验一并分享出来都是常规手册里不会写的东西。第一条是“先慢后快”的调试原则。第一次联调时把波特率降到9600、把轮询周期拉长、把超时时间设到1秒把链路调通了再逐步提速。串口通信出问题时的表象五花八门但绝大多数问题根源是物理链路或者参数不一致。先慢速跑通等于把变量缩小到最容易排查的范围。我见过有同事一开始就上115200出了故障连是线的问题还是程序的问题都分辨不出来。第二条是通信程序要加“总开关”和“单步触发”。我在程序里专门留了三个调试用的控制位通信使能位、单次触发位、调试暂停位。通信使能位控制整个轮询周期运行还是停止单次触发位用于手动发送一帧请求调试暂停位用于快速冻结当前状态。调试时这三个位配合起来几乎可以替代串口逻辑分析仪完成大部分判断。等系统稳定后这三个位依然保留在触摸屏的工程师权限页里现场服务时特别有用。第三条是记录历史通信日志。我的程序里维护了一个环形缓冲区每完成一次通信就把请求帧、响应帧、校验结果、耗时一并存下来最多存最近20条记录。这些数据平时不显示但在故障复盘时价值巨大。有一次现场说设备偶发通信失败我到现场调出日志一看失败往往发生在某台变频器启动瞬间几分钟就锁定了干扰源。如果没有日志这种间歇性故障排查起来非常痛苦。还有一条是关于S7-200 SMART自由口的经验迁移。早年在200 SMART上做自由口XMT指令触发后RCV指令要预先处于接收状态而且发送和接收共用一个缓冲区时经常踩坑。到了1500平台SEND_RECV指令把收发逻辑封装得更好但“发送完成后别急着开接收”的老经验照样适用。建议不要因为模块硬件自动管理方向就放松软件时序软件层面严格一问一答永远是最稳妥的做法。我个人在实际项目里最大的体会是Modbus RTU本身并不复杂真正复杂的是现场无穷无尽的环境因素。通信不上先查线、再查参数、再查报文不要一上来就怀疑程序。自由口方案让我的程序把每一个通信细节都掌握在手里排查问题时不需要拆黑盒这是它最不可替代的价值。
返回列表