刚入行那会儿,看Modbus协议跟看天书似的。寄存器、线圈、CRC、MBAP头,一个个名词堆上来,脑子全是浆糊。后来发现,不是这玩意儿难,是市面上教程写得忒绕。今天按我自己的理解路子,把Modbus从头捋一遍,争取让刚接触的人看完就能上手抓包。
一、先别碰代码,搞清楚Modbus到底是个啥
很多人一上来就翻代码,看NModbus源码,两眼一抹黑。正确姿势是先搞清楚三件事:
第一,Modbus是应用层协议。它不管数据怎么从A传到B,那是串口或者TCP的事。Modbus只规定数据长什么样、怎么问、怎么答。
第二,Modbus是主从模式。主站发问,从站回答。从站从来不主动说话,你问一句它答一句。这个设计简单粗暴,但也意味着从站有问题主站不知道,只能靠超时判断。
第三,Modbus只传16位整数。什么浮点数、字符串、32位整数,全是后来人们想出来的歪招。Modbus底层只认0x0000到0xFFFF,别的都不认。
这三条搞明白,心里就有底了。剩下的全是技术细节,一个坑一个坑填就行。
二、从一帧最简单的报文开始
别上来就搞什么01读线圈、03读寄存器,先看一帧最简单的请求长什么样。
假设你要读从站地址为1的设备,从地址0开始读2个保持寄存器。
RTU发出去的报文是这样的:
text
01 03 00 00 00 02 C4 0B
拆开看:
01:从站地址
03:功能码,读保持寄存器
00 00:起始地址,0x0000
00 02:读2个寄存器
C4 0B:CRC校验
就这么8个字节。你要是能对着这个报文把每一字节的含义说清楚,Modbus就算入门了。
设备回复的报文大概长这样:
text
01 03 04 12 34 56 78 9A 3F
拆开:
01:从站地址
03:功能码
04:数据字节数,4个字节表示2个寄存器
12 34:第一个寄存器的值
56 78:第二个寄存器的值
9A 3F:CRC
看明白了吧?主站问"把0号和1号寄存器的值告诉我",从站答"0号是0x1234,1号是0x5678"。就这么个对话,简单到令人发指。
三、功能码:就记四个常用的
Modbus功能码几十个,实际干活就四个。
| 功能码 | 名称 | 干啥用 |
|---|---|---|
| 01 | 读线圈 | 读开关量输出,比如继电器状态 |
| 02 | 读离散输入 | 读开关量输入,比如按钮状态 |
| 03 | 读保持寄存器 | 读16位数据,可读可写 |
| 04 | 读输入寄存器 | 读16位数据,只读 |
03和04的区别:03读的是PLC程序能改的数据,04读的是传感器直接采上来的数据。温度变送器用04读温度值,变频器用03读运行频率也写目标频率。
另外两个写的功能码:
05:写单个线圈(开关)
06:写单个寄存器(16位)
0F:写多个线圈
10:写多个寄存器
够用了,别的用上的概率极低。
四、地址到底怎么算?这事儿我专门拎出来说
新手最头疼的问题:手册说温度存在40001地址,你发报文时起始地址填多少?
答案是填0x0000。
"40001"是PLC厂家为了让人看懂搞的映射规则,跟协议本身没有半毛钱关系。Modbus协议里地址就是0~65535,没有前缀。
具体的对应关系:
线圈地址对应PLC地址:00001开始,协议地址从0开始
离散输入:10001开始,协议地址从0开始
输入寄存器:30001开始,协议地址从0开始
保持寄存器:40001开始,协议地址从0开始
所以你看到手册写"温度值存放在40001",意思就是功能码用03,协议起始地址填0x0000,读1个寄存器。手册写"40003",那协议起始地址就是0x0002。
协议地址 = PLC地址 - 40001。就这么简单。
有人非要杠"为什么手册写40001而不是0",别问,问就是历史遗留问题,记住换算规则就行。
五、RTU和TCP到底啥关系
很多人分不清RTU和TCP,以为这俩是两套完全不同的协议。其实它们的关系就一句话:
TCP是RTU去掉了CRC、前面加了个MBAP头。
看对比:
RTU请求:
text
[地址][功能码][数据][CRC]
TCP请求:
text
[事务ID][协议ID][长度][单元ID][功能码][数据]
相同颜色的部分,功能码和数据完全一样。TCP把地址改叫单元ID,把CRC砍了,前面加7个字节的头部。
所以只要你学会了RTU的报文格式,TCP就是个加了个头的变种。反过来也一样,把TCP报文去掉前7个字节、尾部加上CRC再算一遍,就变成了RTU报文。
两者传输的内容一样,只是包装不同。理解了这个,RTU和TCP在你眼里就是同一种东西的不同马甲。
六、CRC到底要不要手算
CRC-16-Modbus,多项式0x8005,初值0xFFFF。这个东西不学行不行?
行,但要会用工具算。
网上有在线CRC计算器,填上数据点一下就能出结果。调试阶段用这个完全够用。等你真的需要写代码实现通信了,直接从网上抄现成的CRC计算函数,哪个都行,别自己发明轮子。
但有一点必须清楚:CRC是低字节在前。算出来是0x1234,发的时候先发0x34再发0x12。这地方搞反了,CRC就算对了设备也不认,因为它按约定顺序接收。
七、动手抓一帧报文,比看十遍书都管用
这是最管用的一招。装个串口调试助手或者Modbus Poll,连上一个设备,发一个读寄存器的请求,把收到的原始数据复制出来。
然后对着每一字节分析:
第1字节是谁?
第2字节是什么功能码?
数据部分是哪些字节?
CRC是哪两个字节?
对着手册看一遍,当场就通了。比你看一百页PDF来得都快。
Modbus Poll这个软件特别好用,作为主站可以发各种请求,作为从站可以模拟设备响应。调试时用它发命令,看设备回什么,所有报文都显示在界面上。初期学习阶段,花半小时玩这个软件比读三章协议文档都有用。
八、常见错误:我见过的翻车现场
错误一:串口参数没对上
这是新手第一坑。9600 8 N 1是默认配置,但不是所有设备都用这个。你设9600,设备跑19200,能通就见鬼了。拿到设备第一件事,翻手册查串口参数页。
错误二:A/B线接反
RS485有极性,A接A,B接B。接反了能收到乱码但CRC全错。示波器看一眼波形,反了就是反的,别侥幸。
错误三:从站地址填错
设备地址设的是2,你发请求填1,设备不会理你。如果地址填0,那是广播地址,所有从站都收但不回复。
错误四:读的地址超限
手册说保持寄存器范围40001~40010,你发请求从40011开始读,设备回异常码02(非法地址)。然后你怀疑程序写错了,调了半天才想起来查手册。别问我怎么知道的。
错误五:一次读太多
RTU单帧最多读125个寄存器,读126个有些设备直接不回。TCP也差不多,虽然没硬性限制但设备缓冲区就那么大。
九、学习路线图:按这个顺序来
第一天:搞明白Modbus是主从模式,主问从答。看一帧报文长什么样,能对着报文把每个字节的含义说清楚。
第二天:上手Modbus Poll软件,连一个实际设备或者虚拟设备,发读请求看回复。把功能码01、02、03、04都试一遍。
第三天:把RTU的CRC算一遍,不用手算,用工具算但要理解低字节在前。把RTU和TCP的报文格式对比着看,找出相同点和不同点。
第四天:写代码。用C#或者Python,串口通信把SerialPort整明白,TCP通信把Socket整明白。先实现读寄存器,再实现写寄存器,别的慢慢加。
第五天:抓包调试。把自己代码发出去的报文和Modbus Poll发出去的报文对比,看看哪里不一样。CRC对不上就调CRC,地址对不上就调地址。
按这个节奏走,一周时间足够把Modbus用到项目里。
十、不要被"协议"两个字吓住
说穿了,Modbus就是规定了对话格式。你问问题用一种格式,对方回答用另一种格式,来来回回就这么点事。
复杂的地方不在协议本身,在现场环境。干扰、接线、参数配置、设备故障、厂家不按标准来,这些才是真正消耗时间精力的地方。
所以学习阶段别有压力。会看报文、会算CRC、会发请求,这就叫学会了。至于现场那些幺蛾子,那是经验的活儿,遇到了再解决,解决一次就记住了。
抓包抓包抓包,重要的事情说三遍。你手里有Wireshark和串口监听工具,就没有查不出来的问题。