听到“事务处理”这个词,别以为是财务在算账。在Modbus的语境里,它其实说的是“设备之间怎么互相打招呼、隔空干活”。
Modbus协议牛就牛在,它允许你在各种各样的网络体系里,进行极其简单的通信。
👨💻谁在用Modbus?
可以说是“神仙打架,全员参与”。不管你是工厂里的大脑(PLC)、带屏幕的操作台(HMI)、控制面板,还是各种驱动器和输入/输出设备(I/O),都能使用Modbus协议来启动远程操作,隔空指挥其他设备干活。
🛣️串口和网线,干活一个样
最神奇的一点是:只要动作和指令一样,你在老式的串行链路(拉根串口线)上能干的事,到了现代的以太网(TCP/IP)上,照样能干,而且干得一模一样!
——打个比方:你用对讲机(串口)喊“把3号阀门打开”,和你用微信(以太网)发“把3号阀门打开”,对于接收指令的设备来说,效果是完全等同的。底层的物理通道换了,但大家聊天的核心内容不变。
MODBUS 好用工具:
Modbus Master Emulator - Modbus 主站模拟器
Modbus Slave Emulator - Modbus 从站模拟器
“请求/应答”与“快递包裹”的那些事儿
在Modbus的世界里,聊天模式非常讲究“礼尚往来”——它是一个典型的请求/应答(Request/Response)协议。你问一句,我才答一句;你不问,我绝不主动烦你。为了方便大家交流,它还提供了一套统一的功能码(Function Code),专门用来指代各种数据传输服务。
关于功能码、PDU 和 ADU 这仨兄弟,咱们用“点外卖”和“寄快递”来打个比方🤗:
1. PDU(协议数据单元):最纯粹的“核心指令”
功能码其实就是 PDU 里的元素之一。所谓的 PDU,你可以把它理解为“包裹里真正的货物”。
它是一个与底层网络完全无关的“纯粹数据单元”。比如:“我要读取3号寄存器的数据”——这就叫 PDU。这句指令不管是在顺丰(串口)还是京东(以太网)上跑,核心意思永远不会变。
2. ADU(应用数据单元):打包好的“完整快递”
虽然有 PDU(核心货物)就够了,但在真实的马路上(特定的总线或网络),光有个裸货怎么行?
这时候,Modbus 协议就会给 PDU 穿上不同的“马甲”,加上一些附加域(比如设备地址、校验码),组合成一个完整的包裹,这就叫 ADU。只有成了 ADU,这个包裹才能准确无误地送到目的地。
📦 PDU 与 ADU 的“套娃”魔法
既然要通信,怎么才能让设备之间聊得既简单,又靠谱?
Modbus的设计师想了个绝招:先定义了一个极其精简的 PDU模型。
它有多精简?核心就两部分:功能码 + 数据。
这就好比你在信纸上只写了一句话:“帮我拿一下数据(功能码)+ 3号仓库(数据)”。
但是!光有一张信纸是寄不出去的,不同的网络(比如串口、以太网)对这个“信件”的格式要求不一样。为了让信件能适应各种传输模式,咱们得给它套个“大信封”。
于是,ADU模型 闪亮登场💥!它是在PDU的基础上,加了必要的“前缀”和“后缀”:
前缀(比如地址域):相当于信封上的“收件人地址”,告诉网络这信是发给谁的;
后缀(比如差错校验):相当于快递上的“封口贴”和“防伪标签”,防止信件在半路上被人拆了或者弄坏了。
💡 总结一下套娃公式:
ADU = 前缀(地址) + PDU(功能码+数据) + 后缀(校验)
这两者到底是咋套娃的?它们的亲密关系,直接看图1-4(看图时,你可以一眼看出PDU是那个核心的“心”,而ADU是包在外面的一层壳。)
前面讲了“PDU是信纸,ADU是信封”,那一次完整的“Modbus交易”(事务处理)到底是怎么跑完流程的?
——其实,这就好比一次“隔空点外卖”的完整过程,看图1-5之前,先理清这三步走:
🛵 第一步:主站下单(创建查询报文)
主机设备(或客户端)就像顾客,它把之前的“核心货物”(PDU)加上地址、校验码,打包成一个完整的查询报文(ADU),准备发送。
在这个包裹里,有一个最关键的“暗号”——功能码。功能码只占1个字节,它的作用就像是告诉店家:“我这次是要点菜(读数据),还是退菜(写数据)”。
🔢 第二步:从站接单(根据功能码干活)
从机设备(或服务器端)收到包裹后,第一件事就是看功能码,然后对号入座去执行相应的动作。
功能码的有效范围是十进制 1~255。但为什么 128~255 要被单独拎出来保留呢?因为在Modbus的规矩里,如果从站遇到事儿办不成(比如你点的菜卖光了,或者地址填错了),它就会用 128~255 这个区间的码字来回复主站:“老板,出异常了!”(专业叫法叫异常响应)。
📦 第三步:从站回传(返回响应报文)
从站把活儿干完之后,绝不会默默无闻,而是必须把结果打包成响应报文,原路返回给主机。至此,一次完美的“事务处理”才算闭环。
(配合文章里的图1-5,你可以一眼看出这个“你发我收,我再回你”的完整对话流。)
在“一问一答”的通信过程中,并不是每次都能顺风顺水。如果从站收到指令后发现了问题,它该怎么办?
✅ 顺利的情况:正常应答
如果从站正确接收了包裹,一看功能码,哎,这活儿我会干,也没遇到啥故障。那它就会乖乖按规矩,把你要的数据和正常响应报文打包,原路返回给主机。
❌ 翻车的情况:抛出“异常码”
如果从站遇到“与请求功能有关的差错”呢?比如:你让设备去读100号寄存器,结果它只有50个;或者你想写数据,结果没权限。
这时候,从站绝对不会装死不理你(也就是不能没有响应)。它会立刻回一封信,并且在响应报文的功能码域上动个手脚——塞进去一个“异常码”。
💡上文说到的功能码 128~255 就是为这个准备的!从站通常会把原来的功能码最高位变成1(也就是加上128),再加一个具体的错误代码告诉你到底出了啥事。
对于主机(客户端)来说,收到带“异常码”的报文,就像是收到了外卖客服发来的“退单理由”(比如:没食材了、地址不对、无权点单)。主机一看就秒懂了:哦,原来是这个环节出了岔子!接下来它就可以根据这个异常码,决定下一步该干嘛——是重新发一次请求、换个参数,还是直接拉响警报。
刚才说到,从站遇到问题会抛出“异常码”来通知主站。那这封“报错信”具体是怎么写的呢?来看图1-6:
当从站遇到差错,它返回报文时,不会重新编个新功能码,而是直接在原始功能码上“动个手脚”:
把这个功能码的最高有效位(最左边那一位)强行变成“1”。
为什么这么做?用大白话算个数:
在二进制里,一个字节有8位(比如 0000 0011,这是十进制的3)。如果你把最左边那一位(最高位)变成1,它就变成了 1000 0011。
你看,这数值是不是就变成十进制的 131(也就是原功能码 3 + 128)了?
💡 恍然大悟了吧!
这就完美解释了咱们前面说过的那个冷知识:为什么功能码 128~255 是专门留给异常响应的?
因为只要主站一看功能码的数值大于等于 128(最高位是1),立刻就能秒懂:“坏了,小弟没干成这事儿,这是个报错信号!”(比如,原本正常的03(读取寄存器)只要最高位一变,立刻变成 0x83,主站就知道:出异常了!)