Modbus 协议二之 ModBus在串行链路下的实现

一、简述

  • Modbus在一个主站和一个或多个从站之间交换Modbus请求,位于OSI 模型的第二层.

  • 在物理层,Modbus 串行链路系统可以使用不同的物理接口(RS485、RS232)

image
image

二、Modbus 数据链路层

2.1 通信原理

  • 同一时刻,只有一个主节点连接于总线一个或多个子节点(最大编号为247 ) 连接于同一个串行总线通信总是由主节点发起子节点在没有收到来自主节点的请求时,从不会发送数据。 子节点之间从不会互相通信主节点在同一时刻只会发起一个Modbus 事务处理。

主节点以两种模式对子节点发出Modbus 请求:

  • 单播模式:主节点以特定地址访问某个子节点,子节点接到并处理完请求后,子节点向主节点返回一个报文(一个'应答')。一个Modbus 事务处理包含2 个报文主节点的请求子节点的应答。每个子节点必须有唯一的地址(1 到247)

image

  • 广播模式:主节点向所有的子节点发送请求。广播请求一般用于写命令子节点没有应答返回所有设备必须接受广播模式的写功能。地址0 专门用于表示广播数据。

image

2.2 Modbus 地址规则

image

  • 主节点没有地址
  • 子节点必须有一个地址,且在总线上唯一
  • 所有的子节点必须识别广播地址。

2.3 Modbus 帧描述

协议数据单元(PDU - Protocol DataUnit):
image

  • 不同总线或网络的 Modbus 协议 在协议数据单元(PDU之外)之外映射了一些附加域,发起Modbus 事务处理的客户端(主机)构造Modbus PDU添加附加的域以构造适当的通信PDU

  • 地址域只含有子节点地址。主节点通过地址域对子节点寻址子节点在应答报文中的地址域放入自己的地址告诉主节点哪个子节点应答

  • 功能码指明服务器要执行的动作

  • 错误检验域是对报文内容执行"冗余校验" 的计算结果

2.4 主站/从站状态

Modbus 由两个不同的子层组成:

  • 主/ 从协议

  • 传输模式( RTU 和ASCII 模式)

状态图词法:。标记法要点如下:
image
当一个系统处于"状态_A"发生"触发"事件,只有当"临界条件" 为真系统转换到"状态_B",之后,一个"动作"被执行。

2.4.1 主站状态图

image

  • 单播请求发送到一个子节点,主节点将进入"等待应答" 状态,"响应超时"启动
  • 收到一个应答,主节点在处理数据之前检验应答。收到来自非期望子节点的应答时,“响应超时”继续计时;检测到帧错时,执行一个“重试”。“响应超时”但没有收到应答时,产生一个错误。主节点进入”空闲” 状态, 并发出一个重试请求。

  • 广播请求发送到串行总线,没有响应从子节点返回。主节点需要进行"转换延迟"(使子节点在发送新的请求处理完当前请求)

2.4.2 从站状态图

image

  • 收到一个请求时,子节点在处理请求中要求的动作前 检验报文包。当检测到错误(请求的格式错,非法动作等),必须向主节点发送应答此情况是该帧数据确实是发给我的,但帧结构中的数据有错误,该情况需要返回给主站)

  • 要求的动作完成后,单播报文要求必须格式化一个应答并发往主节点。

  • 子节点在接收到的帧中检测到错误没有响应返回到主节点。(此情况是该帧不是发给我的,或者发给我的帧结构错误,该情况不需要返回给主站)

帧错误包括: 1) 对每个字符的奇偶校验; 2) 对整个帧的冗余校验。

2.4.3 主站/从站通信时序图

多看一下上方的状态图,就大致了解了主从站通信。
image

2.5 两种串行传输模式

两种串行传输模式:

  • RTU 模式

  • ASCII 模式

作用:定义了报文域的位内容在线路上串行的传送,确定了信息如何打包成报文和解码。

  • Modbus 串行链路上所有设备的传输模式(和串行口参数) 必须相同。设备由用户设成期望的模式:RTU 或ASCII默认设置必须为RTU 模式

2.5.1 RTU 传输模式

  • 报文中每个8 位字节含有两个4 位十六进制字符。每个报文必须以连续的字符流传送

  • RTU 模式每个字节( 11 位) 的格式为:1 起始位,8 数据位, 首先发送最低有效位,1 位作为奇偶校验,停止位。

  • 默认校验模式模式必须为偶校验, 其它模式( 奇校验, 无校验) 也可以使用。使用无校验要求2 个停止位

  • 字符字节发送顺序:
    image
    image

  • 帧检验域: 循环冗余校验(CRC)

  • 帧描述:
    image

2.5.1.1 Modbus 报文RTU 帧

  • 发送设备将Modbus 报文构造带有已知起始和结束标记的帧

  • RTU 模式,报文帧由时长至少为3.5 个字符时间的空闲间隔区分
    image
    image

  • 整个报文帧必须以连续的字符流发送。两个字符之间的空闲间隔大于1.5 个字符时间,则报文帧被认为不完整应该被接收节点丢弃
    image

  • 在通信速率等于或低于19200 Bps 时,这两个定时必须严格遵守;波特率大于19200 Bps 的情形,应该使用2 个定时的固定值:建议的字符间超时时间(t1.5)为750μs帧间的超时时间(t1.5) 为1.750ms

  • RTU 传输模式下状态图描述。"主节点" 和"子节点" 的不同角度均在相同的图中表示:
    image

  • 链路空闲时, 在链路上检测到的任何传输的字符被识别为帧起始,链路上没有字符传输的时间间个达到t3.5 后,被识别为帧结束。检测到帧结束后,完成CRC 计算和检验

2.5.1.2 CRC 校验

不管报文有无奇偶校验,均执行此检验。

  • CRC 包含由两个8 位字节组成的一个16 位值。CRC 域附加在报文之后。计算后,首先附加低字节,然后是高字节。高字节为报文发送的最后一个子节。

  • CRC 的值由发送设备计算,接收设备在接收报文时重新计算CRC,将计算结果于实际接收到的CRC 值相比

  • 只有字符中的8个数据位参与生成CRC 的运算,起始位,停止位和校验位不参与CRC 计算。

生成CRC 的过程为:

  1. 将一个16 位寄存器装入十六进制FFFF (全1). 将之称作CRC 寄存器.
  2. 将报文的第一个8 位字节与16 位CRC 寄存器的低字节异或,结果置于CRC 寄存器.
  3. 将CRC 寄存器右移1 位(向LSB 方向), MSB 充零. 提取并检测LSB.
  4. (如果LSB 为0): 重复步骤3 (另一次移位).
    (如果LSB 为1): 对CRC 寄存器异或多项式值0xA001 (1010 0000 0000 0001).
  5. 重复步骤3 和4,直到完成8 次移位。当做完此操作后,将完成对8 位字节的完整操作。
  6. 对报文中的下一个字节重复步骤2 到5,继续此操作直至所有报文被处理完毕。
  7. CRC 寄存器中的最终内容为CRC 值.
  8. 当放置CRC 值于报文时,如下面描述的那样,高低字节必须交换。

当16 位CRC (2 个8 位字节) 在报文中传送时,低位字节首先发送,然后是高位字节。
例如, CRC 值为十六进制1241 (0001 0010 0100 0001):
image

  • 所有的可能的CRC 值都被预装在两个数组中。一个数组含有16 位CRC 域的所有256 个可能的高位字节,另一个数组含有地位字节的值。此函数返回的是已经经过交换的CRC 值。
    函数使用两个参数:
    unsigned char *puchMsg: 指向含有用于生成CRC 的二进制数据报文缓冲区的指针
    unsigned short usDataLen: 报文缓冲区的字节数.

/* 高位字节的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
} ;

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 char *puchMsg ; /* 用于计算CRC 的报文*/
unsigned short usDataLen ; /* 报文中的字节数*/

unsigned short CRC16 (unsigned char * puchMsg,unsigned short  usDataLen ) /* 函数以unsigned short 类型返回CRC */
{
	unsigned char uchCRCHi = 0xFF ; /* CRC 的高字节初始化*/
	unsigned char uchCRCLo = 0xFF ; /* CRC 的低字节初始化*/
	unsigned uIndex ; /* CRC 查询表索引*/
	
	while (usDataLen--) /* 完成整个报文缓冲区*/
	{
		uIndex = uchCRCLo ^ *puchMsgg++ ; /* 计算CRC */
		uchCRCLo = uchCRCHi ^ auchCRCHi[uIndex} ;
		uchCRCHi = auchCRCLo[uIndex] ;
	}
	return (uchCRCHi << 8 | uchCRCLo) ;
}

2.5.1.3 LRC 校验

  • 纵向冗余校验(LRC)为一个字节,含有8 位二进制值
  • 由发送设备计算,接收设备在接收文时计算LRC, 并将计算的结果与在LRC 接收到的实际值相比

生成一个LRC 的过程为:
1.不包括起始”冒号”和结束CRLF 的报文中的所有字节相加到一个8 位域,故此进位被丢弃。
2.从FF (全1)十六进制中减去域的最终值,产生1 的补码(二进制反码)。
3.加1 产生二进制补码.

高位字符首先发送,然后是低位字符。
例如,LRC 值为十六进制61 (0110 0001):
image

函数带有两个参数:
unsigned char *auchMsg; 指向含有用于生成LRC 的二进制数据报文缓冲区的指针,
unsigned short usDataLen; 报文缓冲区的字节数.


unsigned char *auchMsg ; /* 要计算LRC 的报文*/
unsigned short usDataLen ; /* 报文的字节数*/

static unsigned char LRC(auchMsg, usDataLen) /* 函数返回unsigned char 类型的LRC 结果
*/
{
	unsigned char uchLRC = 0 ; /* LRC 初始化*/
	
	while (usDataLen--) /* 完成整个报文缓冲区*/
		uchLRC += *auchMsg++ ; /* 缓冲区字节相加,无进位*/

	return ((unsigned char)(-((char)uchLRC))) ; /* 返回二进制补码*/
}

三、物理层

3.3 电气接口

3.3.1 多点串行总线结构

image

  • 主干间接口:ITr
  • 设备和无源接口:IDv(分支)
  • 设备和有源接口:AUI(附加)

3.3.2 2线-MODBUS

image

  • 任何时刻只有一个驱动器有权力发送数据。

image

3.3.3 可选的4 线-MODBUS 定义

  • 主对总线(RXD1-RXD2)上的数据只能由从站接收,从对总线(TXD0-TXD1)上的数据只能由主站接收。

  • 任何时刻只有一个驱动器有权力发送数据。
    image
    image

  • 主站应该:

    • 从对总线(TXD1-TXD0)上接收来自从站的数据,
    • 主对总线(RXD1-RXD0)上发送数据,由从站接收,

3.3.3.2 4 线与2 线电缆的兼容性

  • 4 线电缆系统可以按下述修改:
    • TXD0 信号应与RXD0 信号连接,使之成为D0 信号。
    • TXD1 信号应与TXD0 信号连接,使之成为D1 信号。
    • 上拉,下拉电阻和线路终端电阻应重新安排以正确地适应D0,D1 信号。

image

posted @ 2023-06-17 15:29  登云上人间  阅读(80)  评论(0编辑  收藏  举报