概述
第一章 初识Modbus
1.1什么是Modbus
简而言之,Modbus通信协议是工业领域通信协议的业界标准,并且是当前工业电子设备之间相当常用的连接方式之一。
Modbus 协议是应用于电子控制器上的一种通用语言。通过此协议,可以实现控制器相互之间、控制器经由网络和其他设备之间的通信。它已经成为一种通用的工业标准,有了它,不同厂商生产的控制设备可以连接成工业网络,进行集中监控。此协议定义了一个控制器能够认识使用的消息结构,而不管它们是经过何种网络进行通信的;而且描述了控制器请求访问其他设备的过程,如何应答来 自 其他设备的请求,以及怎样侦测错误并记录;并制定了统一的消息域的结构和内容。
当在 Modbus 网络上通信时,此协议决定了每个控制器必须要知道它们的设备地址,识别按地址发来的消息,决定要产生何种行为。如果需要回应,控制器将生成反馈信息并通过 Modbus 协议发送。
Modbus 通信协议具有以下几个特点∶
● Modbus 协议标准开放、公开发表并且无版税要求。用户可以免费获取并使用 Modbus 协议,不需要交纳许可证费,也不会侵犯知识产权。
●Modbus 协议可以支持多种电气接口,如 RS232、RS485、TCP/IP 等;还可以在各种介质上传输,如双绞线、光纤、红外、无线等。
● Modbus 协议消息帧格式简单、紧凑、通俗易懂。用户理解和使用简单,厂商容易开发和集成,方便形成工业控制网络。
1.2模型
Modbus 是 OSI 模型第 7 层之上的应用层报文传输协议,它在不同类型总线或网络设备之间提供主站设备/从站设备(或客户机/服务器)通信。自从 1979 年发布并成为工业串行链路通信的事实标准以来,Modbus 使成千上万的自动化设备能够通信。目前,为了继续增加对简单而优雅的 Modbus 通信协议的支持,国际互联网组织规定并保留 了 TCP/IP 协议栈上的系统 502 端口,专门用于访问 Modbus 设备。Modbus 协议栈模型如图所示。
1.3 协议版本
Modbus 通信协议目前存在用于串行链路、TCP/IP 以太网以及其他支持互联网协议的网络版本。大多数 Modbus 设备通信通过串口(RS232/RS485)或 TCP/IP物理层进行连接。
对于 Modbus 串行链路连接,存在两个变种,它们在协议细节上略有不同,主要区别是传输数据的字节表示上的不同。这两个变种包括 RTU 模式和 ASCII 模式。ModbusRTU 模式是一种紧凑的,采用二进制表示数据的方式;而 ModbusASCII 模式是一种人类可读的、冗长的表示方式。这两个变种都使用串行链路通信(Serial Communication)方式,为了确保数据传输的完整性和准确性,RTU 模式下消息格式命令和数据带有循环冗余校验的校验和,而 ASCII 模式下消息格式采用纵向冗余校验的校验和,而且被配置为 RTU 模式的节点不能与配置为 ASCII模式的节点通信,反之亦然。
对于通过 TCP/IP(例如以太 网)物理层的连接,存在多个 Modbus/TCP 变种,这种方式不需要校验和的计算。
对于以上这 3 种通信模式,在数据模型和功能调用上都是相同的,只有传输报文封装方式是不同的。
1.4 通信设备
通常情况下,Modbus 协议是一个主/从(Master/Slave)或客户 端/服务器(Client/Server)架构的协议。通信网络中有一个节点是 Master 节点;其他使用 Modbus 协议参与通信的节点是 Slave 节点,每一个 Slave 设备都有一个唯一的地址。在串行和 MB十网络中,只有被指定为主节点的节点可以启动一个命令(在以太网上,任何一个设备都能发送一个 Modbus 命令,但是通常也只有一个主节点设备用以引导指令)。
一个 Modbus 命令包含了准备执行指令的设备的 Modbus 地址。线路上所有设备都会收到命令,但只有指定地址的设备会执行并回应指令(地址 0 例外,指定地址 0的指令是广播指令,所有收到指令的设备都会运行,不过不用回应指令)。所有 Modbus 传输报文都包含了错误校验码,以确定到达的命令是否完整。例如,基本的 Modbus 命令能指示一个 Modbus RTU设备改变它的寄存器的某个值,控制或者读取一个I/O 端口,以及指挥设备回送一个或者多个其寄存器中的数据。
1.5帧结构
Modbus 是一个请求/应答协议,并且提供统一的功能码用于数据传输服务。 Modbus 功能码是 Modbus 请求/应答 PDU(即 PROTOCOL DATA UNIT,协议数据单元)的元素之一,所谓的 PDU 是 Modbus 协议定义的一个与基础通信层无关的简单协议数据单元。而在特定总线或网络上,Modbus 协议则通过ADU(即APPLICATION DATA UNIT,应用数据单元)引入一些附加域,以实现完整而准确的数据传输。
为了寻求一种简洁的通信格式,Modbus 协议定义了 PDU 模型,即功能码+数据的格式;而为了适应多种传输模式,在 PDU 的基础上增加了必要的前缀(如地址域)和后缀(如差错校验),形成了 ADU模型。
ADU与PDU 之间的关系如图所示。
1.6 事务处理
主机设备(或客户端)创建 Modbus 应用数据单元形成查询报文,其中功能码标识了向从机设备(或服务器端)指示将执行哪种操作。功能码占用一个字节,有效的码字范围是十进制 1~255(其中 128~255 为异常响应保留)。香询报文创建完毕,主机设备(或客户端)向从机设备(或服务器端)发送报文,从机设备(或服务器端)接收报文后,根据功能码做出相应的动作,并将响应报文返回给主机设备(或客户端),如图 所示。
如果在一个正确接收的 Modbus ADU中,不出现与请求 Modbus 功能有关的差错》那么从机设备(或服务器端)将返回正常的响应报文。如果出现与请求 Modbus 功能有关的差错,那么响应报文的功能码域将包括一个异常码,主机设备(或客户端)能够根据异常码确定下一个执行的操作。
如图所示,对于异常响应,从机设备(或服务器端)将返回一个与原始功能码等同的码值,但设置该原始功能码的最高有效位为逻辑 1,用干通知主机设备(或客户端)。
第二章 Modbus开发环境
2.1 VSPD
2.2 Modbus Poll
2.3 Modbus Slave
第三章 Modbus 协议相关
3.1 协议概要
简而言之,Modbus 协议是一种单主/多从的通信协议,其特点是在同一时间,总线上只能有—个主设备,但可以有一个或者多个(最多 247 个)从设备。Modbus通信总是由主设备发起,当从设备没有收到来自主设备的请求时,不会主动发送数据。从设备之间不能相互通信,主设备同时只能启动一个 Modbus 访问事务处理。
主设备可以采用两种方式向从设备发送 Modbus 请求报文,即主设备可以对指定的单个从设备或者线路上所有的从设备发送请求报文,而从设备 只能被动接收请求报文后给出响应报文即应答。这两种模式分别如下;
● 单播模式∶ 主设备仅仅寻址单个从设备。从设备接收并处理完请求之后,向主设备返回一个响应报文,即应答。在这种模式下,一个 Modbus 事务处理包含两个报文∶一个是主设备的请求报文,一个是从设备的响应报文。每个从设备必须有唯一的地址(地址范围 1~247),这样才能区别于其他从设备从而可以独立被寻址,而主设备不占用地址。
● 广播模式∶此种模式下,主设备可以向所有的从设备发送请求指令。而从设备在接收到广播指令后,仅仅进行相关指令的事务处理而不要求返回应答。
基于此,广播模式下,请求指令必须是 Modbus 标准功能中的写指令。
根据 Modbus 标准协议的要求,所有从设备必须接收广播模式下的写指令,且地址 0被保留用来识别广播通信。
1.请求
主设备发送的请求报文主要包括从设备地址(或者广播地址 0)、功能码、传输的数据以及差错检测字段。
查询消息中的功能码告之被选中的从设备要执行何种功能。数据段包含 了从设备要执行功能的任何附加信息。 例如功能代码 03 要求从设备读保持寄存器
并返回其内容。
数据段必须包含要告之从设备的信息∶ 从何寄存器开始读取及要读取的寄存器数量。差错检测域为从设备提供了—种验证消息内容是否正确的方法。
2.应答
从设备的应答报文包括地址、功能码、差错检测域等。
如果从设备产生一个正常的回应,则在回应消息中的功能代码是在查询消息中的功能代码的回应。数据段包括了从设备收集的数据,如寄存器值或状态。
如果有错误发生,功能代码将被修改以用于指出 回应消息是错误的,同时数据段包含了描述此错误信息的代码。差错检测域允许主设备确认消息内容是否可用。
对于串行链路来说,又存在两种传输帧模式∶ ASCII(American Standard Code
for Information Interchange,美 国标 准 信 息 交换码)模式 和 RTU(Remote Terminal Unit)模式。但是,对于同一网络或链路来说,所有的设备必须保持统一,要么统一为 ASCII模式,要么统一为 RTU 模式,不可共存。相对来说,RTU模式传输效率更高,因此,在当前普遍的生产环境中 RTU 模式获得了广泛应用,而 ASCII 模式只作为特殊情况下的可选项。
3.2寄存器
基本表 | 对象类型 | 访问类型 | 注释 |
---|---|---|---|
离散量输入 | 单个位 | 只读 | I/O系统可提供这种类型数据 |
线圈 | 单个位 | 读写 | 通过应用程序而可以改变这种类型数据 |
输入寄存器 | 16位字 | 只读 | I/O系统可提供这种类型数据 |
保持寄存器 | 16位字 | 读写 | 通过应用程序而可以改变这种类型数据 |
3.3串行消息帧格式
3.3.1 ASCII消息帧格式
当控制器设为在 Modbus 网络上以 ASCII 模式通信时,在消息中每个 8 位(bit)的字节都将作为两个 ASCII 字符发送。这种方式的主要优点是字符发送的
时间间隔可达到 1秒而不产生错误。
在 ASCII模式下,消息以冒号(∶)字符(ASCII 码 0x3A)开始,以回车换行符结束(ASCII 码 0x0D,0x0A)。消息帧的其他字段(域)可以使用的传输字符是十六进制的 0…9,A…F。处于网络上的 Modbus 设备不断侦测"∶"字符,当有一个冒号接收到时,每个设备进入解码阶段,并解码下一个字段(地址域)来判断是否是发给自 已的。消息帧中的字符间发送的时间间隔最长不能超过 1 秒,否则接收的设备将认为发生传输错误。
一个典型的 ASCII 消息帧格式如表所示。
3.3.2 RTU消息帧格式
传输设备(主/从设备)将 Modbus 报文放置在带有已知起始和结束点的消息帧中,这就要求接收消息帧的设备在报文的起始处开始接收,并且要知道报文传
输何时结束。另外还必须能够检测到不完整的报文,且能够清晰地设置 错误标志。
在 RTU 模式中,消息的发送和接收以至少 3.5 个字符时间的停顿间隔为标志。实际使用中,网络设备不断侦测网络总线,计算字符间的停顿间隔时间,判断
消息帧的起始点。 当接收到第—个域(地址域)时,每个设备都进行解码以判断是否是发给自己的。在最后一个传输字符结束之后,一个至少 3.5个字符时间的停
顿标定了消息的结束,而一个新的消息可在此停顿后开始。另外,在一帧报文中,必须以连续的字符流发送整个报文帧。如果两个字符之间的空闲间隔大于 1.5个字符时间,那么认为报文帧不完整,该报文将被丢弃。
需要记住的是∶
● 3.5 时间间隔目的是作为区别前后两帧数据的分隔符。
● 3.5 时间间隔只对 RTU模式有效。
如图 3-2 所示,Modbus 通信时规定主机发送完一组命令必须间隔 3.5个字符再发送下一组新命令,这 3.5 个字符主要用来告诉其他设备这次命令(数据)已结束。这 3.5个字符的时间间隔采用以下方式计算∶
通常情况下在串行通信中,1 个字符包括1 位起始位、8 位数据位、1 位校验位(或者没有)、1 位停止位(一般情况下)。这样一般情况下 1 个字符就包括 11 位,那么 3.5个字符就是3.5×11=38.5 位。
而串行通信中波特率的含义是每秒传输的二进制位的个数。例如波特率为9600bps,则意思就是说每 1s(也就是 1000ms)传输 9600 个位的数据;反过来说传输 9600 个二进制位的数据需要 1000ms,那么传输 38.5 个二进制位的数据需要的时间就是∶
38.5×(1000/9600)=4.0104167ms
如图 3-2 所示,Modbus RTU 要求相邻两帧数据的起始和结束之间至少有大于等于3.5 个字符的时间间隔,那么在波特率为 9600bps 的情况下,只要大于 4.0104167ms 即可!
3.3.3 地址域
所谓地址域,指的是 Modbus 通信帧中的地址字段,其内容为从设备地址。 Modbus 消息帧的地址域包含 2 个字符(ASCII模式)或者1个字节(RTU 模式)。
消息帧中可能的从设备地址是 0~247(十进制),单个设备的实际地址范围是1~247(参见表 3-4)。主设备通过将要联络的从设备的地址放入消息中的地址域来选通从设备。当从设备发送回应消息时,它把自己已的地址放入回应的地址域中,以便主设备知道是哪一个设备做出回应。
地址 0用作广播地址,以使所有的从设备都能认识。当 Modbus 协议用于更高级别的网络时,广播方式可能不被允许或以其他方式代替。
3.3.4 功能码域
功能码在 Modbus 协议中用于表示消息帧的功能。
功能码域由 1 个字节构成,因此其取值范围为 1~255(十进制)。例如,常用的功能码有 03、04、06、16 等,其中 03 功能码的作用是读保持寄存器内容,04 功能码的作用是读输入寄存器内容(输入寄存器和保持寄存器的区别可参考上一节的内容),06 功能码的内容是预置单个保持寄存器,16 功能码的内容则是预置多个保持寄存器。
从设备根据功能码执行对应的功能,执行完成后,正常情况下则在返回的响应消息帧中设置同样的功能码;如果出现异常,则在返回的消息帧中将功能码最
高位(MSB)设置为 1。据此,主设备可获知对应从设备的执行情况。
另外,对于主设备发送的功能码,则从设备根据具体配置来决定是否支持此功能码。如果不支持,则返回异常响应。
3.3.5 数据域
数据域与功能码紧密相关,存放功能码需要操作的具体数据。数据域以字节为单位,长度是可变的,对于有些功能码,数据域可以为空。
3.4差错检验
1.ASCII 模式
在 ASCII 模式中,报文包含一个错误校验字段。该字段由两个字符组成,其值基于对全部报文内容执行的纵向冗余校验(Longitudinal Redundancy Check, LRC)计算的结果而来,计算对象不包括起始的冒号(;)和回车换行符号(CRLF)。
2.RTU模式
在 RTU 模式中,报文同样包含一个错误校验字段。与 ASCII 模式不同的是,该字段由 16 个比特位共两个字节组成。其值基于对全部报文内容执行的循环冗
余校验(Cyclical Redundancy Check,CRC)计算的结果而来,计算对象包括校验域之前的所有字节。
3.4.1 LRC校验
(1)对消息帧中的全部字节相加(不包括起始";“和结束符"CR-LF”),并把结果送入 8 位数据区,舍弃进位。
(2)由 0xFF(即全 1)减去最终的数据值,产生 1的补码(即二进制反码)。
(3)加"1"产生二进制补码。
3.4.2 CRC校验
(1)预置一个值为 0xFFFF 的 16 位寄存器,此寄存器为 CRC寄存器。
(2)把第 1 个 8 位二进制数据(即通信消息帧的第 1 个字节)与 16 位的 CRC寄存器的相异或,异或的结果仍存放于该 CRC 寄存器中。
(3)把 CRC寄存器的内容右移一位,用 0 填补最高位,并检测移出位是 0 还是1。
(4)如果移出位为零,则重复步骤(3)(再次右移一位);如果移出位为 1,则CRC 寄存器与0xA001 进行异或。
(5)重复步骤(3)和(4),直到右移 8 次,这样整个 8 位数据全部进行了处理。(6)重复步骤(2)~(5),进行通信消息帧下一个字节的处理。
(7)将该通信消息帧所有字节按上述步骤计算完成后,得到的 16 位 CRC 寄存器的高、低字节进行交换。即发送时首先添加低位字节,然后添加高位字节。
(8)最后得到的 CRC 寄存器内容即为 CRC 校验码。
第四章 功能码
简而言之,Modbus 功能码占用一个字节,取值范围是 1~127 。之所以 127以上不能使用,是因为 Modbus 规定当出现异常时,功能码士0x80(十进制 128)代表异常状态,因此 129(1+128)~255(127+128)的取值代表异常码。
Modbus 标准协议中规定了有 3 类 Modbus 功能码,分别是∶
1.公共功能码
(1)被明确定义的功能码;
(2)保证唯一性;
(3)由 Modbus 协会确认,并提供公开的文档;
(4)可进行一致性测试;
(5)包括协议定义的功能码和保留将来使用的功能码。
2.用户自定义功能码
(1)有两个用户自定义功能码区域,分别是65~72 和 100~110;
(2)用户自定义,不保证唯一性。
3.保留功能码
保留功能码是因为历史遗留原因,某些公司的传统产品上现行使用的功能码不作为公共使用。
功能码可分为位操作和字操作两类。位操作的最小单位为一位(bit),字操作的最小单位为两个字节。
● 位操作指令;读线圈状态功能码 01,读(离散)输入状态功能码 02,写单个线圈功能码 06 和写多个线圈功能码 15。
●字操作指令∶读保持寄存器功能码 03,读输入寄存器功能码 04,写单个保持寄存器功能码 06,写多个保持寄存器功能码 16。
4.1 01 (0x01)读线圈应用方法
读线圈01 (0x01) | ||
---|---|---|
请求 | ||
功能码 | 1字节 | 0x01 |
起始地址 | 2字节 | 0x0000至0xFFFF |
线圈数量 | 2字节 | 1至2000(0x7D0) |
响应 | ||
功能码 | 1字节 | 0x01 |
字节计数 | 1字节 | N |
线圈状态 | n字节 | n = N或N + 1 |
N = 寄存器数量/8,余数不等0,那么N = N + 1 | ||
错误 | ||
功能码 | 1字节 | 功能码 + 0x80 |
异常码 | 1字节 | 01或02或03或04 |
4.2 03 (0x03)读保持寄存器应用方法
请求 | ||
---|---|---|
功能码 | 1个字节 | 0x03 |
起始地址 | 2个字节 | 0x0000 to 0xFFFF |
寄存器数量 | 2个字节 | 1 to 125 (0x7D) |
响应 | ||
功能码 | 1个字节 | 0x03 |
字节数 | 1个字节 | 2 x N* |
寄存器值 | N* x 2个字节 | |
*N = 寄存器数量 | ||
错误 | ||
差错码 | 1个字节 | 0x83 |
异常码 | 1个字节 | 01或02或03或04 |
4.3 06 (0x06)写单个寄存器应用方法
4.4 15 (0x0F) 写多个线圈应用方法
4.5 16 (0x10) 写多个寄存器应用方法
第五章 libmobus库
libmodbus 是一个免费的跨平台的支持 RTU和 TCP 的 Modbus 库,遵循LGPLv2.1+ 协议。libmodbus 支持 Linux、Mac OS X、FreeBSD、QNX 和
Windows 等操作系统。libmodbus 可以向符合 Modbus 协议的设备发送和接收数据,并支持通过串口或者 TCP 网络进行连接。
https://github.com/stephane/libmodbus.git
libmodbus库与应用程序的基本关系。
第六章 libmodbus源码解析
第七章 RTU Slave开发
RTU-Slave的代码流程:
1.初始化并生成modbus_t 结构体;
2.设置从机端的ID;
3.起点调试模式;
4.建立modbus连接;
5.modbus_mapping_new()初始化寄存器,返回一个modbus_mapping_t 指针;
6.调用modbus_receive()函数判断串口的接收数据,负责接收和分析;
7.调用modbus_reply()函数,对接收到的请求指示发送响应(回复);
8.释放modbus_mapping_t 结构体;
9.关闭modbus连接;
10.释放modbus_t 结构体。
(1) 初始化
ctx = modbus_new_rtu("COM4", 9600, 'N', 8, 1); //创建一个RTU类型的容器,即创建一个COM口为C0M4 波特率9600bit/s
MODBUS_API modbus_t* modbus_new_rtu
(const char *device, int baud, char parity, int data_bit, int stop_bit);
设置从机地址
modbus_set_slave(ctx, SERVER_ID);
/* Define the slave number */
int modbus_set_slave(modbus_t *ctx, int slave)
{
if (ctx == NULL) {
errno = EINVAL;
return -1;
}
return ctx->backend->set_slave(ctx, slave);
}
int modbus_get_slave(modbus_t *ctx)
{
if (ctx == NULL) {
errno = EINVAL;
return -1;
}
return ctx->slave;
}
(2) 建立连接
int modbus_connect(modbus_t *ctx)
{
if (ctx == NULL) {
errno = EINVAL;
return -1;
}
return ctx->backend->connect(ctx);
}
(3)申请内存块用作4种寄存器数据存放
mb_mapping = modbus_mapping_new(500, 500, 500, 500);
(4)循环查询和响应
for (;;)
{
//MODBUS_TCP_MAX_ADU_LENGTH,RTU帧格式最大数据字符数
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
int rc;
rc = modbus_receive(ctx, query);
if (rc >= 0)
{
/*rc is the query size*/
modbus_reply(ctx, query, rc, mb_mapping);
}
else
{
//connection closed by the client or error
printf("Connection Closedn");
}
}
(5)释放结构体,关闭连接
modbus_mapping_free(mb_mapping);
modbus_close(ctx);
modbus_free(ctx);
7.1 modbus_receive
/* Waits a response from a modbus server or a request from a modbus client.
This function blocks if there is no replies (3 timeouts).
The function shall return the number of received characters and the received
message in an array of uint8_t if successful. Otherwise it shall return -1
and errno is set to one of the values defined below:
- ECONNRESET
- EMBBADDATA
- EMBUNKEXC
- ETIMEDOUT
- read() or recv() error codes
*/
int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type)
{
int rc;
fd_set rset;
struct timeval tv;
struct timeval *p_tv;
int length_to_read;
int msg_length = 0;
_step_t step;
if (ctx->debug) {
if (msg_type == MSG_INDICATION) {
printf("Waiting for an indication...n");
} else {
printf("Waiting for a confirmation...n");
}
}
/* Add a file descriptor to the set */
FD_ZERO(&rset);
FD_SET(ctx->s, &rset);
/* We need to analyse the message step by step. At the first step, we want
* to reach the function code because all packets contain this
* information. */
step = _STEP_FUNCTION;
length_to_read = ctx->backend->header_length + 1;
if (msg_type == MSG_INDICATION) {
/* Wait for a message, we don't know when the message will be
* received */
if (ctx->indication_timeout.tv_sec == 0 && ctx->indication_timeout.tv_usec == 0) {
/* By default, the indication timeout isn't set */
p_tv = NULL;
} else {
/* Wait for an indication (name of a received request by a server, see schema) */
tv.tv_sec = ctx->indication_timeout.tv_sec;
tv.tv_usec = ctx->indication_timeout.tv_usec;
p_tv = &tv;
}
} else {
tv.tv_sec = ctx->response_timeout.tv_sec;
tv.tv_usec = ctx->response_timeout.tv_usec;
p_tv = &tv;
}
while (length_to_read != 0) {
rc = ctx->backend->select(ctx, &rset, p_tv, length_to_read);
if (rc == -1) {
_error_print(ctx, "select");
if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) {
int saved_errno = errno;
if (errno == ETIMEDOUT) {
_sleep_response_timeout(ctx);
modbus_flush(ctx);
} else if (errno == EBADF) {
modbus_close(ctx);
modbus_connect(ctx);
}
errno = saved_errno;
}
return -1;
}
rc = ctx->backend->recv(ctx, msg + msg_length, length_to_read);
if (rc == 0) {
errno = ECONNRESET;
rc = -1;
}
if (rc == -1) {
_error_print(ctx, "read");
if ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) &&
(errno == ECONNRESET || errno == ECONNREFUSED ||
errno == EBADF)) {
int saved_errno = errno;
modbus_close(ctx);
modbus_connect(ctx);
/* Could be removed by previous calls */
errno = saved_errno;
}
return -1;
}
/* Display the hex code of each character received */
if (ctx->debug) {
int i;
for (i=0; i < rc; i++)
printf("<%.2X>", msg[msg_length + i]);
}
/* Sums bytes received */
msg_length += rc;
/* Computes remaining bytes */
length_to_read -= rc;
if (length_to_read == 0) {
switch (step) {
case _STEP_FUNCTION:
/* Function code position */
length_to_read = compute_meta_length_after_function(
msg[ctx->backend->header_length],
msg_type);
if (length_to_read != 0) {
step = _STEP_META;
break;
} /* else switches straight to the next step */
case _STEP_META:
length_to_read = compute_data_length_after_meta(
ctx, msg, msg_type);
if ((msg_length + length_to_read) > (int)ctx->backend->max_adu_length) {
errno = EMBBADDATA;
_error_print(ctx, "too many data");
return -1;
}
step = _STEP_DATA;
break;
default:
break;
}
}
if (length_to_read > 0 &&
(ctx->byte_timeout.tv_sec > 0 || ctx->byte_timeout.tv_usec > 0)) {
/* If there is no character in the buffer, the allowed timeout
interval between two consecutive bytes is defined by
byte_timeout */
tv.tv_sec = ctx->byte_timeout.tv_sec;
tv.tv_usec = ctx->byte_timeout.tv_usec;
p_tv = &tv;
}
/* else timeout isn't set again, the full response must be read before
expiration of response timeout (for CONFIRMATION only) */
}
if (ctx->debug)
printf("n");
return ctx->backend->check_integrity(ctx, msg, msg_length);
}
/* Receive the request from a modbus master */
int modbus_receive(modbus_t *ctx, uint8_t *req)
{
if (ctx == NULL) {
errno = EINVAL;
return -1;
}
return ctx->backend->receive(ctx, req);
}
7.2modbus_reply
/* Send a response to the received request.
Analyses the request and constructs a response.
If an error occurs, this function construct the response
accordingly.
*/
int modbus_reply(modbus_t *ctx, const uint8_t *req,
int req_length, modbus_mapping_t *mb_mapping)
{
int offset;
int slave;
int function;
uint16_t address;
uint8_t rsp[MAX_MESSAGE_LENGTH];
int rsp_length = 0;
sft_t sft;
if (ctx == NULL) {
errno = EINVAL;
return -1;
}
offset = ctx->backend->header_length;
slave = req[offset - 1];
function = req[offset];
address = (req[offset + 1] << 8) + req[offset + 2];
sft.slave = slave;
sft.function = function;
sft.t_id = ctx->backend->prepare_response_tid(req, &req_length);
/* Data are flushed on illegal number of values errors. */
/********************************************************/
switch (function) {
case MODBUS_FC_READ_COILS:
case MODBUS_FC_READ_DISCRETE_INPUTS: {
unsigned int is_input = (function == MODBUS_FC_READ_DISCRETE_INPUTS);
int start_bits = is_input ? mb_mapping->start_input_bits : mb_mapping->start_bits;
int nb_bits = is_input ? mb_mapping->nb_input_bits : mb_mapping->nb_bits;
uint8_t *tab_bits = is_input ? mb_mapping->tab_input_bits : mb_mapping->tab_bits;
const char * const name = is_input ? "read_input_bits" : "read_bits";
int nb = (req[offset + 3] << 8) + req[offset + 4];
/* The mapping can be shifted to reduce memory consumption and it
doesn't always start at address zero. */
int mapping_address = address - start_bits;
if (nb < 1 || MODBUS_MAX_READ_BITS < nb) {
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE,
"Illegal nb of values %d in %s (max %d)n",
nb, name, MODBUS_MAX_READ_BITS);
} else if (mapping_address < 0 || (mapping_address + nb) > nb_bits) {
rsp_length = response_exception(
ctx, &sft,
MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE,
"Illegal data address 0x%0X in %sn",
mapping_address < 0 ? address : address + nb, name);
} else {
rsp_length = ctx->backend->build_response_basis(&sft, rsp);
rsp[rsp_length++] = (nb / 8) + ((nb % 8) ? 1 : 0);
rsp_length = response_io_status(tab_bits, mapping_address, nb,
rsp, rsp_length);
}
}
break;
/*****************************************HOLDING_REGISTERS*************************************/
case MODBUS_FC_READ_HOLDING_REGISTERS:
case MODBUS_FC_READ_INPUT_REGISTERS: {
unsigned int is_input = (function == MODBUS_FC_READ_INPUT_REGISTERS);
int start_registers = is_input ? mb_mapping->start_input_registers : mb_mapping->start_registers;
int nb_registers = is_input ? mb_mapping->nb_input_registers : mb_mapping->nb_registers;
uint16_t *tab_registers = is_input ? mb_mapping->tab_input_registers : mb_mapping->tab_registers;
const char * const name = is_input ? "read_input_registers" : "read_registers";
int nb = (req[offset + 3] << 8) + req[offset + 4];
/* The mapping can be shifted to reduce memory consumption and it
doesn't always start at address zero. */
int mapping_address = address - start_registers;
if (nb < 1 || MODBUS_MAX_READ_REGISTERS < nb) {
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE,
"Illegal nb of values %d in %s (max %d)n",
nb, name, MODBUS_MAX_READ_REGISTERS);
} else if (mapping_address < 0 || (mapping_address + nb) > nb_registers) {
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE,
"Illegal data address 0x%0X in %sn",
mapping_address < 0 ? address : address + nb, name);
} else {
int i;
rsp_length = ctx->backend->build_response_basis(&sft, rsp);
rsp[rsp_length++] = nb << 1;
for (i = mapping_address; i < mapping_address + nb; i++) {
rsp[rsp_length++] = tab_registers[i] >> 8;
rsp[rsp_length++] = tab_registers[i] & 0xFF;
}
}
}
break;
/***********************************************************************************/
case MODBUS_FC_WRITE_SINGLE_COIL: {
int mapping_address = address - mb_mapping->start_bits;
if (mapping_address < 0 || mapping_address >= mb_mapping->nb_bits) {
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE,
"Illegal data address 0x%0X in write_bitn",
address);
} else {
int data = (req[offset + 3] << 8) + req[offset + 4];
if (data == 0xFF00 || data == 0x0) {
mb_mapping->tab_bits[mapping_address] = data ? ON : OFF;
memcpy(rsp, req, req_length);
rsp_length = req_length;
} else {
rsp_length = response_exception(
ctx, &sft,
MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, FALSE,
"Illegal data value 0x%0X in write_bit request at address %0Xn",
data, address);
}
}
}
break;
case MODBUS_FC_WRITE_SINGLE_REGISTER: {
int mapping_address = address - mb_mapping->start_registers;
if (mapping_address < 0 || mapping_address >= mb_mapping->nb_registers) {
rsp_length = response_exception(
ctx, &sft,
MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE,
"Illegal data address 0x%0X in write_registern",
address);
} else {
int data = (req[offset + 3] << 8) + req[offset + 4];
mb_mapping->tab_registers[mapping_address] = data;
memcpy(rsp, req, req_length);
rsp_length = req_length;
}
}
break;
case MODBUS_FC_WRITE_MULTIPLE_COILS: {
int nb = (req[offset + 3] << 8) + req[offset + 4];
int nb_bits = req[offset + 5];
int mapping_address = address - mb_mapping->start_bits;
if (nb < 1 || MODBUS_MAX_WRITE_BITS < nb || nb_bits * 8 < nb) {
/* May be the indication has been truncated on reading because of
* invalid address (eg. nb is 0 but the request contains values to
* write) so it's necessary to flush. */
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE,
"Illegal number of values %d in write_bits (max %d)n",
nb, MODBUS_MAX_WRITE_BITS);
} else if (mapping_address < 0 ||
(mapping_address + nb) > mb_mapping->nb_bits) {
rsp_length = response_exception(
ctx, &sft,
MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE,
"Illegal data address 0x%0X in write_bitsn",
mapping_address < 0 ? address : address + nb);
} else {
/* 6 = byte count */
modbus_set_bits_from_bytes(mb_mapping->tab_bits, mapping_address, nb,
&req[offset + 6]);
rsp_length = ctx->backend->build_response_basis(&sft, rsp);
/* 4 to copy the bit address (2) and the quantity of bits */
memcpy(rsp + rsp_length, req + rsp_length, 4);
rsp_length += 4;
}
}
break;
case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: {
int nb = (req[offset + 3] << 8) + req[offset + 4];
int nb_bytes = req[offset + 5];
int mapping_address = address - mb_mapping->start_registers;
if (nb < 1 || MODBUS_MAX_WRITE_REGISTERS < nb || nb_bytes != nb * 2) {
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE,
"Illegal number of values %d in write_registers (max %d)n",
nb, MODBUS_MAX_WRITE_REGISTERS);
} else if (mapping_address < 0 ||
(mapping_address + nb) > mb_mapping->nb_registers) {
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE,
"Illegal data address 0x%0X in write_registersn",
mapping_address < 0 ? address : address + nb);
} else {
int i, j;
for (i = mapping_address, j = 6; i < mapping_address + nb; i++, j += 2) {
/* 6 and 7 = first value */
mb_mapping->tab_registers[i] =
(req[offset + j] << 8) + req[offset + j + 1];
}
rsp_length = ctx->backend->build_response_basis(&sft, rsp);
/* 4 to copy the address (2) and the no. of registers */
memcpy(rsp + rsp_length, req + rsp_length, 4);
rsp_length += 4;
}
}
break;
case MODBUS_FC_REPORT_SLAVE_ID: {
int str_len;
int byte_count_pos;
rsp_length = ctx->backend->build_response_basis(&sft, rsp);
/* Skip byte count for now */
byte_count_pos = rsp_length++;
rsp[rsp_length++] = _REPORT_SLAVE_ID;
/* Run indicator status to ON */
rsp[rsp_length++] = 0xFF;
/* LMB + length of LIBMODBUS_VERSION_STRING */
str_len = 3 + strlen(LIBMODBUS_VERSION_STRING);
memcpy(rsp + rsp_length, "LMB" LIBMODBUS_VERSION_STRING, str_len);
rsp_length += str_len;
rsp[byte_count_pos] = rsp_length - byte_count_pos - 1;
}
break;
case MODBUS_FC_READ_EXCEPTION_STATUS:
if (ctx->debug) {
fprintf(stderr, "FIXME Not implementedn");
}
errno = ENOPROTOOPT;
return -1;
break;
case MODBUS_FC_MASK_WRITE_REGISTER: {
int mapping_address = address - mb_mapping->start_registers;
if (mapping_address < 0 || mapping_address >= mb_mapping->nb_registers) {
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE,
"Illegal data address 0x%0X in write_registern",
address);
} else {
uint16_t data = mb_mapping->tab_registers[mapping_address];
uint16_t and = (req[offset + 3] << 8) + req[offset + 4];
uint16_t or = (req[offset + 5] << 8) + req[offset + 6];
data = (data & and) | (or & (~and));
mb_mapping->tab_registers[mapping_address] = data;
memcpy(rsp, req, req_length);
rsp_length = req_length;
}
}
break;
case MODBUS_FC_WRITE_AND_READ_REGISTERS: {
int nb = (req[offset + 3] << 8) + req[offset + 4];
uint16_t address_write = (req[offset + 5] << 8) + req[offset + 6];
int nb_write = (req[offset + 7] << 8) + req[offset + 8];
int nb_write_bytes = req[offset + 9];
int mapping_address = address - mb_mapping->start_registers;
int mapping_address_write = address_write - mb_mapping->start_registers;
if (nb_write < 1 || MODBUS_MAX_WR_WRITE_REGISTERS < nb_write ||
nb < 1 || MODBUS_MAX_WR_READ_REGISTERS < nb ||
nb_write_bytes != nb_write * 2) {
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE,
"Illegal nb of values (W%d, R%d) in write_and_read_registers (max W%d, R%d)n",
nb_write, nb, MODBUS_MAX_WR_WRITE_REGISTERS, MODBUS_MAX_WR_READ_REGISTERS);
} else if (mapping_address < 0 ||
(mapping_address + nb) > mb_mapping->nb_registers ||
mapping_address < 0 ||
(mapping_address_write + nb_write) > mb_mapping->nb_registers) {
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE,
"Illegal data read address 0x%0X or write address 0x%0X write_and_read_registersn",
mapping_address < 0 ? address : address + nb,
mapping_address_write < 0 ? address_write : address_write + nb_write);
} else {
int i, j;
rsp_length = ctx->backend->build_response_basis(&sft, rsp);
rsp[rsp_length++] = nb << 1;
/* Write first.
10 and 11 are the offset of the first values to write */
for (i = mapping_address_write, j = 10;
i < mapping_address_write + nb_write; i++, j += 2) {
mb_mapping->tab_registers[i] =
(req[offset + j] << 8) + req[offset + j + 1];
}
/* and read the data for the response */
for (i = mapping_address; i < mapping_address + nb; i++) {
rsp[rsp_length++] = mb_mapping->tab_registers[i] >> 8;
rsp[rsp_length++] = mb_mapping->tab_registers[i] & 0xFF;
}
}
}
break;
default:
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_FUNCTION, rsp, TRUE,
"Unknown Modbus function code: 0x%0Xn", function);
break;
}
/* Suppress any responses when the request was a broadcast */
return (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU &&
slave == MODBUS_BROADCAST_ADDRESS) ? 0 : send_msg(ctx, rsp, rsp_length);
}
最后
以上就是凶狠高山为你收集整理的Modbus Slave学习笔记的全部内容,希望文章能够帮你解决Modbus Slave学习笔记所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复