简介:针对西门子博图(TIA Portal)S7-1200平台的MODBUS轮询程序文档资料,面向工业自动化初学者与现场调试工程师,帮助理解并实现PLC与多从站设备间的MODBUS RTU/TCP通信,重点解决轮询机制、寄存器映射与通信异常处理等常见问题。压缩包为zip格式,大小约1.03MB,共38个文件,主要包含工程配置相关文件(如cfs、xml、db)及程序文档说明(如dat、log等),覆盖SIMATIC S7-1200项目结构中的用户程序、数据块、系统配置等典型内容,便于对照学习。已有3438人浏览学习。读者可通过该资料快速掌握在博图环境中创建MODBUS连接、编写轮询循环、解析响应数据及添加错误重试与调试优化方法,并且资源中附带的实际项目目录结构有助于理解工程文件的组织方式,为后续独立开发多设备通信程序打下扎实基础。 搞自动化这些年,只要是做过设备联网的项目,几乎都绕不开同一个场景:一台PLC,底下挂着好几台变频器、仪表、温控器,它们平时各干各的活,但PLC想把数据收上来,就得靠MODBUS一根线串起来。博图里实现MODBUS主站很简单,调用MB_MASTER指令就行,可真正上手就会发现一个非常现实的问题——这个指令每一次只能处理一个请求,现场挂了十个八个从站,程序怎么写才能让它们有条不紊地“排队”?这就是本篇要说的博图MODBUS轮询程序。
这篇文章适合刚接触PLC通信、对MODBUS协议还在摸索阶段的工程师,也适合已经在现场调试却被轮询逻辑搞到头大的同行。我会从方案设计讲起,逐步拆解一个可以直接拿去改用的SCL状态机模板,然后把我在现场调试中遇到的几个典型问题拿出来逐条说透。读完你至少能解决两个问题:第一,知道轮询程序的骨架该怎么搭;第二,现场报错时知道从哪下手排查。
1. 搞懂MODBUS轮询的本质,方案才不会走偏
1.1 MB_MASTER的工作方式:一次只能干一件事
MODBUS是一个主从问答协议,主站发请求、从站回响应,一问一答,严格按顺序来。博图里把主站功能封装成了MB_MASTER指令,你在REQ引脚给一个上升沿,它就会根据填好的参数发送一帧报文,然后通过DONE、BUSY、ERROR三个输出反馈执行结果。从指令本身来看并不复杂,复杂的是“一次只能处理一个请求”这个约束。
这里有几个新手很容易忽略的关键点:MB_MASTER的同一个实例,同一时刻只能有一个任务在运行,如果你有两个从站,却不在程序里做调度,直接在两个地方同时对同一个MB_MASTER实例发REQ,最终结果一定是逻辑混乱。串口是半双工通信,特别是RS485,同一根物理线路上同一时刻只能存在一帧数据,所以“同时读写”在物理层面就不成立。还有工程师喜欢给每个从站都建一个独立的MB_MASTER实例,这其实没有必要,因为底层占用的还是同一个物理口,不仅不能提高速度,反而多了一大堆互斥逻辑要处理。
所以,轮询程序的本质不是“让MB_MASTER跑得更快”,而是设计一个调度器,把所有读写请求排成一个队列,按顺序逐条执行、逐条确认,全部执行完再从头开始下一轮。
1.2 轮询策略怎么选:顺序、按需还是混合
根据项目实际需求,我习惯把轮询策略分成三类,大家可以直接对号入座:
- 固定顺序轮询:从站地址从小到大,先读1号、再读2号、再读3号,整轮走完再回头从1号开始。这种策略最常用,代码最简单,时序也最好判断,PLC扫描周期内执行到哪个站一眼就能看出来。
- 按需轮询:只在按钮按下、或者某条指令下来的时候,才去读指定从站、执行指定写操作,做完再回到常规轮询。这种适合那些不常交互的从站,比如只在设备开机时下发一次参数。
- 优先级混合轮询:对关键站点提高刷新率,比如1号站每轮都读,2号站每隔一轮读一次。现场从站多、又要保证关键设备数据及时性的时候用这种,但逻辑复杂度和调试成本会明显上升。
我的建议是:不要一上来就做复杂的动态调度。先用固定顺序轮询把整条通信链路跑通,确认所有从站都能稳定响应了,再在状态机里加条件分支。顺序轮询改造成另外两种非常容易,反过来一上来就写复杂调度,出了问题排查起来会特别痛苦。
2. 轮询程序的骨架:状态机与数据结构设计
2.1 状态机设计:一个整数搞定全部调度
轮询程序最稳的写法是用一个“步骤编号”构建状态机。为什么不能用定时器按固定周期一帧一帧地发请求?因为MODBUS的响应时间不是固定的,设备处理速度、线路干扰导致的重试,都会让每一帧的完成时间产生波动。如果定时器时间设短了,上一帧还没返回结果、下一帧请求就发出去了,整个总线节奏直接乱套;时间设长了,轮询周期又浪费一大截。
状态机的核心思路是“完成一个,再发下一个”。每一步只干一件事:当前步骤的请求发出后,程序停在等待状态;只有收到DONE或ERROR,才把步骤号切到下一步。超时由MB_MASTER内部处理,轮询层不需要再做额外超时判断。这样无论从站响应快还是慢,程序都能自动适应,总线永远保持一问一答的节奏。
我习惯把步骤号设计成大区间套小区间,比如0到9是初始化区,把各种标志位复位;10到19是1号从站的流程区间;20到29是2号从站的;30到39是3号从站的。每个从站的区间再做细分:发请求一步、等完成一步。这样调试监控时,看到当前步骤号是25,就知道正在访问2号从站,并且处于第二阶段,效率会高很多。
2.2 数据区规划与地址映射对照表
MODBUS的地址体系跟PLC的数据地址并不是一回事,MB_MASTER的MB_DATA_ADDR不能随便填。我刚做项目时就在这里吃过亏,填了个40001以为就是普通寄存器号,实际上它指的是“保持寄存器的起始地址”。我把实际编程中最常用的地址映射关系整理成了表格,方便对照使用:
| 地址范围 | 数据区类型 | 支持功能码 | 典型用途 |
|---|---|---|---|
| 00001-09999 | 线圈(Coil) | 01读、05写单个、15写多个 | 启停命令、开关状态 |
| 10001-19999 | 离散输入(Discrete Input) | 02读 | 外部开关信号采集 |
| 30001-39999 | 输入寄存器(Input Register) | 04读 | 模拟量采集、实时测量值 |
| 40001-49999 | 保持寄存器(Holding Register) | 03读、06写单个、16写多个 | 参数读写、数值通信 |
在博图的MB_MASTER指令里,功能码会依据MB_DATA_ADDR所在的区间自动识别,但MB_MODE还是要自己指定方向:0表示读,1表示写。比如读保持寄存器,MB_MODE填0,MB_DATA_ADDR填40001;写保持寄存器,MB_MODE填1,地址同样是40001。方向搞反是新手最常见的报错原因之一。
数据区方面,我的做法是创建一个独立的全局DB存放各从站的交换数据。如果一个站的数据长度都相同,用二维数组最方便,例如Data_Station: Array[1..10, 0..9] of Word,配合MB_DATA_PTR指向对应位置即可。程序里连站号都可以用变量驱动,将来增删站点基本不用改逻辑结构,这是后期维护最大的省心点。
3. 用SCL实现一个可复用的轮询状态机
3.1 直接可用的SCL模板
下面这个模板来自我实际项目,场景是2个MODBUS从站、每个站读取10个保持寄存器,硬件用S7-1200配CM1241 RS485模块,博图里通过MB_COMM_LOAD完成串口初始化,协议选RTU。你拿到手之后,把地址、长度、数据块名称改一下就能直接验证。
// 轮询状态机主逻辑 // #pollStep : 步骤编号 // #mbReq : MB_MASTER的REQ触发信号 // #mbDone : MB_MASTER的DONE完成信号 // #mbError : MB_MASTER的ERROR错误信号 // #mbStatus : MB_MASTER的STATUS状态码 // #dataLen : 读取长度,此处按10个字处理 CASE #pollStep OF 0: // 初始化,确保所有标志位处于复位状态 #mbReq := FALSE; #pollStep := 10; 10: // 发起对1号从站的读保持寄存器请求 #tempAddr := 40001; // 保持寄存器起始地址 #tempLen := 10; #mbMode := 0; // 0 读 #mbReq := TRUE; #pollStep := 11; 11: // 等待1号从站本轮请求完成 IF #mbDone THEN #mbReq := FALSE; #pollStep := 20; // 完成,进入2号从站 ELSIF #mbError THEN #mbReq := FALSE; #errStation := 1; // 记录故障从站号 #errCode := #mbStatus; #pollStep := 20; // 出错也要继续下一个 END_IF; 20: // 发起对2号从站的读保持寄存器请求 #tempAddr := 40001; #tempLen := 10; #mbMode := 0; #mbReq := TRUE; #pollStep := 21; 21: // 等待2号从站本轮请求完成 IF #mbDone THEN #mbReq := FALSE; #pollStep := 0; // 回到初始化,开始下一轮 ELSIF #mbError THEN #mbReq := FALSE; #errStation := 2; #errCode := #mbStatus; #pollStep := 0; // 出错也进入下一轮 END_IF; END_CASE;这段SCL写在OB1里,或者写在一个基于周期扫描的FB里都可以。MB_MASTER指令在每个扫描周期都要调用,REQ接#mbReq,MB_MODE接#mbMode,MB_DATA_ADDR接#tempAddr,MB_DATA_LEN接#tempLen,MB_DATA_PTR指向你要存放数据的DB块。这里的#tempAddr在博图SCL里建议用DInt或UDInt类型,因为40001这个地址超过Int的正数范围了,用Int会溢出。这个细节容易踩坑,先提醒一句。
还有一点:REQ的置位和复位要跟状态步骤严格对应。状态10把#mbReq置TRUE,下一步进入11,之后在等待期间不要再对#mbReq做任何赋值,直到DONE或ERROR出现后再复位。很多新手习惯在每个周期都写上#mbReq := TRUE,结果MB_MASTER收到的是一串持续触发的上升沿,逻辑自然就乱了。
3.2 参数说明和扩展思路
上面的模板虽然简单,但已经能覆盖70%以上的现场需求。如果你想扩展到更多从站,不建议把状态10、20、30这样无脑复制下去,更好的做法是用“站点索引+配置数组”驱动。提前建一个结构体数组,每个元素包含从站地址、寄存器起始地址、读写长度、读写方向,然后轮询状态机固定在几个步骤之间循环,只是每轮切换索引去读取对应配置。这样做的最大好处是增减从站时只改配置数组,不用动状态机逻辑。
MB_MASTER在调用时有几个关键参数需要特别注意。MB_DATA_PTR要求指向实际存储数据的数据块区域,如果用VARIANT方式,一般可以指向优化访问的DB,但为了减少不必要的兼容性问题,我习惯将交换数据放在非优化的全局DB里。DISCONNECT引脚是针对MODBUS TCP的,RTU模式下保持FALSE即可。
还有一个很实用的小技巧:如果某个站的数据需要做4字节浮点数转换,比如把两个连续的Word组合成一个Real,用博图的MOVE指令加POINTER偏移去读会很绕,我更建议直接在SCL里用#wordLow + #wordHigh * 65536或者用BITAND、SHL做拼接,然后把结果MOVE到Real变量里。虽然多几行代码,但逻辑一目了然,现场排查数据对不上的时候能少掉很多头发。
4. 现场调试实录:轮询常见问题与STATUS错误码速查
4.1 轮询中最容易踩的四个坑
先说说我在现场踩过的坑,每一个都是真金白银换来的教训。
第一个坑是REQ信号残留。有人把MB_MASTER放在OB1里,然后直接把系统时钟的上升沿接到REQ上,结果发现程序运行时好时坏,过一会儿就报错。原因就是REQ如果一直以固定周期触发,MB_MASTER会认为不断有新的请求进来,但任务队列并没准备好,状态码就会乱。正确做法是用状态机去控制REQ,一个请求发起后,DB块里的数据区已经被占用,必须等DONE或ERROR回来,才能复位REQ并发下一个请求。
第二个坑是从站地址重复。现场曾经出现过两台仪表出厂默认地址都是1,结果轮询到其中一个站时,另一台也抢着响应,CRC校验一会对一会错,数据跳动非常厉害。排查方法很简单,用上位机MODBUS调试工具单独去读每个从站,确认地址唯一且响应正常。如果设备本身不支持改地址,那就只能一台一台单独拉线配置。
第三个坑是终端电阻和线缆敷设问题。RS485总线两端要各接一个120欧姆终端电阻,很多人觉得距离短就不接,结果轮询开始正常、运行几分钟后偶尔超时,尤其是在变频器启动的瞬间特别明显。后来把双绞线换成带屏蔽层、屏蔽层单端接地后,问题彻底消失。MODBUS RTU对线路质量远比想象中敏感,别拿普通平行线凑合。
第四个坑是轮询周期设得太快。有的工程师觉得轮询越快越好,把每个请求的等待时间压得很紧,结果设备响应稍慢就超时。要知道很多仪表内部是单片机慢慢处理的,几百毫秒响应延迟很正常。轮询周期的下限要由“所有从站的响应时间之和”决定,而不是由程序扫描周期决定。通信稳定永远优先于刷新速度,数据晚100毫秒刷新,现场可能毫无感觉,但通信乱掉,整个系统都要报警。
4.2 STATUS错误码速查表与排查思路
博图MB_MASTER的STATUS引脚会输出一个16进制错误码,我整理了一批在现场最常出现的:
| STATUS | 含义 | 常见原因与处理建议 |
|---|---|---|
| 16#0000 | 无错误 | 正常完成 |
| 16#8180 | 指令参数错误 | 检查MB_DATA_ADDR是否超出对应数据区范围、MB_DATA_LEN是否合理 |
| 16#8200 | 请求超时,从站无响应 | 查线路、查从站供电、查站地址是否匹配、查波特率和校验位 |
| 16#8381 | 接收到的报文CRC错误 | 线路干扰、接线过长、终端电阻未接、波特率设置不一致 |
| 16#8382 | 从站返回的地址不匹配 | 请求地址和响应地址对不上,可能存在多个从站地址冲突 |
| 16#8384 | 从站返回MODBUS异常响应 | 从站协议层报错,常见异常码有01非法功能码、02非法地址、03非法数据 |
| 16#818B | 数据长度错误 | MB_DATA_LEN超出允许范围,确认与从站寄存器区长度一致 |
看到16#8381先别怀疑从站,优先检查通信线缆和接地,这是性价比最高的排查顺序。看到16#8200,再从站号、波特率、设备上电状态这三个方向逐个排除。每次改完线或者改完参数,都建议重新上电后再观察,很多通信问题在冷启动后才会暴露出来。
关于波特率,实际使用中9600和19200最主流,通讯距离越长、波特率就要越低。9600波特率下,读10个保持寄存器的报文约33个字节,每字节约1.04毫秒,加上设备响应时间和帧间隔,一个从站单次请求约需要50到100毫秒。如果挂8个从站,一整轮轮询大概0.5到1秒,这个刷新速度对绝大多数过程量监控是够用的。硬要追求更快的刷新,就该考虑换MODBUS TCP或者调整协议架构了。
最后再说个很多人问过的点:博图版本从V16到V20,MODBUS轮询的编程思路和指令接口基本没变,MB_MASTER的位置和参数都高度一致,所以不管你电脑里装的是哪个版本,这个状态机模板直接套用都没有问题,真正影响的只是库的版本号和编译细节。
做轮询程序,我最大的体会是九成故障不是代码量不够,而是时序意识不够——总觉得发了请求就该马上有响应,结果一个不严谨的REQ触发把整条总线的节奏全打乱了。我现在的习惯是先画一遍步骤流程图,再动手写SCL,写完一个状态区间就单步确认一次,宁可轮询慢一点,也要保证每一帧都干净利落。希望这篇内容能帮你少踩几个坑,哪怕只是让你在现场排查时多一个排查方向,也算值了。
本文还有配套的精品资源,点击获取