Modbus-RTU协议(简单易懂版)

简单描述下Modbus-RTU协议通信特点、过程:

        一主机多从机;仅主机可发起请求;同一时间,只有一个数据在传输,即(单播时)主机发送请求,从机响应,主机不发送,没有数据在传输(广播时,主机发送,所有从机不需要发送响应)

下面具体介绍一些内容,介绍具体过程前需要先介绍一下帧

协议数据单元(PDU)包括1字节的功能码域,和不定长度的数据域

应用数据单元(ADU)在此之上引入了1字节地址域和2字节差错检验

地址域1字节。广播地址为0,每一个从机都有一个对应的地址在1-255,主机没有地址,在从机返回的ADU中,地址域返回的是从机的地址

功能码1字节。功能码通知从机需要进行哪种操作

数据域n字节。数据域是一些附加信息,针对功能码进行的操作给一些补充

差错检验2字节。是针对整个帧进行CRC检验(RTU)

在RTU传输模式中,每个字节(11位)的格式为:

1起始位+8数据位,先发送最低有效位+1奇偶校验位+1停止位

如果无奇偶校验,则为2停止位

下面介绍一个具体的通信过程,分别用主机和从机的状态图来表示

当没有等待的请求时,主机处于空闲状态,这是上电后的初始状态,只有处于空闲状态才能发送请求。当一个请求被发送后,主机就会离开空闲状态,也就不能继续发送请求了,也就是说不能同时发送第二个请求

如果此次发送的请求为广播,则主机在发送后进入会启动转换延迟从而进入等待转换延迟状态,当转换延迟时间到达之后(这个时间足够所有的从机完成对请求内容的执行),主机会回到空闲状态

如果发送的请求为单播,主机发送请求到对应从机之后主机就会启动响应超时(这个时间足够从机进行响应并发回响应,如果超时则表示出现错误,具体应用的超时时间不同),同时进入等待应答状态。如果应答超时没有收到响应,就会进入出错处理状态,主机回到空闲状态并发出一个重试请求,重试的最大次数取决于主节点的设置

如果在响应时间前收到了应答,主机会在处理数据前,对应答进行检验,如果收到非期望的从机应答,响应超时继续计时,如果是产生了帧错误(包括对字符的奇偶校验或对整个帧的冗余校验),则发起一个重试

如果主机收到了正确的应答,主机在处理完应答后,会回到空闲状态,等待下一个请求

在从机端,当没有收到请求时,从机处于空闲状态,当从主机收到一个请求后,从机首先对报文进行检验,如果检验没有问题,则进行处理,完成请求动作,发出正常应答

如果检验过程或处理过程出现错误,则需要不响应或发出异常应答,具体情况如下:

(协议里介绍的是所有异常响应情况)

如果从机没有收到请求,就没有返回响应,主机处理超时状态

如果从机收到请求,但检验出一个通信错误(奇偶校验、CRC...),那么不返回响应,主机处理超时状态

如果从机收到没有通信错误的请求,但是并不能处理这个请求,将会返回一个异常响应

下面介绍三种PDU,包括主机请求,从机响应和从机异常响应

主机请求PDU,1字节功能码+n字节数据

从机正常响应,1字节功能码+n字节数据

从机异常响应,1字节(功能码+0x80(也就是最高位置为1))+1字节异常码(告诉主机哪里异常)

这个图简单介绍了事务处理中,异常码的基本情况

那么,到底实现哪些功能呢,modbus建立了这几个数据模型,基本上功能是围绕它们展开的,包括离散量输入,线圈,输入寄存器和保持寄存器

然后介绍一下功能码,简单看下协议吧

举个例子吧,串联一下

比如说,功能码0x03读保持寄存器,协议会列出几个PDU的组成,然后给你举例具体实现

可以看到,请求PDU是由功能码(1字节)+起始地址(2字节)从0x0000到0xFFFF+寄存器数量(2字节)1-125组成,例子中,功能码03,起始地址高和低各占一个字节,先高后低,寄存器编号也是一样,右侧响应给我们的是返回了左侧请求的从006B也就是108开始,108、109、110这三个寄存器的值,同样是高位在前

modbus里面还比较重要的还有1.5字符时间和3.5字符时间:

modbus报文RTU帧,报文帧由至少为3.5字符时间的空闲间隔区分,整个报文帧必须以连续的字符流发送,如果两个字符之间的空闲间隔大于1.5个字符时间,则报文帧被认为不完整应该被接收节点丢弃

CRC校验

看下协议吧,我再附一个代码

协议给出的这个代码并不是按照计算过程来的,给出了解释,因为这种索引的办法比每个新字符都计算新的CRC更快

//CRC 生成函数
/* 高位字节的 CRC 值 */

static unsigned char auchCRCHi[] = {
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81,
	0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,0x40, 0x01, 0xC0,
	0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1,0x81, 0x40, 0x01,
	0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,0x00, 0xC1, 0x81,
	0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80,0x41, 0x01, 0xC0,
	0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x01,
	0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00,0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81,
	0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,0x40, 0x01, 0xC0,
	0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1,0x81, 0x40, 0x01,
	0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01,0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81,
	0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,0x40, 0x01, 0xC0,
	0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x01,
	0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,0x00, 0xC1, 0x81,
	0x40 };

/* 低位字节的 CRC 值 */

static char auchCRCLo[] = {
	0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,0x05, 0xC5, 0xC4,
	0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB,0x0B, 0xC9, 0x09,
	0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE,0xDF, 0x1F, 0xDD,
	0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2,0x12, 0x13, 0xD3,
	0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,0x36, 0xF6, 0xF7,
	0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E,0xFE, 0xFA, 0x3A,
	0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B,0x2A, 0xEA, 0xEE,
	0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27,0xE7, 0xE6, 0x26,
	0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,0x63, 0xA3, 0xA2,
	0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD,0x6D, 0xAF, 0x6F,
	0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8,0xB9, 0x79, 0xBB,
	0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4,0x74, 0x75, 0xB5,
	0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,0x50, 0x90, 0x91,
	0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94,0x54, 0x9C, 0x5C,
	0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59,0x58, 0x98, 0x88,
	0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D,0x4D, 0x4C, 0x8C,
	0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,0x41, 0x81, 0x80,
	0x40 };

// 函数以unsigned short 类型返回CRC
//puchMsg    :  用于计算CRC的报文
//usDataLen  :  报文中的字节数
unsigned short CRC16(unsigned char *puchMsg, unsigned short usDataLen)    
{
	unsigned char uchCRCHi = 0xFF;            //CRC的高字节初始化
	unsigned char uchCRCLo = 0xFF;            //CRC的低字节初始化
	unsigned uIndex;                          //CRC查询表索引

	while (usDataLen--)                        //完成整个报文缓冲区
	{
		uIndex = uchCRCLo^*puchMsg++;        //计算CRC
		uchCRCLo = uchCRCHi^auchCRCHi[uIndex];
		uchCECHi = auchCRCLo[uIndex];
	}
	return (uchCRCHi << 8 | uchCRCLo);
}
### Modbus RTU 协议基础与实现 #### Modbus RTU 的定义与发展 Modbus 是一种开放标准的通信协议,最早于 1979 年由 Modicon(现施耐德电气)发布,用于支持可编程逻辑控制器 (PLC) 之间的通信。随着技术发展,Modbus 已成为工业自动化领域最广泛使用的通信协议之一,特别适合连接各种工业电子设备[^1]。 #### Modbus RTU 数据帧结构 Modbus RTUModbus 协议的一种变体,采用二进制编码方式传输数据。它的数据帧通常包括以下几个部分: - **地址字段**:指定目标从设备的地址。 - **功能码**:指示要执行的操作类型。 - **数据区**:包含实际的数据内容。 - **校验和**:CRC 校验值,确保数据完整性。 构建一个完整的 Modbus RTU 请求数据包时,需严格遵循上述结构并通过串行通信端口发送至网络中的从设备[^2]。 #### 功能码解析 Modbus 支持多种功能码,常见的有读取线圈状态 (`0x01`)、读取离散输入 (`0x02`)、读取保持寄存器 (`0x03`) 和写单个寄存器 (`0x06`) 等。这些功能码允许主设备向从设备发起不同的操作请求[^3]。 以下是使用 Python 构建并发送 Modbus RTU 请求的一个简单示例: ```python import minimalmodbus # 配置串口参数 instrument = minimalmodbus.Instrument('/dev/ttyUSB0', slaveaddress=1, mode=minimalmodbus.MODE_RTU) # 设置波特率和其他串口配置 instrument.serial.baudrate = 9600 instrument.serial.bytesize = 8 instrument.serial.parity = 'N' instrument.serial.stopbits = 1 # 读取保持寄存器 (功能码 0x03) register_value = instrument.read_register(registeraddr=40001, functioncode=3) print(f"Register Value: {register_value}") # 写入单个寄存器 (功能码 0x06) instrument.write_register(registeraddr=40001, value=123, functioncode=6) ``` 此代码片段展示了如何利用 `minimalmodbus` 库与支持 Modbus RTU 的设备交互。通过设置串口号、波特率及其他必要参数,可以轻松完成基本的读写操作。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值