Modbus RTU/TCPModbus报文解析和示例

1, Modbus地址区的0x、1x、3x、4x各代表什么?如何区分和应用?

地址区名词读/写地址范围别称可用功能码
0x线圈状态/离散量输出(coil status)可读/可写00001~09999对应PLC: DO01、05、15
1x 离散量输入(inputs status 只读10001~19999对应PLC: DI02
3x 输入寄存器(input registers只读30001~39999对应PLC: AI04
4x 保持寄存器(holding registers可读/可写40001~49999对应PLC: AO03、06、16

解释:0x、1x、 3x、4x各是一片数据区,它是5位十进制地址(16bit),是Modbus协议规定的地址范围,各类地址是互相对应的,但在实际编程中,由于前缀的区分作用,所以只需说明后4位数,而且需转换为4位十六进制地址。有些资料就以其第一个数字区分各类地址。这些只是modbus协议规定的地址范围区, 并不参与实际协议数据传输。而其中功能码是真实参与协议传输的。如要操作0x地址数据区,就只能用规定的功能码01、05,而不能用其他功能码。所以,从各类地址的前缀就能知道它的数据区及功能码,反之亦然。

2, 功能码:

功能码(16进制)功能位/字操作操作数量访问类型
0x01读线圈状态位操作单个或多个单bit访问
0x02读离散输入状态位操作单个或多个
0x05写单个线圈位操作单个
0x0F写多个线圈位操作多个
0x03读保持寄存器字操作单个或多个16bit访问
0x04读输入寄存器字操作单个或多个
0x06写单个保持寄存器字操作单个
0x10写多个保持寄存器字操作多个
0x16掩码写入寄存器字操作多个
0x17读/写多个寄存器字操作多个
0x18读FIFO队列字操作多个
0x14读取文件记录文件记录访问
0x15写入文档记录
0x07读取异常状态诊断
0x08诊断
0x0B获取Com事件计数
0x0C获取Com事件日志
0x11报告服务器ID

常用modbus功能码:01、02、03、04、05、06、15、16共8个
这8个功能码可以操作四块分区,其中两块boolean(布尔变量)寄存器,两块整数寄存器。两块整数寄存器可以按位操作,使用一个字节表示8位,每一位1代表开,0代表关。

3, Modbus区分RTU模式,ASCII模式和TCP模式。

Modbus TCP基于以太网和TCP/IP协议,Modbus RTU和Modbus ASCII则是使用异步串行传输(通常是RS-232/422/485)。RTU使用CRC校验, ASCII则使用LRC校验。

   3.1  Modbus RTU模式帧格式:

例子:
发送:09 03 00 04 00 03 XX

其中 "09" 是地址码(从站号),其中"03"是读Holding Register的功能码,"00 04 00 03"是数据区,"00 04"是寄存器的起始地址,"00 03"说明要连续读三个寄存器的值。"XX"代表最后的校验位。 整体报文意思:主站告诉从站09,我要读取的从起始地址为4开始往后连续3个地址位的Holding Register的数值。

接收:09 03 06 02 2B 00 01 00 64 XX
从站回应, 其中"09 03"是复制了主站发来的地址和功能码,"06"代表接下来的数据共有6个字节。
该地址偏移为4的寄存器值为02 2B,地址偏移为5的寄存器值为00 01,地址偏移为6的寄存器值为00 64。

   3.2,  ASCII模式 帧格式:

起始标识地址码功能码数据校验结束标识

(固定为1byte冒号) :

2bytes2bytes0~2*252 bytes2byte(LRC校验)(固定为2bytes回车换行符) CR, LF

  3.3, TCP模式帧格式:

分为两部分,报头MBAP + PDU,其中MBAP固定为7字节,格式如下:

事务处理标识协议标识长度单元表示符
2bytes2bytes2bytes1byte

事务处理标识:可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文。

协议标识符:  固定为 00 00, 表示ModbusTCP协议。
长度:           表示接下来的数据长度,而不是整体报文长度,单位为字节。
单元标识符:  可以理解为从站的设备地址,站号。

例子:
发送:01 c8 00 00 00 06 01 03 00 14 00 0a
序列号:01 c8 ,协议标识符:00 00 ,长度:00 06 ,单元标识符/服务器地址:01,功能码:03,寄存器地址:00 14 ,读取几位数据:00 0a。

接收:01 c8 00 00 00 17 01 03 14 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 03 00 00
序列号:01 c8 ,协议标识符:00 00 ,长度:00 17 ,单元标识符/服务器地址:01 ,功能码:03,数据长度:14 ,数据:就是后面的在第6位和第9位有数据。

下一条数据序,序列号就会加一,变为01 c9。

4, Modbus是一主多从的模式,分为单播和广播模式,广播模式从站地址为0,功能码为写指令,从站不回复。支持广播的功能码:0x05,0x06,0x0F,0x10。

5, 从站应答的典型值小于10ms,最大值约为700ms。所以建议无回复的询问间隔为1s。

6, Modbus寄存器地址和PDU地址偏移为1,例如第一个寄存器(例如0001寄存器),对应的是地址0。

7, 异常响应:

        主设备(客户端)或从设备(服务器)中的任何一个出现异常响应,都会导致数据处理错误。在主设备(客户端)发出一个请求以后,可能出现以下事件中的一种:

    7.1  如果从设备(服务器)从主设备(客户端)接收了请求,其中没有出现通讯错误,并且正确处理了请求,那么它会返回一个正常晌应。
    7.2  如果从设备(服务器)因为通讯错误而没有从主设备(客户端)接收请求,那么它不会返回晌应。主设备程序最终会为请求处理一个超时条件。
   7.3  如果从设备(服务器)从主设备(客户端)接收了请求,但是检测到了一个通讯错误,那么它不会返回响应。主设备程序最终会为请求处理一个超时条件。
    7.4 如果从设备(服务器)从主设备(客户端)接收了请求,其中没有出现通讯错误,但是无法处理它(比如该请求是读取一个不存在的寄存器),那么从设备就会返回一个异常响应,以通知主设备出现了何种错误。

8, Modbus RTU 异常帧格式:

从设备ID异常功能码(请求中的功能码+0x80)异常代码值CRC_LCRC_H
1byte1bytenbytes1byte1byte

9, 异常代码:

异常代码10进制(16进制)名称说明
01(0x01)非法功能在请求中接收的功能代码不是从设备的一个授权操作。从设备可能处于错误状态,无法处理特定请求
02(0x02)非法数据地址从设备接收的数据地址不是从设备的一个授权地址
03(0x03)非法数据值在请求数据栏中的数值不是从设备的一个授权值
04(0x04)从设备故障从设备未能执行一个请求的操作,因为出现了一个无法修复的错误
05(0x05)确认从设备接受了请求,但是需要较长的时间来处理它
06(0x06)从设备繁忙从设备忙于处理另一个命令,主设备必须在从设备空闲后发送请求
07(0x07)否定确认从设备无法执行主设备发送的编程请求
08(0x08)存储器奇偶校验错误从设备在尝试读取扩展存储器的时候从存储器中检测到一个奇偶校验错误
10(0x0A)网关通道不可用网关过载,或者没有正确配置
11(0x0B)网关目标设备未能晌应在网络中不存在从设备

10, ModbusRTU常用功能码报文例子:

0x01 读取线圈/离散量输出值

   该功能码用于读取从设备的线圈或离散量输出的状态,即各DO(Discrete Output,离散输出)的 ON/OFF 状态。
    消息帧中指定了需读取的线圈起始地址和线圈数目。
    起始地址由2个字节构成,取值范围为0x0000 到0xFFFF。
    线圈数量由2个字节构成,取值范围为 0x0001到0x07D0(即十进制1~2000)。
    需要注意,在 Modbus 协议规定的 PDU中,规定所有线或寄存器地址从0开始计算。

0x02 读取离散量输入值

    该功能码用于读取从设备的离散输入,即DI(DiscreteInput)的 ON/OFF 状态。
    消息帧中指定了需读取的离散输入寄存器起始地址和数目,可读取 1~2000 个连续的离散量输入状态。
    如果从设备接受主设备的请求则回复功能码 02 ,并返回离散量输入各变量的当前状态(如果返回的 DI 数量不是 8 的整数倍,将用 0 填充最后数据字节的剩余位)。
    起始地址由2个字节构成,取值范围为 0x0000到0xFFFF。
    离散量数量由2个字节构成,取值范围为 0x0001到 0x07D0(即十进制1~2000),最多一次可读取 2000 个离散输入状态值。

   

0x03 读取保持寄存器值

    该功能码用于读取从设备保持寄存器的内容,不支持广播模式。
    消息帧中指定了需读取的保持寄存器的起始地址和数目,而保持寄存器中各地址的具体内容和意义则由设备开发者自行规定。
    起始地址由2个字节构成,取值范围为 0x0000 到0xFFFF。
    寄存器数量由2个字节构成,取值范围为0x0001到 0x007D(即十进制 1~125),最多一次可连续读取 125 个寄存器值。
    需要注意,Modbus 的保持寄存器和输入寄存器是以字(Word)为基本单位的(1Word等于 2Bytes)。因此,在读取时需要注意字节序(大小端)问题。

0x04 读取输入寄存器值

    该功能码用于读取从设备输入寄存器的内容,不支持广播模式。(与03功能码类似)
     消息帧中指定了需读取的输入寄存器的起始地址和数目,而输入寄存器中各地址的具体内容和意义则由设备开发者自行规定。
    起始地址由2个字节构成,取值范围为0x0000到 0xFFFF。
     寄存器数量由2个字节构成,取值范围为 0x0001到0x007D(即十进制 1~125),最多一次可连续读取 125 个寄存器值。 同样需要注意字节序问题。

0x05 写单个线圈/单个离散输出

    该功能码用于将单个线圈寄存器(或离散输出)设置为 ON或 OFF,支持广播模式。
    在广播模式下,所有从站设备的同一地址的值将被统一修改。
    消息帧中指定了需要变更的线圈地址和设定的状态值。
    起始地址由2个字节构成,取值范围为0x0000到 0xFFFF。
    目标数据(即查询报文中的 ON/OFF 状态)由报文数据字段的常数指定,0xFF00 表示 ON 状态,0x0000 表示OFF 状态,其余所有值均是非法的。
     需要注意,在 Modbus 协议规定的 PDU 中,规定所有线或寄存器地址从0开始计算。

0x06 写单个保持寄存器

    该功能码用于更新从设备的单个保持寄存器的值,支持广播模式。
    在广播模式下,所有从站设备的同一地址的值将被统一修改。
    消息帧中需要指定从设备地址以及需要变更的保持畜存器地址和设定值。
    起始地址由2个字节构成,取值范围为 0x0000 到0xFFFF。
    变更目标数据由 2个字节构成,取值范围为 0x0000 到 0xFFFF。
    保持寄存器以字(Word)为基本单位,写入时需要注意目标数据的字节序问题。

0x0F写多个线圈

     该功能码用于将连续的多个线圈(或离散输出)设置为 ON 或 OFF,支持广播模式。
    在广播模式下,所有从站设备的同一地址的值将被统一修改。
    消息帧中指定了需要变更的线圈起始地址和线圈数目。
    起始地址由2个字节构成,取值范围为0x0000到0xFFFF。
   寄存器数量字段由2个字节构成,取值范围为 0x0001到0x07B0。
   数据字段中为逻辑1的位对应 ON,逻辑0的位对应 OFF。

0x10 写多个保持寄存器

      该功能码用于设置或写入从设备保持寄存器的多个连续的地址块(1~123个寄存器),支持广播模式。
    在广播模式下,所有从站设备的同一地址的值将被统一修改。
    消息帧中需要指定从设备地址以及需要变更的保持寄存器地址和数量。
    起始地址由2个字节构成,取值范围为 0x0000 到 0xFFFF。
    寄存器数量字段由2个字节构成,取值范围为0x0001到0x007B(即十进制 1~123)。
    在实际开发中,该功能码常用于方便用户写入多字节类型的数据,例如浮点数值。因此,需要注意字节序问题。

从地址0开始对10个寄存器进行写值,第4个寄存器111,第7个寄存器222,第十个寄存器333,其他0:

报文格式从站地址功能码起始地址H起始地址L数量H数量L写入字节数写入值1H写入值1L写入值2H写入值2L写入值3H写入值3L写入值4H写入值4L写入值5H写入值5L写入值6H写入值6L写入值7H写入值7L写入值8H写入值8L写入值9H写入值9L写入值10H写入值10LCRC_LCRC_H
请求0x010x100x000x000x000x0A0x140x000x000x000x000x000x000x00

0

x6F

0x000x000x000x000x000xDE0x000x000x000x000x010x4D0x1A0xF9

在 Excel VBA 中通过 Windows API 实现 **Modbus RTU** **Modbus TCP** 通信是工业自动化中常见的需求,用于读取或写入 PLC、传感器等设备的数据。由于 VBA 本身不直接支持 Modbus 协议,我们需要借助 Windows API 调用底层功能: - 对于 **Modbus RTU(串口)**:使用 `CreateFile`、`SetCommState`、`ReadFile`、`WriteFile` 等 Win32 API 操作 COM 口。 - 对于 **Modbus TCP**:使用 Windows 的套接字(Winsock)API 或调用外部 DLL(如 `ws2_32.dll`),或者更简单地使用 ActiveX 控件(如 MSWinsock)。 下面分别给出两个完整示例,并解释其原理代码结构。 --- ### ✅ 示例 1:VBA 实现 Modbus RTU(通过串口 + Windows API) ```vba ' 在标准模块中声明 Windows API 函数 Private Declare PtrSafe Function CreateFile Lib "kernel32" Alias "CreateFileA" _ (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, _ ByVal dwShareMode As Long, lpSecurityAttributes As Any, _ ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, _ ByVal hTemplateFile As LongPtr) As LongPtr Private Declare PtrSafe Function SetCommState Lib "kernel32" Alias "SetCommState" _ (ByVal hFile As LongPtr, lpDCB As DCB) As Long Private Declare PtrSafe Function GetCommState Lib "kernel32" Alias "GetCommState" _ (ByVal hFile As LongPtr, lpDCB As DCB) As Long Private Declare PtrSafe Function ReadFile Lib "kernel32" _ (ByVal hFile As LongPtr, lpBuffer As Any, ByVal nNumberOfBytesToRead As Long, _ lpNumberOfBytesRead As Long, lpOverlapped As Any) As Long Private Declare PtrSafe Function WriteFile Lib "kernel32" _ (ByVal hFile As LongPtr, lpBuffer As Any, ByVal nNumberOfBytesToWrite As Long, _ lpNumberOfBytesWritten As Long, lpOverlapped As Any) As Long Private Declare PtrSafe Function CloseHandle Lib "kernel32" (ByVal hObject As LongPtr) As Long ' 定义常量 Private Const GENERIC_READ = &H80000000 Private Const GENERIC_WRITE = &H40000000 Private Const OPEN_EXISTING = 3 Private Const INVALID_HANDLE_VALUE = -1 ' DCB 结构体定义(串口配置) Private Type DCB DCBlength As Long BaudRate As Long fBitFields As Long wReserved As Integer XonLim As Integer XoffLim As Integer ByteSize As Byte Parity As Byte StopBits As Byte XonChar As Byte XoffChar As Byte ErrorChar As Byte EofChar As Byte EvtChar As Byte wReserved1 As Integer End Type ' 缓冲区类型 Dim buffer(255) As Byte ' Modbus RTU 功能码 Private Const MODBUS_READ_HOLDING_REGISTERS = &H3 ' 发送 Modbus RTU 请求并读取响应 Function ModbusRTU_ReadRegisters(portName As String, slaveID As Byte, startAddr As Integer, numRegs As Integer) As Variant Dim hCom As LongPtr Dim dcb As DCB Dim bytesRead As Long Dim bytesWritten As Long Dim request(7) As Byte Dim response() As Byte Dim i As Integer ' 打开串口 hCom = CreateFile("\\.\\" & portName, GENERIC_READ Or GENERIC_WRITE, 0, ByVal 0, OPEN_EXISTING, 0, 0) If hCom = INVALID_HANDLE_VALUE Then MsgBox "无法打开串口 " & portName Exit Function End If ' 获取当前串口设置 If GetCommState(hCom, dcb) = 0 Then MsgBox "获取串口状态失败" CloseHandle hCom Exit Function End If ' 设置串口参数 dcb.BaudRate = 9600 dcb.ByteSize = 8 dcb.Parity = 0 ' 无校验 dcb.StopBits = 1 ' 1位停止位 If SetCommState(hCom, dcb) = 0 Then MsgBox "设置串口失败" CloseHandle hCom Exit Function End If ' 构造 Modbus RTU 请求帧 request(0) = slaveID ' 从站地址 request(1) = MODBUS_READ_HOLDING_REGISTERS ' 功能码 request(2) = HiByte(startAddr) ' 起始地址高字节 request(3) = LoByte(startAddr) ' 低字节 request(4) = HiByte(numRegs) ' 寄存器数量高字节 request(5) = LoByte(numRegs) ' 低字节 ' CRC 校验自动添加(此处省略,建议封装 CRC 计算函数) Dim crc As Integer crc = CalculateCRC(request, 6) request(6) = crc And &HFF request(7) = (crc \ 256) And &HFF ' 发送请求 Call WriteFile(hCom, request(0), 8, bytesWritten, ByVal 0) ' 接收响应(最小响应长度:从站地址 + 功能码 + 字节数 + 数据 + CRC) ReDim response(5 + numRegs * 2 + 2) Call ReadFile(hCom, response(0), UBound(response), bytesRead, ByVal 0) ' 验证响应 If bytesRead < 5 Then MsgBox "响应数据过短" CloseHandle hCom Exit Function End If If response(0) <> slaveID Or response(1) <> MODBUS_READ_HOLDING_REGISTERS Then MsgBox "响应地址或功能码错误" CloseHandle hCom Exit Function End If ' 提取寄存器值 Dim resultArray() As Integer ReDim resultArray(numRegs - 1) For i = 0 To numRegs - 1 resultArray(i) = response(3 + i * 2) * 256 + response(4 + i * 2) Next i ModbusRTU_ReadRegisters = resultArray ' 关闭句柄 CloseHandle hCom End Function ' 辅助函数:高位字节 Function HiByte(ByVal w As Integer) As Byte HiByte = (w \ 256) And &HFF End Function ' 辅助函数:低位字节 Function LoByte(ByVal w As Integer) As Byte LoByte = w And &HFF End Function ' CRC16 计算函数(Modbus 使用 CRC-16-IBM) Function CalculateCRC(data() As Byte, length As Integer) As Integer Dim crc As Integer Dim i As Integer, j As Integer crc = &HFFFF For i = 0 To length - 1 crc = crc Xor data(i) For j = 0 To 7 If (crc And 1) Then crc = (crc \ 2) Xor &HA001 Else crc = crc \ 2 End If Next j Next i CalculateCRC = crc End Function ``` #### ✅ 使用方法: ```vba Sub TestModbusRTU() Dim res As Variant res = ModbusRTU_ReadRegisters("COM3", 1, 0, 5) ' 读取从站1的前5个保持寄存器 If IsArray(res) Then Dim i As Integer For i = 0 To UBound(res) Debug.Print "Register " & i & ": " & res(i) Next i End If End Sub ``` --- ### ✅ 示例 2:VBA 实现 Modbus TCP(使用 Winsock 控件) 由于 VBA 不能直接调用原始套接字 API,我们推荐使用 **MSWinsock 控件**(需引用 Microsoft Winsock Control)。 #### 步骤: 1. 在 Excel 中启用“开发者选项卡” → “插入” → “更多控件” → 选择 `Microsoft Winsock Control`。 2. 将控件拖到工作表上(如命名为 `Winsock1`)。 ```vba ' Modbus TCP 请求函数 Function ModbusTCP_ReadRegisters(ipAddress As String, port As Integer, slaveID As Byte, startAddr As Integer, numRegs As Integer) As Variant Dim winsock As Object Set winsock = Me.Controls("Winsock1") ' 假设 Winsock 控件名为 Winsock1 Dim transactionID As Integer transactionID = 1 ' 可递增 ' 构造 Modbus TCP ADU 报文 Dim pdu(5) As Byte ' PDU: 功能码 + 地址 + 数量 Dim mbap(6) As Byte ' MBAP 头部 ' MBAP: 事务ID、协议ID、长度、单元ID mbap(0) = transactionID \ 256 mbap(1) = transactionID And &HFF mbap(2) = 0 ' 协议 ID 高位 mbap(3) = 0 ' 协议 ID 低位 mbap(4) = 0 ' 长度高位(后续填充) mbap(5) = 6 ' 长度低位(PDU 长度) mbap(6) = slaveID ' PDU pdu(0) = &H3 ' 功能码:读保持寄存器 pdu(1) = HiByte(startAddr) pdu(2) = LoByte(startAddr) pdu(3) = HiByte(numRegs) pdu(4) = LoByte(numRegs) ' 总报文 Dim request() As Byte ReDim request(Length:=11) ' 7 + 5 - 1 Dim i As Integer For i = 0 To 6 request(i) = mbap(i) Next i For i = 0 To 4 request(i + 7) = pdu(i) Next i ' 连接并发送 winsock.Close winsock.Connect ipAddress, port Do Until winsock.State = 7 ' sckConnected DoEvents Loop winsock.SendData request ' 等待响应(简化处理,实际应使用事件驱动) ' 响应将由 Winsock1_DataArrival 事件接收 Static resultBuffer resultBuffer = Empty ' 这里为了演示,仅返回占位符 ' 实际应在 DataArrival 事件中解析 ModbusTCP_ReadRegisters = "请在 Winsock1_DataArrival 中处理响应" End Function ' Winsock1 数据到达事件(放在用户窗体代码中) Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long) Dim response() As Byte Winsock1.GetData response ' 解析 Modbus TCP 响应 If UBound(response) >= 9 Then Dim byteCount As Byte byteCount = response(8) Dim numRegs As Integer numRegs = byteCount / 2 Dim values() As Integer ReDim values(numRegs - 1) Dim i As Integer For i = 0 To numRegs - 1 values(i) = response(9 + i * 2) * 256 + response(10 + i * 2) Next i ' 输出到 Immediate Window 测试 For i = 0 To numRegs - 1 Debug.Print "Register " & i & " = " & values(i) Next i End If End Sub ``` --- ### 🔍 说明与注意事项: | 特性 | Modbus RTU | Modbus TCP | |------|------------|-----------| | 传输层 | 串口 RS485/RS232 | TCP/IP 网络 | | API 依赖 | kernel32.dll (CreateFile, ReadFile 等) | MSWinsock 控件 或 WinINet/WSA | | 开发复杂度 | 较高(需处理串口、CRC) | 中等(需处理 MBAP 头) | | 实时性 | 高 | 受网络影响 | --- ### ❗注意: - `PtrSafe` 是 64 位 VBA 所需的关键字(Office 2010+ 默认 64 位)。 - 上述 Modbus RTU 示例适用于 **无校验、8-N-1** 设置,可根据设备调整。 - 生产环境中建议封装成类模块,增加超时、重试机制。 - 若需更高性能,可编译 C++ DLL 并由 VBA 调用。 --- ### ✅ 相关工具建议: - 使用 **QModMaster** 或 **Modbus Poll** 测试设备通信。 - 使用 **Wireshark** 抓包分析 Modbus TCP。 - 使用 **SerialPort Monitor** 分析串口数据。 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值