1, Modbus地址区的0x、1x、3x、4x各代表什么?如何区分和应用?
地址区 | 名词 | 读/写 | 地址范围 | 别称 | 可用功能码 |
0x | 线圈状态/离散量输出(coil status) | 可读/可写 | 00001~09999 | 对应PLC: DO | 01、05、15 |
1x | 离散量输入(inputs status ) | 只读 | 10001~19999 | 对应PLC: DI | 02 |
3x | 输入寄存器(input registers) | 只读 | 30001~39999 | 对应PLC: AI | 04 |
4x | 保持寄存器(holding registers) | 可读/可写 | 40001~49999 | 对应PLC: AO | 03、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冒号) : | 2bytes | 2bytes | 0~2*252 bytes | 2byte(LRC校验) | (固定为2bytes回车换行符) CR, LF |
3.3, TCP模式帧格式:
分为两部分,报头MBAP + PDU,其中MBAP固定为7字节,格式如下:
事务处理标识 | 协议标识 | 长度 | 单元表示符 |
2bytes | 2bytes | 2bytes | 1byte |
事务处理标识:可以理解为报文的序列号,一般每次通信之后就要加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_L | CRC_H |
1byte | 1byte | nbytes | 1byte | 1byte |
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 | 写入值10L | CRC_L | CRC_H |
请求 | 0x01 | 0x10 | 0x00 | 0x00 | 0x00 | 0x0A | 0x14 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0 x6F | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0xDE | 0x00 | 0x00 | 0x00 | 0x00 | 0x01 | 0x4D | 0x1A | 0xF9 |