#ifndef __MODBUS_TCP_H__ #define __MODBUS_TCP_H__ #include "config.h" /* 数据帧 __________________________________________________ | Modbus ADU | |------------------------------------------------| | MBAP | PDU | |---------------------------------|--------------| | 事务元标识符 | 协议标识符 | 长度 | 单元标识符 | 功能码 | 数据 | |————————————————————————————————————————————————| 事务元标识符(2个字节):用于事务处理配对。在响应中,MODBUS服务器复制请求的事务处理标识符。 这里在以太网传输中存在一个问题,就是先发后至,我们可以利用这个事务处理标识符做一个TCP序列号,来防止这种情况所造成的数据收发错乱。 (这里我们先不讨论这种情况,这个事务处理标识符我们统一使用0x00,0x01) 协议标识符(2个字节):MODBUS 协议标识符为0x00,0x00 长度(2个字节):长度域是下一个域的字节数,包括单元标识符和数据域。 单元标识符(1个字节):该设备的编号。(可以使用控制器的IP地址标识)。 在MODBUS MODBUS+串行链路子网中对设备进行寻址时,这个域是标识设备地址。在这种情况下,“单元标识符”携带一个远端设备的MODBUS从站地址: 如果MODBUS服务器连接到MODBUS+或MODBUS串行链路子网,并通过一个桥或网关配置地址这个服务器,MODBUS单元标识符对识别连接到网桥或网关后的子网的从站设备是必需的。 目的IP地址识别了网桥本身的地址,而网桥则使用MODBUS单元标识符将请求转交给正确的从站设备。 分配串行链路上MODBUS从站设备地址为1~247(10进制),地址0作为广播地址。 对 TCP/IP 来说,利用IP地址寻址MODBUS服务器;因此,MODBUS单元标识符是无用的。必需使用值0xFF。 当对直接连接到TCP/IP网络上的MODBUS服务器寻址时,建议不要在“单元标识符”域使用有效的MODBUS从站地址。 在一个自动系统中重新分配IP地址的情况下,并且如果以前分配给MODBUS服务器的IP地址又被指配给网关,使用一个有效的从站地址可能会由于网关的路由不畅而引起麻烦。 使用无效的从站地址,网关仅是简单地废弃MODBUD PDU,而不会有任何问题。建议:在采用0xFF作为“单元标识符”的无效值。 注:0也可以用作与MODBUS/TCP设备直接通信。 MODBUS 请求 (PDU)是 标准 MODBUS RTU 去掉CRC校验 的部分 */ //------------------------------------------------------------------ #define LEN_MBAP 7 #define MAX_RD_REG_NUMS 125 #define MAX_WR_REG_NUMS 123 #define MAX_MBDAT MAX_RD_REG_NUMS #define MAX_LEN_PDU (1+MAX_MBDAT*2) #define MAX_RD_BIT_NUMS (MAX_RD_REG_NUMS*16) #define MAX_WR_BIT_NUMS (MAX_WR_REG_NUMS*16) #define LEN_ERR_ANSWER (LEN_MBAP + 2) //------------------------------------------------------------------ typedef union { u8 datbuff[LEN_MBAP+MAX_LEN_PDU]; // dat buff 方式 struct { // MBAP u8 mbap[LEN_MBAP]; // MODBUS PDU u8 pdu[MAX_LEN_PDU]; // 数据 } __attribute__ ((packed)) mbappdu; // 通用方式 struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 cmd; // 功能码 u16 data[MAX_MBDAT]; // 数据 } __attribute__ ((packed)) normal; // 通用方式 // 读线圈(离散量输出) 0x01 读离散量输入 0x02 /* 在一个远程设备中,使用该功能码读取线圈(输入)的1 至2000 连续状态。请求PDU 详细说明了起始地址,即指定的第一个线圈(输入)地址和线圈(输入)编号。 寻址线圈(输入)从零开始,因此寻址线圈(输入)1-16 为0-15。 根据数据域的每个比特将响应报文中的线圈(输入)分成为一个位。指示状态为1= ON 和0= OFF。 第一个数据字节的LSB(最低有效位)包括在询问中寻址的线圈(输入)。其它线圈(输入)依次类推,一直到这个字节的高位端为止,并在后续字节中从低位到高位的顺序。 如果返回的线圈(输入)数量不是八的倍数,将用零填充最后数据字节中的剩余比特(一直到字节的高位端)。字节数量域说明了数据的完整字节数。 */ struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 cmd; // 功能码 = 0x01 或 0x02 u16 bitBegAddr; // 位起始地址 0x0000至0xFFFF u16 bitsNum; // 位数量 1至2000(0x7D0) } __attribute__ ((packed)) readBitsStatusRequest; // 读取位请求包 struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 acmd; // 正确应答功能码 = 请求功能码 = 0x01 或 0x02 u8 bytes; // 字节数。 N = 输出数量/8,如果余数不等于0,那么 N=N+1 u8 bitsStatus[MAX_RD_BIT_NUMS/8]; // 位状态, } __attribute__ ((packed)) readBitsStatusAnswer; // 读取位响应包 // 读保持寄存器(输出寄存器) 0x03 , 读输入寄存器 0x04 /* 在一个远程设备中,使用该功能码读取保持寄存器连续块的内容。请求PDU说明了起始寄存器地址和寄存器数量。从零开始寻址寄存器。因此,寻址寄存器1-16 为0-15。 将响应报文中的寄存器数据分成每个寄存器有两字节,在每个字节中直接地调整二进制内容。 对于每个寄存器,第一个字节包括高位比特,并且第二个字节包括低位比特。 */ struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 cmd; // 功能码 = 0x03 或 0x04 u16 begAddr; // 起始地址 (MSB) 0x0000至0xFFFF u16 regsNum; // 寄存器数量 (MSB) 1至125(0x7D) } __attribute__ ((packed)) readRegisterRequest; // 读取寄存器请求包 struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 acmd; // 正确应答功能码 = 请求功能码 = 0x03 或 0x04 u8 bytes; // 字节数。 2xN N为输入寄存器的数量 u16 regsValue[MAX_RD_REG_NUMS]; // 读取的寄存器的内容,每个寄存器内容占用两个字节,高字节在前,低字节在后 (MSB) } __attribute__ ((packed)) readRegisterAnswer; // 读取寄存器应答包 // 写单个线圈 0x05 /* 在一个远程设备上,使用该功能码写单个输出为ON 或OFF。 请求数据域中的常量说明请求的ON/OFF状态。十六进制值FF 00请求输出为ON。十六进制值00 00 请求输出为OFF。其它所有值均是非法的,并且对输出不起作用。 请求PDU说明了强制的线圈地址。从零开始寻址线圈。因此,寻址线圈1 为0。线圈值域的常量说明请求的ON/OFF 状态。 十六进制值0XFF00请求线圈为ON。十六进制值0X0000请求线圈为OFF。其它所有值均为非法的,并且对线圈不起作用。 正常响应是请求的应答,在写入线圈状态之后返回这个正常响应。 */ struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 cmd; // 功能码 = 0x05 u16 bitBegAddr; // 起始地址 0x0000至0xFFFF u8 bitValue; // 位的值 0x00 或 0xFF } __attribute__ ((packed)) writeBitRequest; // 写位请求包 struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 acmd; // 正确应答功能码 = 请求功能码 = 0x05 u8 bitStatus; // 位的状态 0x00 或 0xFF } __attribute__ ((packed)) writeBitAnswer; // 写位响应包 // 写单个寄存器 0x06 /* 在一个远程设备中,使用该功能码写单个保持寄存器。 请求PDU 说明了被写入寄存器的地址。从零开始寻址寄存器。因此,寻址寄存器1 为0。 正常响应是请求的应答,在写入寄存器内容之后返回这个正常响应。 */ struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 cmd; // 功能码 = 0x06 u16 regBegAddr; // 起始地址 0x0000至0xFFFF u16 regValue; // 寄存器的值 0x0000 至 0xFFFF } __attribute__ ((packed)) writeRegisterRequest; // 写位请求包 struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 acmd; // 正确应答功能码 = 请求功能码 = 0x06 u16 regValue; // 寄存器的值 0x0000 至 0xFFFF } __attribute__ ((packed)) writeRegisterAnswer; // 写位响应包 // 写多个线圈 0x0F /* 在一个远程设备中,使用该功能码强制线圈序列中的每个线圈为ON 或OFF。请求PDU说明了强制的线圈参考。从零开始寻址线圈。因此,寻址线圈1 为0。 请求数据域的内容说明了被请求的ON/OFF 状态。域比特位置中的逻辑“1”请求相应输出为ON。域比特位置中的逻辑“0”请求相应输出为OFF。 正常响应返回功能码、起始地址和强制的线圈数量。 */ struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 cmd; // 功能码 = 0x05 u16 bitBegAddr; // 起始位地址 0x0000至0xFFFF u16 bitsNum; // 位的数量 0x0001至0x07B0(2000) u8 bytes; // 字节数 N = 输出数量/8,如果余数不等于0,那么N=N+1 u8 bitsArray[MAX_WR_BIT_NUMS/8]; // 位的值,位在字节中是LSB } __attribute__ ((packed)) writeBitArrayRequest; // 写位请求包 struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 acmd; // 正确应答功能码 = 请求功能码 = 0x05 u16 bitBegAddr; // 起始位地址 0x0000至0xFFFF u16 bitsNum; // 位的数量 0x0001至0x07B0(2000) } __attribute__ ((packed)) writeBitArrayAnswer; // 写位响应包 // 写多个寄存器 0x10 /* 在一个远程设备中,使用该功能码写连续寄存器块(1 至约120 个寄存器)。 在请求数据域中说明了请求写入的值。每个寄存器将数据分成两字节。 正常响应返回功能码、起始地址和被写入寄存器的数量。 */ struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 cmd; // 功能码 = 0x10 u16 regBegAddr; // 寄存器起始位地址 0x0000至0xFFFF u16 regsNum; // 寄存器的数量 0x0001至0x007B(123) u8 bytes; // 字节数 N = 寄存器数量 * 2 u16 regsArray[MAX_WR_REG_NUMS]; // 寄存器的值,MSB } __attribute__ ((packed)) writeRegArrayRequest; // 写位请求包 struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 acmd; // 正确应答功能码 = 请求功能码 = 0x05 u16 regBegAddr; // 寄存器起始位地址 0x0000至0xFFFF u16 regsNum; // 寄存器的数量 0x0001至0x007B(123) } __attribute__ ((packed)) writeRegArrayAnswer; // 写位响应包 /* 洛源驱动器特殊功能码 */ // 读取子参数 0x65 struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 cmd; // 功能码 u16 begAddr; // 起始地址 u16 subAddr; // 子地址 } __attribute__ ((packed)) readSubRegRequest; // 子参数请求包 struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 cmd; // 功能码 u8 bytes; // 字节数 N = 寄存器数量 * 2 u16 subRegsArray[MAX_RD_REG_NUMS]; // 寄存器的值,MSB } __attribute__ ((packed)) readSubRegAnswer; // 子参数应答 // 写入子参数 0x69 struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 acmd; // 功能码 0x69 u16 regBegAddr; // 寄存器起始位地址 0x0000至0xFFFF u16 subAddr; // 寄存器子地址 u8 bytes; // 字节数 N = 寄存器数量 * 2 u16 subRegsArray[MAX_RD_REG_NUMS]; // 寄存器的值,MSB 实际寄存器数量 N = (len - 6) / 2 } __attribute__ ((packed)) writeSubRegRequest; // 子参数请求 struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 cmd; // 功能码 u16 regBegAddr; // 寄存器起始位地址 0x0000至0xFFFF u16 subAddr; // 寄存器子地址 } __attribute__ ((packed)) writeSubRegAnswer; // 子参数应答 struct { // MBAP u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。 u16 protId; // 协议标识符。MODBUS 协议标识符为0x00,0x00 u16 len; // 帧长度。 u8 unitId; // 单元标识符。 // MODBUS PDU u8 acmd; // 错误应答功能码 = 请求功能码+0x80 u8 errCode; // 错误码 } __attribute__ ((packed)) answerError; // 错误应答 } ModbusTcpADU; /* // MODBUS 数据类型 ________________________________________________________________ | 基本表格 | 对象类型 | 访问类型 | 内容 | |———————————————————————————————————————————————————————————————| | 离散量输入 | 单个比特 | 只读 | I/O系统提供这种类型数据 | |-----------|-----------|-----------|---------------------------| | 线圈 | 单个比特 | 读写 | 通过应用程序改变这种类型数据 | |-----------|-----------|-----------|---------------------------| | 输入寄存器 | 16比特字 | 只读 | I/O系统提供这种类型数据 | |-----------|-----------|-----------|---------------------------| | 保持寄存器 | 16比特字 | 读写 | 通过应用程序改变这种类型数据 | |———————————————————————————————————————————————————————————————| */ // MODBUS 功能码 #define READ_COIL_STATUS 0x01 // 读取线圈状态(内部比特或物理线圈) #define READ_INPUT_STATUS 0x02 // 读取输入状态(物理离散量输入) #define READ_HOLDING_REGISTER 0x03 // 读保持寄存器(内部存储器或物理输出存储器) #define READ_INPUT_REGISTER 0x04 // 读取输入寄存器(输入寄存器) #define WRITE_SINGLE_COIL 0x05 // 写单线圈(内部比特或物理线圈) #define WRITE_SINGLE_REGISTER 0x06 // 写单个寄存器(内部存储器或物理输出存储器) #define WRITE_MULTIPLE_COIL 0x0F // 写多个线圈(内部比特或物理线圈) #define WRITE_MULTIPLE_REGISTER 0x10 // 写多个寄存器(内部存储器或物理输出存储器) #define READ_FILE_RECORD 0x14 // 读文件记录(文件记录访问) #define WRITE_FILE_RECORD 0x15 // 写文件记录(文件记录访问) #define WRITE_MASK_REGISTER 0x16 // 屏蔽写寄存器(内部存储器或物理输出存储器) #define RW_MULTIPLE_REGISTER 0x17 // 读/写多个寄存器(内部存储器或物理输出存储器) #define READ_FILE_RECORD_SUBCODE 0x06 // 读文件记录(文件记录访问) #define WRITE_FILE_RECORD_SUBCODE 0x06 // 写文件记录(文件记录访问) // 洛源驱动器功能码 #define READ_REGISTER_SUBPARA 0x65 // 读取子参数 #define WRITE_REGISTER_SUBPARA 0x69 // 写入子参数 // MODBUS TCP 异常码 #define MODBUS_TCP_ERR01 0x01 // 非法的功能码, 服务器不了解功能码 #define MODBUS_TCP_ERR02 0x02 // 非法的数据地址, 与请求有关 #define MODBUS_TCP_ERR03 0x03 // 非法的数据值, 与请求有关 #define MODBUS_TCP_ERR04 0x04 // 服务器故障, 在执行过程中,服务器故障 #define MODBUS_TCP_ERR05 0x05 // 确认, 服务器接受服务调用,但是需要相对长的时间完成服务。因此,服务器仅返回一个服务调用接收的确认。 #define MODBUS_TCP_ERR06 0x06 // 服务器繁忙, 服务器不能接受MODBUS请求PDU。客户应用由责任决定是否和何时重发请求。 #define MODBUS_TCP_ERR0A 0x0A // 网关故障, 网关路经是无效的。 #define MODBUS_TCP_ERR0B 0x0B // 网关故障, 目标设备没有响应。网关生成这个异常信息。 //------------------------------------------------------------------ //------------------------------------------------------------------ #ifndef MAX_MODBUS_TCP_NODE #define MAX_MODBUS_TCP_NODE 0 #endif //------------------------------------------------------------------ void InitModbusTcp(void); void ModbusTcpServerRun(void); // 功能: 写入多个寄存器 int ModbusTcpWriteRegs(int nidx, u16 addr, u16 * regAry, u16 nums); u16 SwapHighLow(u16 data); //------------------------------------------------------------------ #endif