文章目录
- 5.1 学习Modbus的快速方法
- 5.1.1 寄存器速记
- 5.1.2 协议速记
- 5.2 初识Modbus
- 5.2.1 背景
- 5.2.2 什么是Modbus?
- 1. Modbus简介
- 2. Modbus特点
- 3. Modbus常用术语
- 4. Modbus事务处理
- 5.3 Modbus软件与使用
- 5.3.1 Modbus软件简介
- 5.3.2 Modbus Poll(主站设备)
- 1. Modbus Poll简介
- 2. Modbus Poll 使用
- 5.3.3 Modbus Slave(从站设备)
- 1. Modbus Slave简介
- 2. Modbus Slave使用
- 5.3.4 虚拟串口软件
- 1.软件简介
- 2.虚拟串口的使用
- 5.3.5 Modbus Poll 与 Modbus Slave互联/通
- 5.4 Modbus协议细节
- 5.4.1 Modbus协议概述
- 5.4.2 Modbus寄存器(存储区)
- 1. 存储区类型
- 2. 协议地址模型
- 5.4.3 Modbus常用功能码
- 5.4.4 Modbus协议类型
- 5.4.5 Modbus报文帧
- 1. Modbus ASCII 模式
- 2. Modbus RTU 模式
- 3. 串行报文帧总结:
- 5.4.6 Modbus 差错校验
- 1. LRC校验
- 2. CRC校验
- 5.4.7 字节序和大小端
- 5.4.8 Modbus报文分析
- 5.4.9 Moubus TCP 消息帧格式
- 1.协议描述
- 2. 查询与响应报文示例
- 5.5 Moubus功能码详解
- 5.5.1 功能码概要
- 5.5.2 01(0x01)读取线圈
- 1.功能说明
- 2. 查询报文
- 3. 响应报文
- 5.5.3 02 (0x02) 读取离散量输入值
- 1. 功能说明
- 2. 查询报文
- 3. 响应报文
- 5.5.4 03(0x03)读取保持寄存器值
- 1. 功能说明
- 2. 查询报文
- 3. 响应报文
- 5.5.5 04 (0x04)读取输入寄存器值
- 1.功能说明
- 2.查询报文
- 3.响应报文
- 5.5.6 05 (0x05)写单个线圈
- 1. 功能说明
- 2. 查询报文
- 3.响应报文
- 5.5.7 06 (0x06) 写单个保持寄存器
- 1.功能说明
- 2. 查询报文
- 3.响应报文
- 5.5.8 08 (0x08) 诊断功能
- 1.功能说明
- 2. 查询报文
- 3.响应报文
- 4. 诊断子功能码
- 5.5.9 11 (0x0B) 获取通信事件计数器
- 1. 功能说明
- 2. 查询报文
- 3. 响应报文
- 5.5.10 12 (0x0C) 获取通信事件记录
- 1. 功能说明
- 2. 查询报文
- 3. 响应报文
- 5.5.11 15(0x0F)写多个线圈
- 1.功能说明
- 2. 查询报文
- 3. 响应报文
- 5.5.12 16 (0x10) 写多个保持寄存器
- 1. 功能说明
- 2. 查询报文
- 3. 响应报文
- 5.5.13 17 (0x11) 报告从站ID (仅用于串行链路)
- 1. 功能说明
- 2. 查询报文
- 3. 响应报文
- 5.5.14 Modbus异常响应
5.1 学习Modbus的快速方法
5.1.1 寄存器速记
作为初学者,你阅读Modbus协议时会发现它的概念别扭、重复、不易区分,比如线圈状态(Coil Status)、离散输入状态(Discrete Input Status)、保持寄存器(Holding Register)、输入寄存器(Input Register)。
回到事情的本质,在工业控制PLC领域,涉及数字信号的输入、输出,模拟信号的输入、输出,如下图所示:
对于软件开发而言:
- 想得到按键输入状态时,读取到的是一位数据;
- 想控制LED时,需要输出一位数据,想读取LED当前状态时,也可以读取到一位数据
- 想读取模拟信号时,读取到的是多位数据,比如16位数据
- 想输出模拟信号时,写入的是多位数据,比如16位数据;也可以读取“模拟量输出”的当前值。
在上图中,“数字量输入DI”是只读的,“数字量输出DO”是可读可写的,“模拟量输入AI”是只读的,“模拟量输出AO”是可读可写的。
上图里的“模拟量输入AI”、“模拟量输出AO”都表示“多位数值”,这些“多位数值”无需局限于只表示“模拟量”,也可以表示“多位数字量”。把AI、AO的含义扩展后,如下图所示:
对于软件开发而言:
- 想得到按键输入状态时,读取到的是一位数据;
- 想控制LED时,需要输出一位数据,想读取LED当前状态时,也可以读取到一位数据
- 想读取参数时,读取到的“输入寄存器”,得到多位数据,比如16位数据
- 想设置参数时,写的是“保存寄存器”,写入的是多位数据,比如16位数据;也可以读“保存寄存器”
在电子系统里,无论是单bit的数值、多bit的数值,都是保存在寄存器里。根据上图,这些寄存器可以分为4类:
寄存器种类 | 说明 | 与PLC类比 | 举例说明 |
---|---|---|---|
线圈状态(Coil Status) | 输出端口。可设定端口输出状态,也可以读取该位的输出状态。可分为两种不同的执行状态,列如保持型或边沿触发型 | DO(数字量输出) | 电磁阀输出、MOSFEF输出、LED显示等 |
离散输入状态(Discrete Input Status) | 输入端口。通过外部设定改变输入状态,可读但不可以写 | DI(数字量输入) | 拨码开关、接近开关等 |
保持寄存器(Holding Register) | 输出参数或保持参数,控制器运行时被设定的某些参数,可读可写 | AO(模拟量输出) | 模拟量输出设定值,PID运行参数,变量阀输出大小,传感器报警上限下限 |
输入寄存器(Input Register) | 输入参数。控制器运行时从外部设备获得的参数,但可读不可写 | AI(模拟量输入) | 模拟量输入 |
在Modbus中,多位操作时都是16位(2bytes)的,总结如下:
- bit操作涉及的寄存器有2类:线圈状态(可读可写)、离散输入状态(只读)
- 16bit操作的寄存器有2类:保存寄存器(可读可写)、输入寄存器(只读)
一个设备里,可能有多个“线圈状态”、多个“离散输入状态”、多个“保存寄存器”、多个“输入寄存器”。怎么分辨某类寄存器中的某一个?它们有“寄存器地址”,如下图所示:
寄存器种类 | PLC寄存器地址范围 | Modbus寄存器地址范围 | 简称 | 读写状态 |
---|---|---|---|---|
线圈状态 | 00001~09999 | 0000H~FFFFH | 0x | 可读可写 |
离散输入状态 | 10001~19999 | 0000H~FFFFH | 1x | 只读 |
保持寄存器 | 40001~49999 | 0000H~FFFFH | 4x | 可读可写 |
输入寄存器 | 30001~39999 | 0000H~FFFFH | 3x | 只读 |
在上表中,“线圈状态”的寄存器N、“离散输入状态”的寄存器N,是两个不同的寄存器。
简单记忆方法:“
- 偶数类的寄存器”是可读可写的,比如“0x”和“4x”;
- “奇数类的寄存器”是只读的,比如“1x”和“3x”;
- “0x”和“1x”是bit寄存器;
- “3x”和“4x”是16bit寄存器。
5.1.2 协议速记
Modbus是一主多从的协议,如下图所示:
主控发出的数据里,必定含有如下信息:
- 设备地址:你要访问从设备1,还是访问从设备2
- 访问哪类寄存器,是读还是写,只访问1个寄存器,还是多个寄存器:这被称为功能码
- 起始寄存器地址、寄存器数量:这在数据里定义
- 为了保证数据传输的可靠,还附带有CRC检验码
以Modbus RTU协议为例,主控发出的数据包格式如下:
功能代码有哪些?常用的功能码如下:
- 读线圈状态(01)
- 读离散输入状态(02)
- 写单个线圈(05)、写多个线圈(15)
- 读保持寄存器(03)
- 读输入寄存器(04)
- 写单个保存寄存器(06)、写多个保存寄存器(16)
数据的格式,由功能代码确定。以“读线圈状态”为例,主控发出的请求、从设备返回的响应包,或者从设备返回的错误包,格式如下:
上图中,寄存器起始地址(“Starting Address”)是16位的,先传输高字节,再传输低字节。线圈数量(“Quantiti of coils”)也是16位的,先传输高字节,再传输低字节。
响应包回复多少个数据呢(上图中N为多少)?N = Quantiti of coils / 8,如果余数不等于0,则N再加1。比如Quantiti of coils=9,则返回2个字节。
在《Modbus_Application_Protocol_V1_1b3.pdf》中,列出了如下功能表。根据次表,在结合《5.5 Moubus功能码详解》的示例,就可以对Modbus RTU协议有很好的理解了。
5.2 初识Modbus
5.2.1 背景
Modbus诞生于1979年莫迪康(Modicon)公司,后来被施耐德电气公司收购。Modbus提供通用语言用于彼此通信的设备和设备,是全球第一个真正用于工业现场的总线协议。Modbus已经成为工业领域通信协议的业界标准,并且现在是工业电子设备之间常用的连接方式。Modbus作为目前工业领域应用最广泛的协议,之后为了更好地普及和推动Modbus基于以太网 (TCP/IP) 的分布式应用,施耐德公司已将Modbus协议的所有权移交给IDA (Interface for Distributed Automation,分布式自动化接口)组织,并成立了Modbus-IDA组织,此组织的成立和发展进一步推动了Modbus协议的广泛应用。
5.2.2 什么是Modbus?
1. Modbus简介
Modbus协议是一种已广泛应用于当今工业控制领域的通用通讯协议。通过此协议,控制器相互之间、或控制器经由网络(如以太网)可以和其它设备之间进行通信。Modbus协议使用的是主从通讯技术,即由主设备主动查询和操作从设备。一般将主控设备方所使用的协议称为Modbus Master,从设备方使用的协议称为Modbus Slave。典型的主设备包括工控机和工业控制器等;典型的从设备如PLC可编程控制器等。有了它,不同厂商生产的控制设备就可以连接成工业网络,进行集中监控。Modbus协议定义了一个控制器能够认识和使用的消息结构,而不管它们是经过何种网络进行通信的;而且描述了控制器请求访问其他设备的过程,如何应答来自其他设备的请求,以及怎样侦测错误并记录,并制定了统一的消息域的结构和内容。 当在Modbus网络上通信时,Modbus协议决定了每个控制器必须要知道它们的设备地址,识别按地址发来的消息决定要产生何种行为。如果需要回应,则控制器将生成反馈信息并通过Modbus协议发送。
Modbus通讯物理接口可以选用串口(包括RS232、RS485和RS422等),也可以选择以太网口。其通信遵循以下的过程:
- 主设备向从设备发送请求
- 从设备分析并处理主设备的请求,然后向主设备发送结果
- 如果出现任何差错,从设备将返回一个异常功能码
此协议定义了一个控制器能认识使用的消息结构,而不管它们是经过何种网络进行通信的。它描述了一控制器请求访问其它设备的过程,如何回应来自其它设备的请求,以及怎样侦测错误并记录。它制定了消息域格局和内容的公共格式。
当在Modbus网络上通信时,此协议决定了每个控制器须要知道它们的设备地址,识别按地址发来的消息,决定要产生何种行动。如果需要回应,控制器将生成反馈信息并用Modbus协议发出。在其它网络上,包含了Modbus协议的消息转换为在此网络上使用的帧或包结构。这种转换也扩展了根据具体的网络解决节地址、路由路径及错误检测的方法。
Modbus的工作方式是请求/应答,每次通讯都是主站先发送指令,可以是广播,或是向特定从站的单播;从站响应指令,并按要求应答,或者报告异常。当主站不发送请求时,从站不会自己发出数据,从站和从站之间不能直接通讯。
MODBUS 是一种应用层消息传递协议,位于 OSI 模型的第 7 层。它提供连接在不同类型总线或网络上的设备之间的客户端/服务器通信。
Modbus通信栈如下:
2. Modbus特点
Modbus通信协议具有以下几个特点:
- Modbus协议标准开放、公开发表且无版税要求。用户可以免费获取并使用Modbus协议,不需要交纳许可证费,也不会侵犯知识产权。
- Modbus协议支持多种电气接口,如RS232、RS485、TCP/IP等,还可以在各种介质上传输,如双绞线、光纤、红外、无线。
- Modbus协议消息帧格式简单、紧凑、通俗易懂。用户理解和使用简单,厂商容易开发和集成,方便形成工业控制网络。
- 可靠性: Modbus 是最古老的工业自动化通信协议。它使用和编程简单,因此学习曲线较低。
- 遗留基础设施:许多制造商在早期自动化方面投入了大量资金。Modbus 对于配置、DLR、节点、子站和其他基础设施非常友好,这些基础设施可能会被新的或更先进的协议所淘汰。
- 快速部署: Modbus 可以轻松、立即集成到 SCADA和其他控制系统中
- 灵活性: Modbus 已适应新兴技术。例如,Modbus TCP 可以通过话配器进行转换,以与LAN 和远程控制系统集成。它还可以利用基于网络和基于云的平台。
- 简单性:由于通信简单,因此可以轻松扩展到新技术。例如,Modbus TCP/P 由于指令集简单,部署速度很快。它还可以与以太网配合使用,无需添加芯片或板。
3. Modbus常用术语
名词 | 意义 |
---|---|
Master | 主(站) 设备 |
Slave | 从 (站) 设备 |
Client | 客户端 |
Server | 服务器端 |
ADU | 应用数据单元(Application Data Unit) |
PDU | 协议数据单元 (Protocol Data Unit) |
MSB | 最高有效位(Most Significant Bit) |
LSB | 最低有效位 (Least Significant Bit) |
MBAP | Modbus应用协议(Modbus Application Protocol) |
PLC | 可编程逻辑控制器(Programmable Logic Controller) |
4. Modbus事务处理
Modbus协议允许在各种网终体系结构内进行简单通信,每种设备 (包括PLC、HMI、控制面板、驱动程序、动作控制、输入/输出设备) 都能使用Modbus协议启动远程操作。在基于串行链路和以太网 (TCP/IP)的Modbus上可以进行相互通信。一些网关允许在几种使用MODBUS协议的总线或网络之间进行通讯。
MOUBUS网络体系结构的实例:
Modbus是一个请求、应答协议,并且提供统一的功能码用于数据传输服务。Modbus功能码是Modbus请求/应答PDU (Protocol Data Unit,协议数据单元)的元素之一,所谓的PDU其实就是Modbus协议定义的一个与基础通信层无关的简单协议数据单元。特定总线或网络上的Modbus协议映射能够在ADU (Application Data UInit ,应用数据单元)上引入一些附加域,从而实现完整而准确的数据传输。
为了寻求一种简洁的通信格式,Modbus协议定义了PDU模型,即功能码+数据的格式,而为了适应多种传输模式,又在PDU的基础上增加了必要的前缀 (如地址域)和后缀(如差错校验) ,形成了ADU模型(见下图)。
通用MODBUS帧如下:
Modbus事务处理过程:
-
主机设备 (或客户端)创建Modbus应用数据单元形成查询报文,其中功能码标识了向从机设备 (或服务器端)指示将要执行的操作。其中功能码占用1字节,有效的码字范围是十进制1 ~ 255 (其中128 ~255为异常响应保留) 。查询报文创建完毕,主机设备 (或客户端) 向从机设备 (或服务器端)发送报文,从机设备 (或服务器端)接收报文后根据功能码做出相应的动作,并将响应报文返回给主机设备 (或客户端),如图下所示:
MouBus事务处理(无异常)
-
如果在一个正确接收的Modbus ADU中不出现与请求Modbus功能有关的差错,那么从机设备 (或服务器端) 将返回正常的响应报文。如果出现与请求Modbus功能有关的差错,那么响应报文的功能码域将包括一个异常码,主机设备(或客户端)能够根据异常码确定下一步执行的操作;对于异常响应,服务器返回一个与原始功能码等同的码,设置该原始功能码的最高有效位为逻辑1,用于通知主设备(客户端)。如下图所示:
MouBus事务处理(异常响应)
5.3 Modbus软件与使用
5.3.1 Modbus软件简介
为了更好的学习和理解Modbus,这里推出三个软件Modbus Poll(主站设备)、Modbus Slave(从站设备)和虚拟串口软件,借助三款设备我们可以在PC上做一些基础实验,更加直观地观察通信数据,加深我们的理解,我们将它称为Modbus学习必备三件套,这是一个很好的入门方法。
5.3.2 Modbus Poll(主站设备)
1. Modbus Poll简介
Modbus Poll是Modbus主站设备仿真器,用于测试和调试Modbus从设备便于观察Modbus通信过程中的各种报文数据。该软件支持ModbusRTU、ASCII、TCP/IP。用来帮助开发人员测试Modbus从设备,或者其它Modbus协议的测试和仿真。它支持多文档接口,即,可以同时监视多个从设备/数据域。每个窗口简单地设定从设备ID,功能,地址,大小和轮询间隔。你可以从任意一个窗口读写寄存器和线圈。如果你想改变一个单独的寄存器,简单地双击这个值即可。或者你可以改变多个寄存器/线圈值。提供数据的多种格式方式,比如浮点、双精度、长整型(可以字节序列交换)。该软件支持Modbus RTU、ASCII、TCP/IP等协议模式。
Modbus Poll支持下列协议模式:
Modbus RTU | Modbus RTU Over TCP/IP |
---|---|
Modbus ASCII | Modbus ASCI Over TCP/IP |
Modbus TCP/IP | Modbus RTU Over UDP/IP |
Modbus UDP/IP | Modbus ASCII Over UDP/IP |
2. Modbus Poll 使用
点击链接获取软件,按照提示安装即可;链接:
https://pan.baidu.com/s/1SpTRz6Z1XlkoCZjDozwqog 提取码:timc
下载完界面如下:
状态栏:
- Tx = 0表示向主站发送数据帧次数,图中为0次;
- Err = 0表示通讯错误次数,图中为0次;
- ID = 1表示模拟的Modbus子设备的设备地址,图中地址为1;
- F = 03表示所使用的Modbus功能码,图中为03功能码;
- SR = 1000ms表示发送周期,1S一次。
- 红字部分,表示当前的错误状态,“No Connection”表示未连接状态。
建立连接:
点击Connection->Connect进入配置页面,选择我们想要的连接,选择我们虚拟出来的串口,选择模式,例如:我们选择串口的连接方式,选则RTU模式,对应我们的Modbus RTU协议;接下来在设置波特率、比特位、校验位、停止位,如下图所示:
设置参数:点击Setup->Read/Write Definition进入配置页面,配置从机地址、功能码、地址类型、寄存器地址、访问数量、轮询时间,具体配置如下图:
5.3.3 Modbus Slave(从站设备)
1. Modbus Slave简介
Modbus从设备仿真器,可以仿真32个从设备/地址域。每个接口都提供了对EXCEL报表的OLE自动化支持。主要用来模拟Modbus从站设备,接收主站的命令包,回送数据包。帮助Modbus通讯设备开发人员进行Modbus通讯协议的模拟和测试,用于模拟、测试、调试Modbus通讯设备,便于观察Modbus通信过程中的各种报文数据;可以32个窗口中模拟多达32个Modbus子设备。与Modbus Poll的用户界面相同,支持功能01, 02, 03, 04, 05, 06, 15, 16, 22和23,监视串口数据。
Modbus Slave支持下列协议模式:
Modbus RTU | Modbus RTU Over TCP/IP |
---|---|
Modbus ASCII | Modbus ASCI Over TCP/IP |
Modbus TCP/IP | Modbus RTU Over UDP/IP |
Modbus UDP/IP | Modbus ASCII Over UDP/IP |
2. Modbus Slave使用
获取软件链接同上,下载完后主页面如图所示:
建立连接:
点击Connection->Connect进入配置页面,选择我们想要的连接,选择我们虚拟出来的串口,选择模式,接下来在设置波特率、比特位、校验位、停止位,如下图所示:
设置参数:点击Setup->Read/Write Definition进入配置页面,配置从机地址、功能码、地址类型、寄存器地址、访问数量,具体配置如下图:
这里有两点需要我们注意一下:
- 一是:Function列表框选择功能中的0x~4x,表示的是存储区0区、1区、3区、4区
- 输出线圈
- 输入线圈
- 保持寄存器
- 输入寄存器
Modbus协议规定了4个存储区 分别是0、1、3、4区 其中0区和4区是可读可写,1区和3区是只读。
区号 | 名称 | 读写 | 地址范围 |
---|---|---|---|
0区 | 输出线圈 | 可读可写布尔量 | 00001-09999 |
1区 | 输入线圈 | 只读布尔量 | 10001-19999 |
3区 | 输入寄存器 | 只读寄存器 | 30001-39999 |
4区 | 保持寄存器 | 可读可写寄存器 | 40001-49999 |
- 二是:Address项,这里需要特别强调一下,Address表示Modbus寄存器地址,其取值范围与设备寄存器地址存在映射关系,如下表所示:
Device address | Modbus address | Description | Function | R/W |
---|---|---|---|---|
1…10000 | address - 1 | Coils(outputs) | 01 | Read/Write |
10001…20000 | address - 10001 | Discrete Inputs | 02 | Read |
40001…50000 | address - 40001 | Holding Registers | 03 | Read/Write |
30001…40000 | address - 30001 | Input Registers | 04 | Read |
这里我们只简单介绍下地址和存储区,下面我们会详细展开。
5.3.4 虚拟串口软件
1.软件简介
虚拟串口工具,可以创建2个互联的串口,如下图所示:
比如Modbus Poll工具使用COM1发送数据给COM2,Modbus Slave从COM2读到数据。使用虚拟串口,就可以不使用开发板也可以体验Modbus Poll、Modbus Slave。
软件在网盘里:
2.虚拟串口的使用
安装后运行虚拟串口程序“Virtual Serial Port Tools”,安装下图创建2个串口:
打开设备管理器,可以看到如下串口:
5.3.5 Modbus Poll 与 Modbus Slave互联/通
下面我们进行Modbus Poll 与 Modbus Slave互联互通实验,通过形象直观的方式展示Modbus数据流,根据前面的设定我们已将知道了如何运用Modbus学习必备三件套,下面我们就通过三件套来进行实验,首先打开VSPD虚拟串口软件,设置虚拟串口,我这里就以上面设订COM1,COM2为例,接下来我们再来配置我们的Modbus Poll 与 Modbus Slave;
我们首先打开Modbus Slave端,设置连接,连接方式我们选择Serial Port串口连接,选择我们设置的串口COM1,模式选择RTU模式,如下图所示:
- Modbus Slave连接设定
在设置参数,从机地址我们设定1(你也可以自己随意设定),Function项我们选择03 Holding Register(4x),地址类型我们选择DEC(十进制格式),Address首地址我们设置为0,访问寄存器数量设置为10,如下图所示:
- Modbus Slave参数设定
接下来我们再来设置Modbus Poll端,设置方法也是和Modbus Slave端一一对应的,连接设定,参数设定,如下图所示:
- Modbus Poll连接设定
注意这里串口要选择我们设定的COM20,其它串口参数必须一一对应。
- Modbus Poll参数设定
设置好后,我们主设备和从设备分别连接了我们设置的COM1,COM2,这样我们便可观察当前寄存器的读取情况。
我们双击Modbus Poll(主设备端)地址中的0值,便可打开值设置窗口如下图所示:
修改值为66,点击Send打开Modbus Slave(从设备端)便可发现也做出了改变,如下图所示:
我们还可以打开Modbus Poll,点击Display,选择Commuaction,查看发送的报文:
TX是我们主站发送的报文,RX是从站返回的报文,报文我们下面会展开说明,带领大家一起看报文;
5.4 Modbus协议细节
5.4.1 Modbus协议概述
简而言之,Modbus 协议是一种单主/多从的通信协议,其特点是在同一时间总线上只能有一个主设备,但可以有一个或者多个(最多 247 个)从设备。Modbus通信总是由主设备发起,当从设备没有收到来自主设备的请求时,从设备不会主动发送数据。从设备之间不能相互通信,主设备只能同时启动一个 Modbus 访回事务处理。 主设备可以采用两种方式向从设备发送 Modbus 请求报文,即主设备可以对指定的单个从设备或者线路上所有的从设备发送请求报文,而从设备只能在被被动接收请求报文后给出响应报文,即应答。这两种模式分别如下图所示:
Modbus请求应答周期
单播模式。主设备仅仅寻址单个从设备,从设备接收并处理请求后,向主设备返回一个响应报文,即应答。在这种模式下,一个 Modbus 事务处理包含两个报文:一个是主设备的请求报文,另一个是从设备的响应报文。
每个从设备必须有唯一的地址(地址范围为 1~247),这样才能区别于其它从设备,从而可以独立被寻址,同时主设备不占用地址。
广播模式。此种模式下,主设备可以向所有从设备发送请求指令,而从设备在接收到广播指令后仅进行相关指令的事务处理,而不要求返回应答。因此广播模式下,请求指令必须是 Modbus 标准功能中的写指令。
根据 Modbus 标准协议的要求,所有从设备必须接收广播模式下的写指令,且地址0被保留,用来识别广播通信
-
请求:
主设备发送的请求报文主要包括从设备地址(或广播地址0)、功能码、传输的数据以及差错检测字段。
查询消息中的功能码告诉我们被选中的从设备地址要执行何种功能。数据段包含从设备要执行功能的所有附加信息。例如,功能代码 03 要求从设备读取保持寄存器并返回其内容。
数据段必须包含要告诉从设备的信息:从哪个寄存器开始读取及要读取的寄存器数量。差错检测域为从设备提供一种验证消息内容是否正确的方法。
-
应答:
从设备的应答报文包括地址、功能码、差错检测域等; 如果从设备产生了一个正常的回应,则回应消息中的功能码是查询消息中的功能码的回应。数据段包括从设备收集的数据,如寄存器值或状态。如果有错误发生,则功能码将被修改以用于指出回应消息是错误的,同时数据段包含描述此错误信息的代码。差错检测域允许主设备确认消息内容是否可用。
对于串行链路来说,又存在两种传输模式:ASCII(American StandardCode for Information Interchange,美国标准信息交换码)模式和 RTU(RemoteTerminal Unit,远程终端单元)模式。但是,对于同一网络或链路来说,所有设备必须保持统一,要么统一为 ASCII 模式,要么统一为 RTU 模式,不可共存。相对来说,RTU模式的传输效率更高,因此在当前普遍的生产环境中,RTU 模式获得了广泛应用,而 ASCII模式只能作为特殊情况下的可选项。
5.4.2 Modbus寄存器(存储区)
Modbus 协议中的一个重要概念是寄存器,所有数据均存放于寄存器。最初,Modbus 协议借鉴了 PLC 中寄存器的含义,但是随着 Modbus 协议的广泛应用,寄存器的概念进一步泛化,它不再是指具体的物理寄存器,也可能是指一块内存区域Modbus 寄存器根据存放的数据类型以及各自的读写特性将寄存器分为四部分,这四部分既可以连续,也可以不连续,由开发者决定。寄存器意义如下表所示:
Modbus寄存器(存储区)
寄存器种类 | 说明 | 与PLC类比 | 举例说明 |
---|---|---|---|
线圈状态(Coil Status) | 输出端口。可设定端口输出状态,也可以读取该位的输出状态。可分为两种不同的执行状态,列如保持型或边沿触发型 | DO(数字量输出) | 电磁阀输出、MOSFEF输出、LED显示等 |
离散输入状态(Input Status) | 输入端口。通过外部设定改变输入状态,可读但不可以写 | DI(数字量输入) | 拨码开关、接近开关等 |
保持寄存器(Holding Register) | 输出参数或保持参数,控制器运行时被设定的某些参数,可读可写 | AO(模拟量输出) | 模拟量输出设定值,PID运行参数,变量阀输出大小,传感器报警上限下限 |
输入寄存器 | 输入参数。控制器运行时从外部设备获得的参数,但可读不可写 | AI(模拟量输入) | 模拟量输入 |
(PLC)数据的传输离不开存储和读写操作,为了更好存储不同的数据类型,我们可以理解Modbus 会将布尔和非布尔的数据分开存储。
1. 存储区类型
我们可以将存储区类型分为布尔类型和非布尔类型,布尔类型我们用线圈(Coil)表示,非布尔用寄存器(Register)表示;
-
什么是布尔?
- 布尔类型只有两个值,false 和 true。
- 通常用来判断条件是否成立。
- C语言语法规定,如果变量值为 0 就是 false,否则为 true,布尔变量只有这两个值。
因此,我们便有了线圈和寄存器的概念;
- 那线圈和寄存器又是什么?
- 线圈: 从电气角度来看,在电气控制回路中,一般都是靠接触器或中间继电器来实现控制,接触器或中继最终靠的是线圈的得电和失电来控制触点闭合和断开,因此用线圈表示布尔量;
- 寄存器: 用来暂时存放参与运算的数据和运算结果,具有接收数据、存放数据和输出数据的功能。
- 而寄存器在计算机中,就是用来存储数据的,因此非布尔的数据放在寄存器里。
回到存储区分类,目的就是: 更好地存储和区分不同的数据类型。
Modbus的线圈和寄存器应该也按照只读、读写来进一步划分,因此这就形成了Modbus的存储区,如下表所示:
- Modbus存储区类型及名称
序号 | 读写 | 存储类型 | 存储区名称 |
---|---|---|---|
1 | 只读 | 线圈 | 输入线圈 |
2 | 读写 | 线圈 | 输出线圈 |
3 | 只读 | 寄存器 | 输入寄存器 |
4 | 读写 | 寄存器 | 保持寄存器 |
- 存储区代号:
为什么需要存储区代号?上面表格里的存储区名称是一个全称,开发和使用中使用全称会比较麻烦,因此需要给他们取个别名。所以Modbus也要给这些存储区取一个代号,干脆直接用数字吧,于是,就有了下面的规定:
存储区名称 | 存储区代号 |
---|---|
输入线圈 | 1区 |
输出线圈 | 0区 |
输入寄存器 | 3区 |
保持寄存器 | 4区 |
存储区代号其实可以简单理解为我们的名字,例如我们的名字有全名,和小名,全名呢是正式场合,外人叫的,小名呢是我们的亲近的人,日常场合叫的。这么一说是不是就理解为什么有存储区代号啦。
- 存储区范围:
- 无论是什么存储区,都会有一个范围的限制;Modbus的每个存储区也规定了一个范围,不能无限制使用。
- Modbus规定每个存储区的最大范围是65536,也就是0~65535。
2. 协议地址模型
PLC地址是我们常见的,但它是怎么组成的的,它是由存储区代号 + 地址组成,我们把这样的地址称为绝对地址,把后面的地址成为相对地址;而我们的Modbus地址跟PLC地址也是类似的;
Modbus地址公式:存储区代号 + (地址 + 1)如下表所示:
- Modbus长地址模型
存储区名称 | 存储区代号 | 绝对地址范围 | 相对地址范围 |
---|---|---|---|
输入线圈 | 1区 | 100001~165536 | 0~65535 |
输出线圈 | 0区 | 000001~065536 | 0~65535 |
输入寄存器 | 3区 | 300001~365536 | 0~65535 |
保持寄存器 | 4区 | 400001~465536 | 0~65535 |
Modbus地址分为长地址模型和短地址模型,上面Modbus长地址模型,但是在实际使用中,我们一般用不了这么多地址,一般情况下,10000以内就已经足够使用了;因此,为了方便我们便有了另一种短的地址模型,如下图所示:
- Modbus短地址模型
存储区名称 | 存储区代号 | 绝对地址范围 | 相对地址范围 |
---|---|---|---|
输入线圈 | 1区 | 10001~19999 | 0~9998 |
输出线圈 | 0区 | 00001~09999 | 0~9998 |
输入寄存器 | 3区 | 30001~39999 | 0~9998 |
保持寄存器 | 4区 | 40001~49999 | 0~9998 |
5.4.3 Modbus常用功能码
Modbus功能码占用1字节,取值范围是1127。之所以127以上不能用,是因为Modbus规定当出现异常时,功能码+0x80(十进制128)代表异常状态,因此129(1+128)255(127+128)的取值代表异常码。
- Modbus常用功能码
功能码 | 描述 | 寄存器PLC地址 | 位/字操作 | 操作数量 |
---|---|---|---|---|
01H | 读线圈寄存器(读输出线圈) | 00001~09999 | 位操作 | 单个或多个 |
02H | 读离散输入寄存器(读输入线圈) | 10001~19999 | 位操作 | 单个或多个 |
03H | 读保持寄存器 | 40001~49999 | 字操作 | 单个或多个 |
04H | 读输入寄存器 | 30001~39999 | 字操作 | 单个或多个 |
05H | 写单个线圈寄存器 | 00001~09999 | 位操作 | 单个 |
06H | 写单个保持寄存器 | 40001~49999 | 字操作 | 单个 |
0FH(15) | 写多个线圈寄存器 | 00001~09999 | 位操作 | 多个 |
10H(16) | 写多个保持寄存器 | 40001~49999 | 字操作 | 多个 |
功能码可以分为位操作和字操作两类。位操作的最小为1位(bit),字操作的最小单位为2字节。
① 位操作指令:读取线圈状态的功能码 01 ,读(离散)输入状态功能码 02 ,写单个线圈功能码 05 和写多个线圈功能码 15。
② 字操作指令:读保持寄存器功能码 03 ,读输入寄存器功能码 04 ,写单个保持寄存器功能码 06 ,写多个保持寄存器功能码 16 。
5.4.4 Modbus协议类型
Modbus可以在各种介质上传输,那么他的传输模式也分为三种。包括ASCII、RTU(远程终端控制系统)、TCP三种报文类型
- 常用Modbus协议
Modbus协议 | Modbus协议 |
---|---|
Modbus RTU | Modbus RTU Over TCP/IP |
Modbus ASCII | Modbus ASCI Over TCP/IP |
Modbus TCP/IP | Modbus RTU Over UDP/IP |
Modbus UDP/IP | Modbus ASCII Over UDP/IP |
Modbus协议使用串口传输时可以选择RTU或ASCII模式,并规定了消息、数据结构、命令和应答方式并需要对数据进行校验。ASCII 模式采用LRC校验,RTU模式采用16 位CRC校验。通过以太网传输时使用TCP,这种模式不使用校验,因为TCP协议是一个面向连接的可靠协议。
5.4.5 Modbus报文帧
一个报文就是一帧数据,一个数据帧就一个报文: 指的是一串完整的指令数据,本质就是一串数据。
Modbus报文是指主机发送给从机的一帧数据,其中包含着从机的地址,主机想执行的操作,校验码等内容
1. Modbus ASCII 模式
当控制器设为在Modbus网络上以ASCII模式通信时,在消息中每个8位(b)字节都将作为两个 ASCII字符发送。这种方式的主要优点是字符发送的时间可隔可达到 1秒且不产生错误。
在ASCII模式下,消息以冒号(:)字符(ASCII码为 0x3A)开始,以回车换行奇结束(ASCII码为 0x0D、0x0A)。消息的其他字段(域)可以使用的传输字符是十六进制的0···9、A···F。处于网络上的 Modbus 设备不断侦测“:”字符,当接收到一个冒号时,每个设备进入解码阶段,并解码下一个字段(地址域)以判断是否是发给自己的。消息帧中的字符间发送的时间间隔最长不能超过 1秒,否则接收设备将认为发生传输错误。
下表是一个典型的ASCII报文格式:
开始字符 | 地址 | 功能码 | 数据 | LRC校验 | 结束字符 |
---|---|---|---|---|---|
1byte (😃 | 2byte | 2byte | Nbyte | 2byte | CR、LF |
2. Modbus RTU 模式
Modbus协议RTU报文格式如下所示:
从机地址 | 功能码 | 数据 | CRC校验 |
---|---|---|---|
1byte | 1byte | Nbyte | 2byte |
- 帧结构 = 从机地址 + 功能码 + 数据 + 校验
- 从机地址: 每个从机都有唯一地址,占用一个字节,范围0-255,其中有效范围是1-247,其中255是广播地址(广播就是对所有从机发送应答)
- 功能码: 占用一个字节,功能码的意义就是,知道这个指令是干啥的,比如你可以查询从机的数据,也可以修改从机的数据,所以不同功能码对应不同功能.
- 数据: 根据功能码不同,有不同功能,比方说功能码是查询从机的数据,这里就是查询数据的地址和查询字节数等。
- 校验: 在数据传输过程中可能数据会发生错误,CRC检验检测接收的数据是否正确
3. 串行报文帧总结:
ModbusASCII有开始字符(和结束字符(CR LF),可以作为一帧数据开始和结束的标志,而ModbusRTU没有这样的标志,需要用时间间隔来判断一帧报文的开始和结束,协议规定的时间为3.5个字符周期,就是说一帧报文开始前,必须有大于3.5个字符周期的空闲时间,一帧报文结束后,也必须要有3.5个字符周期的空闲时间否则就会出现粘包的情况。
注意:针对3.5个字符周期,其实是一个具体时间,但是这个时间跟波特率相关。 在串口通信中,1个字符包括1位起始位、8位数据位(一般情况)、1位校验位(或者没有)、1位停止位(一般情况下),因此1个字符包括11个位,那么3.5个字符就是38.5个位,波特率表示的含义是每秒传输的二进制位的个位,因此如果是9600波特率,3.5个字符周期=38.5/9600=0.00401s*1000=4.01ms
5.4.6 Modbus 差错校验
在Modbus串行通信中,根据传输模式(ASCII或RTU)的不同,差错校验域将采用不同的校验方法。
-
ASCII模式
在ASCII模式中,报文包含一个错误校验字段,该字段由两个字符组成,其基于对全部报文内容执行的纵向冗余校验(Longitudinal Redundancy ChedLRC)计算的结果而来,计算对象不包括起始的冒号(:)和回车换行符号(CR LF)。
-
RTU模式
在RTU模式中,报文同样包含一个错误校验字段。与ASCII模式不同的是该字段由 16 个比特位共2字节组成,其值基于对全部报文内容执行的循环冗余校验(Cyclical Redundancy Check,CRC)计算的结果而来,计算对象包括校验域之的所有字节。
1. LRC校验
在ASCII模式中,消息是由特定的字符作为帧头和帧尾分隔的。
一条消息必须以“冒号”(:)字符(ASCII码为0x3A)开始,以“回车换行(CRLF)(ASCII码为 0x0D 和 0x0A)结束。LRC 校验算法的计算范围为“:” “CRLF”之间的字符。
从算法本质来说,LRC域自身为1字节,即包含一个 8位二进制数据,由发送设备通过LRC算法把计算值附到信息末尾。接收设备在接收信息时通过 LRC法重新计算值,并把计算值与 LRC 字段中接收的实际值进行比较。若两者不同,则产生一个错误,返回一个异常响应帧,即对报文中的所有相邻的两个 8位字相加,丢弃任何进位,然后对结果进行二进制补码,计算出 LRC值。
必须注意的是,计算 LRC 校验码的时机是在对报文中每个原始字节进行ASCII码编码之前,对每个原始字节进行LRC校验的计算操作。
- LRC校验流程:
- 将消息中的全部字节相加(不包括起始“:”和结束符(CRLF),并把结果送入8位数据区,舍弃进位。
- 由0xFF(即全 1)减去最终的数据值,产生1的补码(即二进制反码)
- 加1产生二进制补码。
以上产生的LRC值占用1字节,但实际上在通过串行链路由ASCII模式传递消息顿时,LRC的结果(1字节)被编码为2字节的ASCII字符,并将其放置在ASCII模式报文帧的CRLF字段之前。
Modbus 标准协议的英文版提供了 LRC算法,其中的参数意义如下unsigned char*auchMsg:含有生成LRC所使用的二进制数据的报文缓存区指针。 unsigned short usDataLen:报文缓存区中的字节数。
LCR的代码如下:
/*函数返回unsigned char类型的 LRC值*/
static unsigned char LRC(unsigned char * auchMsg, unsigned short usDatalen)
{
unsigned char uchLRC=0; /*LRC字节初始化*/
while(usDataLen--) /*遍历报文缓冲区*/
uchLRC+=*auchMsg++; /*缓冲区宇节相加,自动舍弃进位*/
return ((unsigned char)(-(( char)uchLRC))); /*返回二进制补码*/
}
下面举一个简单的例子。假设从设备地址为 1,要求读取输人寄存器地址30001的值,则具体的查询消息帧如下:
":" , "0" , "1" , "0" , "4" , "0" ,"0" ,"0" ,"0" ,"0" ,"0" ,"0" , "1" , "F" , "A" ,CR/LF
其中,“F”, “A”即为LRC值在 ASCII模式下的形式,即0xFA.
2. CRC校验
在Modbus RTU传输模式下,通信报文(帧)包括一个基于循环冗余校验方法的差错校验字段。
Modbus协议采用了CRC-16标准校验方法。在RTU模式下,CRC自身由2字节组成,即CRC是一个16位的值。CRC字段校验整个报文的内容,无论报文中的单个字节采用何种奇偶校验方式,整个通信报文均可使用CRC-16校验算法,CRC字段作为报文的最后字段添加在整个报文末尾。
需要注意的是,因为CRC-16是由2字节组成,所以涉及哪个字节放在前面,哪个字节放在后面传输的问题,即大小端模式的选择问题。另外,由于Modbus协议规定寄存器为16位(即2字节)长度,因此大小端问题的存在给很多初学者造成了困扰,下一章我们会重点讲一下大小端的问题。
- CRC校验流程:
- 预置一个16位寄存器为0xFFFF(全1),称之为CRC寄存器。
- 把数据帧中的第一个字节的8位与CRC寄存器中的低字节进行异或运算,结果存回CRC寄存器。
- 将CRC寄存器向右移一位,最高位填以0,最低位移出并检测是0还是1。
- 如果最低位为0:重复第三步(再次右移一位);如果最低位为1:将CRC寄存器与一个预设的固定值(0xA001)进行异或运算。
- 重复第三步和第四步直到8次移位。这样处理完了一个完整的八位。
- 重复第2步到第5步来处理下一个八位,直到所有的字节处理结束。
- 将该通信消息帧的所有字节按上述步骤计算完成后,再将得到的16位CRC寄存器的高、低位字节进行交换,即发送时首先添加低位字节,然后添加高位字节。
- 最终CRC寄存器的值就是CRC的校验码。
需要注意的是,在进行CRC计算时只有串行链路上的每个字符的8个数据位参与计算,从而起始位、停止位、奇偶校验位等都不参与CRC计算。
常用的CRC-16算法有查表法、计算法。
查表法:
CRC查表法是将位移异或的计算结果做成了一个表,即将0~256 放入一个度为16位的寄存器的低8位,高8位填充0,然后将该寄存器与多项式0xA001照上述步骤3、4直到8 位全部移出,最后寄存器中的值就是表格中的数据,高8位、低 8 位分别单独做成一个表。 实际上,Modbus 标准协议的英文版提供了CRC 查表算法 函数的输入参数意义如下:
unsigned char * puchMsg; /*要进行CRC校验的消息*/
unsigned short usDataLen; /*消息中的字节数*/
/*函数返回 unsigned short(即2个字节)类型的 CRC值*/
unsigned short CRC16(unsigned char *puchMsg,unsigned short usDataLen)
{
unsigned charuchCRCHi=0xFF; /*高 CRC字节初始化* /
unsigned char uchCRCLo=0xFF; /*低 CRC字节初始化*/
unsigned short uIndex; /*CRC 循环表中的索引*/
while (usDataLen--) /* 循环处理传输缓冲区消息 */
{
uIndex=uchCRCHi ^ * puchMsg++; /*计算 CRC* /
uchCRCHi=uchCRCLo ^ auchCRCHi[uIndex];
uchCRCLo=auchCRCLo[uIndex];
}
return (uchCRCHi <<81uchCRCLo);
}
其中,auchCRCHi和auchCRCLo的定义分别如下:
static unsigned char auchcRCHi[] =
{
0x00,0xC1,0x81,0x48,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,0x01,
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,0xCl,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,0xDB,0x18,0x19,0xD9,0x1B,0xDB,0xDA,0x1A,0x1E,0XDE,0XDE,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,0xE3,0xE2,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,0xAB,0x68,0x78,0xB8,0xB9,0x79,0xBB,
0x7B,0x7A,0xBA,0xBE,0x7E,0x7E,0xBE,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,0x8E,0x4F,0x8D,0x4D,0x4C,0x8C,
0x44,0x84,0x85,0x45,0x87,0x47,0x46,0x86,0x82,0x42,0x43,0x83,0x41,0x81,0x80,
0x40
};
注意:实际编程时,auchcRCHi[]和auchCRCLo[]的定义应该放在函数CRC-16()之前。
查表法可以进一步化简如下:
unsigned short CRC16(unsigned char * puchMsg,unsigned short usDataLen)
{
static const unsigned short usCRCTable[]=
{
0x0000,0xC0C1,0xC181,0x0140,0XC301,0X03C0,0X0280,0xc241,
0XC601,0X06C0,0x0780,0XC741,0X0500,0XC5C1,0XC481,0X0440,
0xCC01,0X0CC0,0X0D80,0XCD41,0X0F00,0XCEC1,0XCE81,0X0E40,
0X0A00,0XCAC1,0XCB81,0X0B40,0XC901,0X09C0,0X0880,0XC841,
0XD801,0x18c0,0X1980,0XD941,0X1B00,0XDBC1,0XDA81,0X1A40,
0X1E00,0XDEC1,0XDF81,0X1F40,0XDD01,0X1DC0,0X1C80,0XDC41,
0x1400,0XD4C1,0XD581,0X1540,0XD701,0X17C0,0X1680,0XD641,
0XD201,0X12c0,0X1380,0XD341,0X1100,0XD1C1,0XD081,0X1040,
0XF001,0X30C0,0X3180,0XE141,0X3300,0XE3C1,0XE281,0X3240,
0X3600,0XF6C1,0XE781,0X3740,0XE501,0X35C0,0X3480,0XE441,
0X3C00,0XFCC1,0XFD81,0X3D40,0XFF01,0X3FC0,0X3EB0,0XFE41,
0XFA01,0X3AC0,0X3B80,0XFB41,0X3900,0XE9C1,0XF881,0X3840,
0X2800,0XE8C1,0XE981,0X2940,0XEB01,0X2BC0,0X2A80,0XEA41,
0XEE01,0X2EC0,0X2F80,0XEF41,0X2D00,0XEDC1,0XEC81,0X2C40,
0XE401,0X24C0,0X2580,0XE541,0X2700,0XE7C1,0XE681,0X2640,
0x2200,0XE2C1,0XE381,0X2340,0XE101,0X21C0,0X2080,0XE041,
0XA001,0X60C0,0X6180,0XA141,0X6300,0XA3C1,0XA281,0X6240,
0X6600,0XA6c1,0XA781,0X6740,0XA501,0X65C0,0X6480,0XA441,
0X6C00,0XACC1,0XAD81,0X6D40,0XAF01,0X6EC0,0X6E80,0XAE41,
0XAA01,0X6AC0,0X6B80,0XAB41,0X6900,0XA9C1,0XA881,0X6840,
0X7800,0XB8C1,0XB981,0X7940,0XBB01,0X7BC0,0X7A80,0XBA41,
0XBE01,0X7EC0,0X7F80,0XBF41,0X7D00,0XBDC1,0XBC81,0X7C40,
0XB401,0X74C0,0X7580,0XB541,0X7700,0XB7C1,0XB681,0X7640,
0X7200,0XB2C1,0XB381,0X7340,0XB101,0X71C0,0X7080,0XB041,
0X5000,0X90c1,0X9181,0X5140,0X9301,0X53c0,0X5280,0X9241,
0X9601,0X56C0,0X5780,0X9741,0X5500,0X95c1,0X9481,0X5440,
0X9C01,0X5cc0,0X5D80,0X9D41,0X5E00,0X9FC1,0X9E81,0X5E40,
0X5A00,0X9AC1,0X9B81,0X5B40,0x9901,0x59c0,0x5880,0X9841,
0x8801,0X4BC0,0X4980,0X8941,0X4B00,0XBBC1,0X8AB1,0X4A40,
0X4E00,0X8EC1,0X8F81,0X4F40,0X8D01,0X4DC0,0X4C80,0X8C41,
0X4400,0X84c1,0x8581,0X4540,0X8701,0X47C0,0X4680,0X8641,
0x8201,0X42c0,0x4380,0X8341,0X4100,0XB1C1,0X8081,0X4040,
};
unsigned char nTemp;
unsigned short usRegCRC = 0xFFFF;
while (usDataLen--)
{
nTemp = * puchMsg ++ ^ usRegCRC;
usRegCRC >> = 8;
usRegCRC ^= usCRCTable[nTemp];
}
return usRegCRC;
}
查表法的特点是以字节为单位进行计算,速度快,语句少,但表格会占用一定的程序空间。
- 计算法:
计算法按位计算,适用于所有长度的数据校验,最为灵活;但由于是按位计算,其效率并不是最优的,只适用于对速度不敏感的场合。计算法的基本算法如下; 输入参数的意义:
unsigned char * puchMsg; /*要进行 CRC校验的消息* /
unsigned short usDataLen; /*消息中的字节数*/
/*函数返回unsigned short(即 2个字节)类型的CRC值*/
unsigned short CRC16(unsigned char *puchMsg,unsigned short usDataLen)
{
int i,j; /*循环变量*/
unsigned shortusRegCRC =0xFFFF; /*用于保存CRC值*/
for(i=0;i < usDataLen;i++) /*循环处理传输缓冲区消息*/
{
usRegCRC ^= * puchMsg++; /*异或算法得到CRC值*/
for(j=0;j<8;j++) /*循环处理每个 bit位*/
{
if (usRegCRC &0x0001)
usRegCRC =usRegCRC >>1^0xA001;
else
usRegCRC >>=1;
}
}
return usRegCRC;
}
下面举一个简单的例子。假设从设备地址为 1,要求读取输入寄存器地址30001的值,则RTU模式下的具体查询消息帧如下:
0x01,0x04,0x00,0x00,0x00,0x01,0x31,0xCA
其中,0xCA31即为CRC值。因为Modbus规定发送时CRC必须低字节在前、高字节在后,因此实际的消息帧的发送顺序为0x31,0xCA。
5.4.7 字节序和大小端
Modbus中传输的数据,按照“大字节序”来传输,比如:
寄存器数值是0x1234,先传输0x12,再传输0x34。
在 Modbus 寄存器中,对于一个由 2字节组成的16数,在内存中存储这两个字节有两种方法:一种是将低序字节存储在起始地址为小端(Little-Endian)字节序;另一种方法是将高序字节存储在起始地称为大端(Big-Endian)字节序。Modbus 通信协议中具体规定了字节高低位发送顺序,这样就自然引出了字节序和大小端的问题。
-
什么是大端:
所谓大端,是指数据的低位保存在内存的高地址中,数据的高位保存在内存的低地址中。
-
什么是小端:
所谓小端,是指数据的低位保存在内存的低地址中,数据的高位保存在内存的高地址中。
-
为什么会有大小端:
计算机系统是以字节为单位的,每个地址单元都对应着1个字节,一个字节为8bit。但在C语言中除了8bit的char类型,还有16bit的short类型和32bit的long类型,还有就是对于位数大于8位的处理器,如16位或32位的处理器,由于寄存器宽度大于一个字节,那么必然存在一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式的出现。
低位字节和高位字节:好比如:123456 其中的1就是高位数字,6就是低位数字。
举一个例子,在32位数字0x12345678在内存中的表示形式为:
1)大端模式:
低地址 | —— | —— | 高地址 |
---|---|---|---|
0x12 | 0x34 | 0x56 | 0x78 |
2)小端模式:
低地址 | —— | —— | 高地址 |
---|---|---|---|
0x78 | 0x56 | 0x34 | 0x12 |
5.4.8 Modbus报文分析
在第二章中我们已经生成了一个报文,我们就拿此报文来逐步分析一下,报文如下:
我们可以看到上面报文都是循环发送的,这样看起来不太容易分析,我摘抄下来其中一组来给大家分析
发送:
从机地址 | 功能码 | 起始地址高位 | 起始地址低位 | 寄存器数量高位 | 寄存器数量低位 | CRC高位 | CRC低位 |
---|---|---|---|---|---|---|---|
01 | 03 | 00 | 00 | 00 | 0A | C5 | CD |
响应:
从机地址 | 功能码 | 返回字节数 | 数据位 | CRC高位 | CRC低位 |
---|---|---|---|---|---|
01 | 03 | 14 | 00 42… | CF | 10 |
这里我们就以03功能码为例来分析一下报文;
- 03发送报文格式:从机地址+功能码+加起始地址+寄存器数量+CRC校验
- 03接受报文格式:从机地址+功能码+字节数+具体数据+CRC校验
首先我们看一下发送报文:
从机地址是01,功能码03,起始地址00,寄存器数量是十六进制0A也就是10,和门设置的是一样的,我们来对对照一下我们设置的参数:
我们再来看一下接收报文:
从机地址是01,功能码03,返回字节数是十六进制14也就是返回20给字节,我们发送是个返回20个字节也是对的上的,第一个数据位是00 42 也是和我们发送的可以对上,十六机制42,也就66,我们来看一下我们之前设置的参数:
5.4.9 Moubus TCP 消息帧格式
1.协议描述
在Modbus TCP/IP中,串行链路中的主/从设备分别演变为客户端/服务器端设备,即客户端相当于主站设备,服务器端设备相当于从设备。基于TCP/IP网络的传输特性,串行链路上一主多从的机构也演变为多客户端/多服务器端的构造模型。Modbus协议在TCP/IP上的实现是在TCP/IP层上的应用,它需要一个完整的TCP/IP栈作为支撑,Modbus TCP/IP服务器端通常使用端口502作为接收报文的端口。
下图为Moubus TCP的通讯结构:
ModbusTCP与ModbusUDP的报文格式是一样的,它们之间的区别其实就是TCP与UDP的区别,因此下面就针对ModbusTCP的协议进行分析,ModbusTCP与ModbusRtu(ModbusASCII)之间的区别如下图:
从上图可以看出,ModbusTCP在Modbus串行通信的基础上,去除了校验(由于TCP本身就带有校验和)和设备地址(ModbusTCP弱化了设备地址,用IP地址来取代),再加上MBAP报文头(占7 bytes),下面针对MBAP进行分析说明:
字段名 | 长度 | 描述 | 主站(客户端) | 从站(服务器端) |
---|---|---|---|---|
事务处理标识符(Transaction Identifier) | 2字节 | Modbus 请求/应答传输过程的事务处理的识别码,可以设置为0,也可以设置为每次通讯时自动+1 | 主站(客户端)生成 | 应答时从主站(客户端)复制该值 |
协议标识符(Protocol Identifier) | 2字节 | 00表示是 Modbus协议,固定值 | 主站(客户端)生成 | 应答时从主站(客户端)复制该值 |
长度(Lendgth) | 2字节 | 从单元标识符开始,整个PDU的数据长度,分为字节长度Hi和Lo | 请求时生成 | 应答时重新生成 |
单元标识符(Unit Identifier) | 1字节 | 串行链路或其他总线上连接的远程从站的识别码,可以设置为从机设备的地址 | 主站(客户端)生成 | 应答时从主站(客户端)复制该值 |
事务处理标识符:
事务处理标识用于在查询报文与未来响应之间建立联系。因此,对 TCP/IP 连接来说,在同一时刻这个标识符必须是唯一的。有以下几种使用此标识符的方式。
例如,可以将传输标识作为一个带有计数器的简单“TCP 发送顺序号”,在每个请求发送时自动+1;也可以用作智能索引或指针,用来识别事务处理的内容,以便记忆当前的远端服务器和未处理的请求。
服务器端可接收的请求数量取决于其容量,即服务器资源量和 TCP 窗口尺同样,客户端同时启动事务处理的数量也取决于客户端的资源容量。
单元标识符:
在对Modbus或 Modbus+等串行链路子网中的设备进行寻址时,这个域用于路由的目的。在这种情况下,单元标识符(Unit Identifier) 携带一个远端设备的Modbus 从站地址。
如果 Modbus 服务器连接到 Modbus+或 Modbus 串行链路子网,并通过一个网桥或网关配置这个服务器的IP 地址,则 Modbus 单元标识符对识别连接到网桥或网关后的子网的从站设备是必需的。TCP 连接中的目的 IP 地址识别了网桥本身的地址,而网桥则使用 Modbus 单元标识符将请求转交给正确的从站设备。 分配给串行链路上的 Modbus 从站设备地址为 1~247(十进制),地址0作为广播地址。
对单纯的 Modbus TCP/IP 设备来说用IP 地址即可寻址 Modbus 服务器端设备,此时 Modbus 单元标识符是无用的,必须使用值0xFF 填充。当对直接连接到 TCP/IP 网络上的 Modbus 服务器寻址时,建议不要在“单元标识符”域使用有效的Modbus从站地址。
以上是MBAP报文头个字段含义的详细说明。
实际上,在 Modbus TCP/IP传输过程中,服务端(从机)返回的响应报文中同样包含 MBAP报头,除了 Length 字段外,其他字段均与客户端一致。Modbus消息由 TCP/IP 层提供,不需要像串行链路那样自己判断一帧是否结束,所有数据传输均由 TCP/IP层处理。因为底层的 TCP/IP 确保了端到端的连接,而且 TCP/IP链路层已确保传输数据的准确性,所以 Modbus TCP/IP 中已不再需要 LRC或CRC 等校验功能。
2. 查询与响应报文示例
对于Modbus TCP消息帧格式,下面举例说明各部分的含义。
-
查询报文:00 00 00 00 00 06 09 03 00 04 00 01。
- 0x06: 后续还有6字节。
- 0x09: 单元标识符为9。
- 0x03: 功能码 3,即读保持寄存器的值。
- 0x00 0x04: Modbus起始地址4(即40005)。
- 0x00 0x01: 读取寄存器个数为1。
-
响应报文:00 00 00 00 00 05 09 03 02 00 05。
- 0x05:表示后续还有5字节。
- 0x09:同查询报文,单元标识符。
- 0x03:功能码,同查询报文。
- 0x02:返回数据字节数。
- 0x00 0x05:寄存器的值。
可见,在Modbus TCP模式下,差错校验字段已不复存在。但在某些特殊场合,例如串行Modbus协议转Modbus TCP的情况下,串行协议数据可以完整地装载到Modbus TCP的数据字段,这时CRC或LRC差错校验字段仍然存在。例如,Modbus RTU Over TCP/IP 或 Modbus ASCII Over TCP/IP等。
5.5 Moubus功能码详解
本节大部分内容参考《Modbus软件开发实战指南》。
5.5.1 功能码概要
Modbus标准在协议中规定了以下3类Modbus功能码。
-
公共功能码:
- 被明确定义的功能码;
- 保证唯一性;
- 由Modbus协会确认,并提供公开的文档;
- 可进行一致性测试;
- 包括协议定义的功能码和保留将来使用的功能码。
-
用户自定义功能码:
- 有两个用户自定义功能码区域,分别是6572和100110;
- 用户自定义,无法保证唯一性。
-
保留功能码:
保留功能码因为历史遗留原因,某些公司的传统产品现行使用的功能码不作为公共使用。
5.5.2 01(0x01)读取线圈
1.功能说明
01功能码用于读取从设备的线圈或离散量输出的状态,即各Do(Discrete Output,离散输出)的ON/OFF状态。消息帧中指定了需要读取的线圈起始地址和线圈数目。需要注意的是,在Modbus协议规定的PDU中,所有线圈或寄存器地址都必须从0开始计算。
2. 查询报文
如下表所示,查询帧的消息中定义了从设备地址为3,并读取从设备的Modbus地址0001900055(线圈地址0002000056)共计37个状态值。起始线圈地址为0x13(即十进制00019),因为线圈地址是从0开始计数的。
功能码01查询报文示例
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x03 | “0”,“3” | 0x03 |
功能码 | 0x01 | “0”,“1” | 0x01 |
起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
起始地址(低位) | 0x13 | “1”,“3” | 0x13 |
寄存器数(高位) | 0x00 | “0”,“0” | 0x00 |
寄存器数(低位) | 0x25 | “2”,“5” | 0x25 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 17 | 8 |
Modbus协议规定,起始地址由2字节构成,取值范围为0x0000 ~ 0xFFFF: 线圈数量由2字节构成,取值范围为0x0001~ 0x07D0 (即+进制1~2000)另外,注意观察ASCII模式和RTU模式的区别,ASCII模式直接按每4位拆分成对应的字符表示。
3. 响应报文
在响应报文的数据字段中,每个线圈占用1位 (bit),状态被表示为1=ON和O=OFF两种类型。第1个数据字节的LSB(最低有效位)标识查询报文中的起始地址线圈的状态值,其他线圈以此类推,一直到这个字节的MSB(最高有效位)为止,并在后续字节中按照同样的方式(由低到高)排列。
例如,下表中线圈20~27的状态值分别是ON - ON - OFF OFF - ON - OFF - ON - OFF表示为二进制则为01010011 (0x53),注意观察对应的顺序。1字节可以表示8个线圈的状态如果最后的数据字节中不能填满8个线圈的状态,则用0填充。对应于查询报文中需要读取37个线圈的状态,共需要5字节保存状态值。
功能码01响应报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x03 | “0”,“3” | 0x03 |
功能码 | 0x01 | “0”,“1” | 0x01 |
数据域字节数 | 0x05 | “0”,“5” | 0x05 |
数据1 | 0x53 | “5”,“3” | 0x53 |
数据2 | 0x6B | “6”,“B” | 0x6B |
数据3 | 0x01 | “0”,“1” | 0x01 |
数据4 | 0xF4 | “F”,“B” | 0xF4 |
数据5 | 0x1B | “1”,“B” | 0x1B |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 21 | 10 |
5.5.3 02 (0x02) 读取离散量输入值
1. 功能说明
02功能码用于读取从设备的离散输入,即DI (Discrete Input) 的ON/OFF状态。消息顿中指定了需要读取的离散输入寄存器的起始地址和数目,可以读取1 ~ 2000个连续的离散量输入状态如果从设备接受主设备的请求则回复功能码02,并返回离散量且输入各变量的当前状态。如果返回的离散输入数量的个数不是8的整数倍,将用0填充最后的数据字节的剩余位。
2. 查询报文
如下表所示,查询顿的消息中定义了从设备的地址为3,并读取从设备的离散输入寄存器中地址10101~10120 (Modbus地址表示为十进制100~119) 共计20个离散输入状态值。从下表中可以发现,起始地址为0x64 (即十进制100),因为消息PDU中的Modbus地址从0开始计数。
功能码02查询报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x03 | “0”,“3” | 0x03 |
功能码 | 0x02 | “0”,“2” | 0x02 |
起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
起始地址(低位) | 0x64 | “6”,“4” | 0x64 |
寄存器数(高位) | 0x00 | “0”,“0” | 0x00 |
寄存器数(低位) | 0x14 | “1”,“4” | 0x14 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 17 | 8 |
与5.4.2节中的功能码 (01 (0x01) 读取线圈/离散量输出状态 (Read Coil status/DOs))一样,本功能码的起始地址由2字节构成,取值范围为0x0000 ~ 0xFFFF;离散量数量由2字节构成,取值范围为0x0001 ~0x07D0 (即十进制1~ 2000),最多一次性可读取2000人离散输入状态值。
3. 响应报文
响应报文的各项构成和意义与5.4.2章节的功能码(01(0x01)读取线圈/离散量输出状态(Re-ad Coil Status/DOs))一样,如下表所示:
功能码02响应报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x03 | “0”,“3” | 0x03 |
功能码 | 0x02 | “0”,“2” | 0x02 |
数据域字节数 | 0x03 | “0”,“3” | 0x03 |
数据1 | 0x53 | “5”,“3” | 0x53 |
数据2 | 0x6B | “6”,“B” | 0x6B |
数据3 | 0x01 | “0”,“1” | 0x01 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 21 | 10 |
5.5.4 03(0x03)读取保持寄存器值
1. 功能说明
03功能码用于读取从设备保持寄存器的内容,不支持广播模式。消息顿中指定了需要读取的保持寄存器的起始地址和数目。而保持寄存器中各地址的具体内容和意义则由设备开发者自行规定。
2. 查询报文
在查询报文中,必须指定保持寄存器的开始地址和需要读取的寄存器数量,例如,如下表所示,从设备地址为7 (0x07),需要读取保持寄存器地址40201 ~ 40203共计3个寄存器的内容即读取Modbus协议地址200~ 202的内容,在报文中表示如下。
起始地址: 0x00C8 (十进制200)。
读取数量: 0x0003 (十进制3)。
功能码03查询报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x07 | “0”,“7” | 0x07 |
功能码 | 0x03 | “0”,“3” | 0x03 |
起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
起始地址(低位) | 0xC8 | “C”,“8” | 0xC8 |
寄存器数(高位) | 0x00 | “0”,“0” | 0x00 |
寄存器数(低位) | 0x03 | “0”,“3” | 0x03 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 17 | 8 |
本功能码的起始地址由2字节构成,取值范围为0x00000xFFFF;寄存器数量由2字节构成取值范围为0x00010x007D (即十进制1~125) ,即最多可以连续读取125个寄存器。
需要特别注意的是,Modbus的保持寄存器和输入寄存器是以字 (Word) 为基本单位的(1Word=2Byte),所以如果读取保持寄存器地址为40001开始的一个16位 (bit) 的无符号数,那么返回2字节,并可以从40002开始读取下一个16位的无符号数。如果需要读取寄存器地址为40001开始的一个32位浮点数,则需要返回4字节,即必须连续读取40001和40002的内容,而且下一个32位的浮点数必须从40003开始读取。对于浮点数(或者32位的整数)而言,连续读取的两个寄存器之间存在字节序和大小端的问题,这一点在开发时必须引起注意。
3. 响应报文
响应报文的各项构成和意义如下表所示。因为Modbus的保持寄存器和输入寄存器是以字为基本单位的,在上面的例子中,查询报文连续读取3个寄存器的内容,将返回6字节,参考表下表中数据1~3的高位和低位。
功能码03响应报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x07 | “0”,“7” | 0x07 |
功能码 | 0x03 | “0”,“3” | 0x03 |
数据域字节数 | 0x06 | “0”,“6” | 0x06 |
数据1(高位) | 0x03 | “0”,“3” | 0x03 |
数据1(低位) | 0x53 | “5”,“3” | 0x53 |
数据2(高位) | 0x01 | “0”,“1” | 0x01 |
数据2(低位) | 0xF3 | “F”,“3” | 0xF3 |
数据3(高位) | 0x01 | “0”,“1” | 0x01 |
数据3(低位) | 0x05 | “0”,“5” | 0x05 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 23 | 11 |
5.5.5 04 (0x04)读取输入寄存器值
1.功能说明
与功能码03类似,04功能码用于读取从设备输入寄存器的内容,不支持广播模式。消息顿中指定了需要读取的输入寄存器的起始地址和数目,而输入寄存器中各地址的具体内容和意义则由设备开发者自行规定。
2.查询报文
在查询报文中必须指定输入寄存器的起始地址和需要读取的寄存器数量。例如,如表4-9所示,从设备地址为7 (0x07),需要读取输入寄存器地址30301 ~ 30303共计3个寄存器的内容即读取Modbus协议地址300 ~302的内容,在报文中表示如下。
起始地址: 0x012C (十进制300) 。
读取数量: 0x0003 (十进制3)。
功能码04查询报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x07 | “0”,“7” | 0x07 |
功能码 | 0x04 | “0”,“4” | 0x04 |
起始地址(高位) | 0x01 | “0”,“1” | 0x01 |
起始地址(低位) | 0x2C | “2”,“C” | 0x2C |
寄存器数(高位) | 0x00 | “0”,“0” | 0x00 |
寄存器数(低位) | 0x03 | “0”,“3” | 0x03 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 17 | 8 |
本功能码中,起始地址由2字节构成,取值范围为0x0000 ~ 0xFFFF;寄存器数量由2字节构成,取值范围为0x0001 ~0x007D (即十进制1~125) ,即最多可以连续读取125个寄存器同样有一点需要注意,Modbus的保持寄存器和输入寄存器是以字为基本单位的。所以对于浮点数(或者32位的整数)而言,连续读取的两个寄存器之间存在字节序和大小端的问题,这一点在开发时必须引起注意。
3.响应报文
响应报文的各项构成和意义如下表所示。因为Modbus的保持寄存器和输入寄存器是以字为基本单位的,上面的例子中,查询报文连续读取3个寄存器的内容,那么将返回6字节,参考下表中数据1~3的高位和低位。
功能码04响应报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x07 | “0”,“7” | 0x07 |
功能码 | 0x04 | “0”,“4” | 0x04 |
数据域字节数 | 0x06 | “0”,“6” | 0x06 |
数据1(高位) | 0x03 | “0”,“3” | 0x03 |
数据1(低位) | 0x53 | “5”,“3” | 0x53 |
数据2(高位) | 0x01 | “0”,“1” | 0x01 |
数据2(低位) | 0xF3 | “F”,“3” | 0xF3 |
数据3(高位) | 0x01 | “0”,“1” | 0x01 |
数据3(低位) | 0x05 | “0”,“5” | 0x05 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 23 | 11 |
5.5.6 05 (0x05)写单个线圈
1. 功能说明
05功能码用于将单个线圈寄存器 (或离散输入)设置为ON或OFF,该功能码支持广播模式在广播模式下,所有从站设备的同一地址的值将被统一修改。查询报文中的ON/OFF状态由报文数据字段的常数指定,0xFF00表示ON状态,0x0000表示OFF状态。其他值均是非法的,并且对寄存器不起作用,将会返回异常响应。
2. 查询报文
查询报文中需要指定从设备地址以及需要变更的线圈地址和设定的状态值。需要注意的是,在查询报文中,线圈地址从地址0开始计数。例如,如下表所示,从设备地址为3,设置线圈地址00150为ON状态,则查询报文中的线圈地址设置为0x95 (149)。
功能码05查询报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x03 | “0”,“3” | 0x03 |
功能码 | 0x05 | “0”,“5” | 0x05 |
起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
起始地址(低位) | 0x95 | “9”,“5” | 0x95 |
变更数据(高位) | 0xFF | “F”,“F” | 0xFF |
变更数据(低位) | 0x00 | “0”,“0” | 0x00 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 17 | 8 |
本功能码中,起始地址由2字节构成,取值范围为0x0000~0xFFFF:变更目标数据由2字节构成,取值只能为0xFF00或0x0000。
3.响应报文
响应报文的各项构成和意义如下表所示。对于从设备,在线圈或离散输出寄存器正常变更的情况下会返回与查询报文相同的响应报文。如果修改失败,则会返回一个异常响应,对于异常响应,后续章节会进一步详细介绍。
功能码05响应报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x03 | “0”,“3” | 0x03 |
功能码 | 0x05 | “0”,“5” | 0x05 |
起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
起始地址(低位) | 0x95 | “9”,“5” | 0x95 |
变更数据(高位) | 0xFF | “F”,“F” | 0xFF |
变更数据(低位) | 0x00 | “0”,“0” | 0x00 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 17 | 8 |
5.5.7 06 (0x06) 写单个保持寄存器
1.功能说明
06功能码用于更新从设备的单个保持寄存器的值,该功能码支持广播模式,在广播模式下所有从设备的同一地址的值将被统一修改。
2. 查询报文
查询报文中需要指定从设备地址以及需要变更的保持寄存器地址和设定的值。需要注意的是查询报文中,寄存器地址从地址0开始计数。例如,如下表所示,从设备地址为3,设置寄存器地址40150为1200 (即0x04B0) ,则查询报文中的地址字段设置为0x95 (149)。
功能码06查询报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x03 | “0”,“3” | 0x03 |
功能码 | 0x06 | “0”,“6” | 0x06 |
起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
起始地址(低位) | 0x95 | “9”,“5” | 0x95 |
变更数据(高位) | 0x04 | “0”,“4” | 0x04 |
变更数据(低位) | 0xB0 | “B”,“0” | 0xB0 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 17 | 8 |
本功能码中,起始地址由2字节构成,取值范围为0x0000~0xFFFF; 变更目标数据由2字节构成,取值范围为0x0000~0xFFFF。
3.响应报文
响应报文的各项构成和意义,如下表所示。对于从设备,在保持寄存器正常变更的情况下会返回与查询报文相同的响应报文。如果修改失败,则返回个异常响应。
功能码06响应报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x03 | “0”,“3” | 0x03 |
功能码 | 0x06 | “0”,“6” | 0x06 |
起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
起始地址(低位) | 0x95 | “9”,“5” | 0x95 |
变更数据(高位) | 0x04 | “0”,“4” | 0x04 |
变更数据(低位) | 0xB0 | “B”,“0” | 0xB0 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 17 | 8 |
5.5.8 08 (0x08) 诊断功能
1.功能说明
08功能码仅用于串行链路,主要用于检测主设备和从设备之间的通信故障,或检测从设备的各种内部故障,该功能码不支持广播。为了区别各诊断类型,查询报文中提供了2字节的子功能码字段。
通常在正常的响应报文中,从设备将原样回复功能码和子功能码.
2. 查询报文
查询报文中需要指定从设备地址、功能码以及子功能码。
例如,下表中标识了子功能码“原样返回查询数据”的诊断功能,其中子功能码为0(0x0000)。在子功能码为0x0000的情况下,数据字段可以为任意值。各子功能码的详细意义可参考下表。
功能码08查询报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x05 | “0”,“5” | 0x05 |
功能码 | 0x08 | “0”,“8” | 0x08 |
子功能码(高位) | 0x00 | “0”,“0” | 0x00 |
子功能码(低位) | 0x00 | “0”,“0” | 0x00 |
数据(高位) | 0x04 | “0”,“4” | 0x04 |
数据(低位) | 0xB0 | “B”,“0” | 0xB0 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 17 | 8 |
本功能码中,子功能码由2字节构成,取值则根据意义而不同;数据字段由2字节构成,其取值由子功能码确定。
3.响应报文
响应报文的各项构成和意义如下表所示。对于从设备,在保持寄存器正常变更的情况下会返回与查询报文相同的响应报文。如果修改失败,则返回一个异常响应。
功能码08响应报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x05 | “0”,“5” | 0x05 |
功能码 | 0x08 | “0”,“8” | 0x08 |
子功能码(高位) | 0x00 | “0”,“0” | 0x00 |
子功能码(低位) | 0x00 | “0”,“0” | 0x00 |
数据(高位) | 0x04 | “0”,“4” | 0x04 |
数据(低位) | 0xB0 | “B”,“0” | 0xB0 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 17 | 8 |
4. 诊断子功能码
各常用的诊断子功能码的定义如下。
Return Query Data(00)
诊断内容 | 原样返回查询报文 |
---|---|
子功能码 | 0x00,0x00 |
查询报文数据字段 | 任意16位数据 |
响应报文数据字段 | 同查询报文 |
Restart Communication Option(01)
诊断内容 | 重启通信选项; 用于初始化并重新启动从站设备,清楚所有通信事件计数器; 如果端口处于 Listen Only Mode,则不返回响应;否则在重启之前返回响应 |
---|---|
子功能码 | 0x00,0x01 |
查询报文数据字段 | 0x00, 0x00 保持事件记录; 0xFF,0x00 清除事件记录 |
响应报文数据字段 | 同查询报文 |
Return Diagnostics Register(02)
诊断内容 | 返回诊断寄存器 |
---|---|
子功能码 | 0x00,0x04 |
查询报文数据字段 | 0x00,0x00 |
响应报文数据字段 | 诊断寄存器的内容 |
Force Listen Only Mode
诊断内容 | 强制只听模式; 强制被寻址的从站设备进入只听模式,使得此设备与网络中的其他设备断开,不返回响应 |
---|---|
子功能码 | 0x00,0x04 |
查询报文数据字段 | 0x00,0x00 |
响应报文数据字段 | 不返回响应 |
Clear Counters and Diagnostic Register
诊断内容 | 清除计数器和诊断寄存器 |
---|---|
子功能码 | 0x00,0x0A |
查询报文数据字段 | 0x00,0x00 |
响应报文数据字段 | 同查询报文 |
Return Bus Message Count(11,0x0B)
诊断内容 | 返回总线报文计数 |
---|---|
子功能码 | 0x00,0x0B |
查询报文数据字段 | 0x00,0x00 |
响应报文数据字段 | 返回报文的技数值 |
Return Bus Communication Error Count(12,0x0C)
诊断内容 | 返回总线通信CRC差错计数 |
---|---|
子功能码 | 0x00,0x0C |
查询报文数据字段 | 0x00,0x00 |
响应报文数据字段 | 返回报文的CRC出错总数 |
Return Bus Exception Error Count(13,0x0D)
诊断内容 | 返回总线异常差错计数 |
---|---|
子功能码 | 0x00,0x0D |
查询报文数据字段 | 0x00,0x00 |
响应报文数据字段 | 返回异常响应的总数 |
Return Slave Message Count(14,0x0E)
诊断内容 | 返回从站设备报文总数 |
---|---|
子功能码 | 0x00,0x0E |
查询报文数据字段 | 0x00,0x00 |
响应报文数据字段 | 返回从站设备接收报文总数 |
Return Slave No Response Counrt(15,0x0F)
诊断内容 | 返回从站设备无响应计数 |
---|---|
子功能码 | 0x00,0x0F |
查询报文数据字段 | 0x00,0x00 |
响应报文数据字段 | 返回加电后没有返回响应的报文的保文数量 |
Return Slave Busy Count(17,0x11)
诊断内容 | 返回从站设备忙计数 |
---|---|
子功能码 | 0x00,0x11 |
查询报文数据字段 | 0x00,0x00 |
响应报文数据字段 | 返回加电后异常响应忙的报文数量 |
Return Bus Character Overrun Count(18,0x12)
诊断内容 | 返回总线字符超限计数 |
---|---|
子功能码 | 0x00,0x12 |
查询报文数据字段 | 0x00,0x00 |
响应报文数据字段 | 返回超限的报文数量 |
5.5.9 11 (0x0B) 获取通信事件计数器
1. 功能说明
11功能码主要用于获取从设备通信计数器中的状态字和事件计数的值,本功能码不支持广播模式。通过在通信报文之前和之后读取通信事件计数值,可以确定从设备是否正常处理报文。
对于正常完成报文处理和传输的场合,事件计数器增加1;而对于异常响应、轮询命令或读事件计数器(即Ox0B功能码)的场合,则计数器不变。通过【0x08诊断功能】 中的子功能码【Restart Communication Option ( 0x0001 )】 和 【Clear Counters and Diagnostic Register (0x000A) 】 可以复位事件寄存器。
2. 查询报文
下表中的示例表示获取通信事件计数器的查询报文内容,其中从站设备地址为5.
功能码11查询报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x05 | “0”,“5” | 0x05 |
功能码 | 0x0B | “0”,“B” | 0x0B |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 9 | 4 |
3. 响应报文
对于从设备,在正常情况下,响应报文返回2字节的状态字和2字节的事件计数。其中,如果从站设备处于忙状态,那么状态字为0xFFFF,否则状态字为0x0000。在表4-18的示例中,状态字为0x0000,表示从站设备外于空闲状态。事件计数的值为0x03E8,表示记录了1000 (0x03E8)个事件。
功能码11响应报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x05 | “0”,“5” | 0x05 |
功能码 | 0x0B | “0”,“B” | 0x0B |
子功能码(高位) | 0x00 | “0”,“0” | 0x00 |
子功能码(低位) | 0x00 | “0”,“0” | 0x00 |
数据(高位) | 0x03 | “0”,“3” | 0x03 |
数据(低位) | 0xE8 | “E”,“8” | 0xB8 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 17 | 8 |
5.5.10 12 (0x0C) 获取通信事件记录
1. 功能说明
12功能码主要用于从从设备获取状态字、事件计数、报文计数以及事件字节字段。其中,状态字和事件计数与功能码11 (0x0B) 获取的值一致。报文计数包含加电重启、清除计数器之后的报文数量,报文计数与通过诊断功能码08 (0x08)、子功能码11 (0x0B) 获取的值一致。事件字节字段包含0~64字节,用来定义各种事件。
2. 查询报文
下表中的示例表示获取通信事件记录的查询报文内容,其中从站地址位5。
功能码12查询报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x05 | “0”,“5” | 0x05 |
功能码 | 0x0C | “0”,“C” | 0x0C |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 9 | 4 |
3. 响应报文
对于从站设备,在正常情况下,响应报文包括一个2字节的状态字字段、一个2字节的事件计数字段、一个2字节的消息计数字段以及0~ 64字节的事件字段。因为事件字段是变长的,所以增加了一个1字节的数据长度字段,以方便读取响应数据,如下表所示;
功能码12响应报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x05 | “0”,“5” | 0x05 |
功能码 | 0x0C | “0”,“C” | 0x0C |
字节数 | 0x08 | “0”,“8” | 0x08 |
状态字(高位) | 0x00 | “0”,“0” | 0x00 |
状态字(低位) | 0x00 | “0”,“0” | 0x00 |
事件计数(高位) | 0x03 | “0”,“3” | 0x03 |
事件计数(低位) | 0xE8 | “E”,“8” | 0xE8 |
消息计数(高位) | 0x01 | “0”,“1” | 0x01 |
消息计数(低位) | 0xF6 | ”F“,”6“ | 0xF6 |
事件0 | 0x20 | ”2“,”0“ | 0x20 |
事件1 | 0x00 | ”0“,”0“ | 0x00 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 17 | 8 |
5.5.11 15(0x0F)写多个线圈
1.功能说明
15功能码用于将连续的多个线圈或离散输出设置为ON/OFF状态,支持广播模式,在广播模式下,所有从站设备的同一地址的值将被统一修改。15功能码中,起始地址字段由2字节构成,取值范围为0x00000xFFFF:而寄存器数量字段由2字节构成,取值范用为0x00010x07B0.
2. 查询报文
查询报文中包含请求数据字段,用于定义ON或OFF状态。数据字段中为逻辑1的位对应ON;逻辑0的位对应OFF。其中,ON/OFF与数据字段的对应关系可参考前面的童节“01 (0x01) 读取线圈,离散量输出状态 (Read Coil status/DOs)” 中的内容举例说明,假设从站设备地址为5,需要设置线圈地址20 ~30的状态如下表所示。
线圈状态:
值 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
线圈 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | — | — | — | — | — | 30 | 29 | 28 |
那么,写入的数据字段被划分为2字节,值分别为0xD1,对应于27 ~ 20的线圈,值0x05对应于30~28的线圈,注意细体会其中的高低位的对应关系。需要注意的是,在查询报文中,Modbus协议的起始地址为19 (0x13) ,即比线圈起始地址20少1。如下表所示,其中字节数字段表示需要变更数据的字节总数。
功能码15查询报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x05 | “0”,“5” | 0x05 |
功能码 | 0x0F | “0”,“F” | 0x0F |
起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
起始地址(低位) | 0x13 | “1”,“3” | 0x13 |
寄存器数(高位) | 0x00 | “0”,“0” | 0x00 |
寄存器数(低位) | 0x0B | “0”,“B” | 0x08 |
字节数 | 0x02 | “0”,“2” | 0x02 |
变更数据(高位) | 0xD1 | ”D“,”1“ | 0xD1 |
变更数据(低位) | 0x05 | ”0“,”5“ | 0x05 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 23 | 11 |
3. 响应报文
对于从设备,在正常情况下,响应报文包括功能码、起始地址以及写入的线圈数量,如下表所示。
功能码15响应报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x05 | “0”,“5” | 0x05 |
功能码 | 0x0F | “0”,“F” | 0x0F |
起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
起始地址(低位) | 0x13 | “1”,“3” | 0x13 |
寄存器数(高位) | 0x00 | “0”,“0” | 0x00 |
寄存器数(低位) | 0x0B | “0”,“B” | 0x08 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 17 | 8 |
5.5.12 16 (0x10) 写多个保持寄存器
1. 功能说明
16功能码用于设置或写入从设备保持寄存器的多个连续的地址块 (1 ~ 123个寄存器),支持广播模式,在广播模式下,所有从站设备的同一地址的值将被统一修改。本功能码中,起始地址字段由2字节构成,取值范围为0x0000 ~ 0xFFFF;而寄存器数量字段由2字节构成,取值范围为0x0001~0x007B。
2. 查询报文
查询报文包含请求数据字段。数据字段保存需要写入的数值,各数据按每个寄存器2字节存放。举例说明,从站设备地址为5,需要将保持寄存器地址40020 ~ 40022设置为如下表所示的数值。
寄存器的设置:
寄存器地址 | 设定值 | 寄存器地址 | 设定值 |
---|---|---|---|
40020 | 0x0155 | 40022 | 0x0157 |
40021 | 0x0156 |
对应于40020~40022的寄存器,注意仔细体会其中的高低位的对应关系。需要注意的是,在查询报文中,Modbus协议的起始地址为19 (0x13),即比寄存器起始地址20少1。如下表所示,其中字节数字段表示需要变更数据的字节总数。
功能码16查询报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x05 | “0”,“5” | 0x05 |
功能码 | 0x10 | “0”,“F” | 0x0F |
起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
起始地址(低位) | 0x13 | “1”,“3” | 0x13 |
寄存器数(高位) | 0x00 | “0”,“0” | 0x00 |
寄存器数(低位) | 0x03 | “0”,“B” | 0x08 |
字节数 | 0x06 | ”0“,”6“ | 0x06 |
变更数据1(高位) | 0x01 | ”0“,”1“ | 0x01 |
变更数据1(低位) | 0x55 | ”5“,”5“ | 0x56 |
变更数据2(高位) | 0x01 | ”0“,”1“ | 0x01 |
变更数据2(低位) | 0x56 | ”5“,”6“ | 0x56 |
变更数据3(高位) | 0x01 | ”0“,”1“ | 0x01 |
变更数据3(低位) | 0x57 | ”5“,”7“ | 0x57 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 31 | 15 |
3. 响应报文
对于从设备,在正常情况下,响应报文包括功能码、起始地址及写入的寄存器数量,如下表所示。
功能码16响应报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x05 | “0”,“5” | 0x05 |
功能码 | 0x10 | “1”,“0” | 0x10 |
起始地址(高位) | 0x00 | “0”,“0” | 0x00 |
起始地址(低位) | 0x13 | “1”,“3” | 0x13 |
寄存器数(高位) | 0x00 | “0”,“0” | 0x00 |
寄存器数(低位) | 0x03 | “0”,“3” | 0x03 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 17 | 8 |
在实际开发过程中,功能码“16 (0x10) 写多个寄存器 (Preset Multiple Registers) ”通常用于方便用户写入多字节类型的数据。
例如,假设从站设备地址为5,需要向保持寄存器写入一个32位 (4字节) 的浮点数,那么此浮点数将占用2个寄存器地址。假设浮点数将存放在40001和40002寄存器中,设定值为1.235(即0x3F9E 147A)实际的查询和响应报文如下(其中标记部分为设定的浮点数值,假设字节序为AB-CD,参考第5.3.7章字节序和大小端的内容)。
查询报文: 05 10 00 00 00 02 04 3F 9E 14 7A 05 86
响应报文: 05 10 00 00 00 02 40 4C
对于64位(8字节)的双精度浮点数,同理将占用4个寄存器地址共8字节的空间。特别需要注意的是字节序及大小端的问题,前面讨论过多字节存在大小端问题,因此主站设备和从站设备必须保持一致的规则处理,约定Modbus传输中的数据字段的字节序,否则会因为大小端不一致而产生数据处理错误。
5.5.13 17 (0x11) 报告从站ID (仅用于串行链路)
1. 功能说明
17功能码用于读取从站设备的ID、类型描述、当前状态以及其他信息,不支持广播模式。响应消息的构成依赖于设备而不尽相同。
2. 查询报文
查询报文中不包含请求数据字段。举例说明,从站设备地址为5,获取相关信息,如下表所示。
功能码17查询报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x05 | “0”,“5” | 0x05 |
功能码 | 0x11 | “1”,“1” | 0x11 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 9 | 4 |
3. 响应报文
对于从设备,在正常情况下,响应报文包括从站ID、运行状态以及其他附加信息,如下表所示。运行状态字段占用1字节,且0x00=OFF,0xFF=ON,而响应报文的组成则由开发者决定。
功能码17响应报文示例:
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x05 | “0”,“5” | 0x05 |
功能码 | 0x11 | “1”,“1” | 0x11 |
字节数 | 设备相关 | 设备相关 | 设备相关 |
从设备ID | 设备相关 | 设备相关 | 设备相关 |
运行状态 | 0xFF | “F”,“F” | 0xFF |
附加情报1 | 设备相关 | 设备相关 | 设备相关 |
…… | 设备相关 | 设备相关 | 设备相关 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 17 | 8 |
5.5.14 Modbus异常响应
以上介绍了一些常见的公共功能码的报文 (消息顿)构成,广播模式以外的查询报文都希望能够获取一个正常的响应报文。在通常情况下,从站设备将返回一个正常响应报文,但是在某些特殊情况下将返回异常响应报文。
对于查询报文,存在以下4种处理反馈:
- 正常接收,正常处理,返回正常响应报文;
- 因为通信错误等原因造成从站设备没有接收到查询报文,主站设备将按超时处理:从站设备接收到的查询报文存在通信错误 (如LRC、CRC错误等),此时从站设备将丢弃报文不响应,主站设备将按超时处理;
- 从站设备接收到正确的报文,但是超过处理范围(如不存在的功能码或者寄存器等),此时从站设备将返回包含异常码 (Exception Code) 的响应报文。
- 异常响应报文由从站地址、功能码以及异常码构成。其中,功能码与正常响应报文不同,在异常响应报文中,功能码最高位 (即MSB) 被设置为1。因为Modbus协议中的功能码占用1字节故用表达式描述为:
异常功能码=正常功能码+0x80
举例说明,如下表所示,查询报文的起始地址为0x012C (十进制300) ,即需要读取寄存器地址为30301开始的值。若从站设备中不存在输入寄存器30301,则从站设备将返回一个异常响应报文,参见下表的功能码和异常码。
异常响应示例(功能码04查询报文 ):
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x07 | “0”,“7” | 0x07 |
功能码 | 0x04 | “0”,“4” | 0x04 |
起始地址(高位) | 0x01 | “0”,“1” | 0x01 |
起始地址(低位) | 0x2C | “2”,“C” | 0x2C |
寄存器数(高位) | 0x00 | “0”,“0” | 0x00 |
寄存器数(低位) | 0x03 | “0”,“3” | 0x03 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 17 | 8 |
异常响应示例(功能码04响应报文 ):
字段 | 例(Hex) | ASCII模式字符型 | RTU模式8位(Hex) |
---|---|---|---|
帧头 | “:” | ||
从设备地址 | 0x07 | “0”,“7” | 0x07 |
功能码 | 0x84 | “8”,“4” | 0x84 |
异常码 | 0x02 | “0”,“2” | 0x02 |
差错校验 | LRC(2字符) | CRC(2字节) | |
帧尾 | CR/LF | ||
合计字节数 | 11 | 5 |
常见的异常码如下表所示:
常见异常码说明:
异常码 | 名称 | 说明 |
---|---|---|
01 | 非法功能码 | 从站设备不支持此功能码 |
02 | 非法数据地址 | 指定的数据地址在从站设备中不存在 |
03 | 非法数据值 | 指定的数据超过范围或者不允许使用 |
04 | 从站设备故障 | 从站设备处理响应的过程中出现未知错误等 |