optical/NxFuncs/modbus/modbus_tcp.h
2025-09-04 09:45:08 +08:00

455 lines
16 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#ifndef __MODBUS_TCP_H__
#define __MODBUS_TCP_H__
#include "config.h"
/*
数据帧
__________________________________________________
| Modbus ADU |
|------------------------------------------------|
| MBAP | PDU |
|---------------------------------|--------------|
| 事务元标识符 | 协议标识符 | 长度 | 单元标识符 | 功能码 | 数据 |
|————————————————————————————————————————————————|
事务元标识符2个字节用于事务处理配对。在响应中MODBUS服务器复制请求的事务处理标识符。
这里在以太网传输中存在一个问题就是先发后至我们可以利用这个事务处理标识符做一个TCP序列号来防止这种情况所造成的数据收发错乱。
这里我们先不讨论这种情况这个事务处理标识符我们统一使用0x000x01
协议标识符2个字节MODBUS 协议标识符为0x000x00
长度2个字节长度域是下一个域的字节数包括单元标识符和数据域。
单元标识符1个字节该设备的编号。可以使用控制器的IP地址标识
在MODBUS MODBUS+串行链路子网中对设备进行寻址时这个域是标识设备地址。在这种情况下“单元标识符”携带一个远端设备的MODBUS从站地址
如果MODBUS服务器连接到MODBUS+或MODBUS串行链路子网并通过一个桥或网关配置地址这个服务器MODBUS单元标识符对识别连接到网桥或网关后的子网的从站设备是必需的。
目的IP地址识别了网桥本身的地址而网桥则使用MODBUS单元标识符将请求转交给正确的从站设备。
分配串行链路上MODBUS从站设备地址为124710进制地址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 协议标识符为0x000x00
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 协议标识符为0x000x00
u16 len; // 帧长度。
u8 unitId; // 单元标识符。
// MODBUS PDU
u8 cmd; // 功能码 = 0x01 或 0x02
u16 bitBegAddr; // 位起始地址 0x0000至0xFFFF
u16 bitsNum; // 位数量 1至20000x7D0
} __attribute__ ((packed)) readBitsStatusRequest; // 读取位请求包
struct
{
// MBAP
u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。
u16 protId; // 协议标识符。MODBUS 协议标识符为0x000x00
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 协议标识符为0x000x00
u16 len; // 帧长度。
u8 unitId; // 单元标识符。
// MODBUS PDU
u8 cmd; // 功能码 = 0x03 或 0x04
u16 begAddr; // 起始地址 (MSB) 0x0000至0xFFFF
u16 regsNum; // 寄存器数量 (MSB) 1至1250x7D
} __attribute__ ((packed)) readRegisterRequest; // 读取寄存器请求包
struct
{
// MBAP
u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。
u16 protId; // 协议标识符。MODBUS 协议标识符为0x000x00
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 协议标识符为0x000x00
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 协议标识符为0x000x00
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 协议标识符为0x000x00
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 协议标识符为0x000x00
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 协议标识符为0x000x00
u16 len; // 帧长度。
u8 unitId; // 单元标识符。
// MODBUS PDU
u8 cmd; // 功能码 = 0x05
u16 bitBegAddr; // 起始位地址 0x0000至0xFFFF
u16 bitsNum; // 位的数量 0x0001至0x07B02000
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 协议标识符为0x000x00
u16 len; // 帧长度。
u8 unitId; // 单元标识符。
// MODBUS PDU
u8 acmd; // 正确应答功能码 = 请求功能码 = 0x05
u16 bitBegAddr; // 起始位地址 0x0000至0xFFFF
u16 bitsNum; // 位的数量 0x0001至0x07B02000
} __attribute__ ((packed)) writeBitArrayAnswer; // 写位响应包
// 写多个寄存器 0x10
/*
在一个远程设备中,使用该功能码写连续寄存器块(1 至约120 个寄存器)。
在请求数据域中说明了请求写入的值。每个寄存器将数据分成两字节。
正常响应返回功能码、起始地址和被写入寄存器的数量。
*/
struct
{
// MBAP
u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。
u16 protId; // 协议标识符。MODBUS 协议标识符为0x000x00
u16 len; // 帧长度。
u8 unitId; // 单元标识符。
// MODBUS PDU
u8 cmd; // 功能码 = 0x10
u16 regBegAddr; // 寄存器起始位地址 0x0000至0xFFFF
u16 regsNum; // 寄存器的数量 0x0001至0x007B123
u8 bytes; // 字节数 N = 寄存器数量 * 2
u16 regsArray[MAX_WR_REG_NUMS]; // 寄存器的值MSB
} __attribute__ ((packed)) writeRegArrayRequest; // 写位请求包
struct
{
// MBAP
u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。
u16 protId; // 协议标识符。MODBUS 协议标识符为0x000x00
u16 len; // 帧长度。
u8 unitId; // 单元标识符。
// MODBUS PDU
u8 acmd; // 正确应答功能码 = 请求功能码 = 0x05
u16 regBegAddr; // 寄存器起始位地址 0x0000至0xFFFF
u16 regsNum; // 寄存器的数量 0x0001至0x007B123
} __attribute__ ((packed)) writeRegArrayAnswer; // 写位响应包
/*
洛源驱动器特殊功能码
*/
// 读取子参数 0x65
struct
{
// MBAP
u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。
u16 protId; // 协议标识符。MODBUS 协议标识符为0x000x00
u16 len; // 帧长度。
u8 unitId; // 单元标识符。
// MODBUS PDU
u8 cmd; // 功能码
u16 begAddr; // 起始地址
u16 subAddr; // 子地址
} __attribute__ ((packed)) readSubRegRequest; // 子参数请求包
struct
{
// MBAP
u16 transId; // 事务元标识符。MODBUS请求/响应事务处理的识别码。
u16 protId; // 协议标识符。MODBUS 协议标识符为0x000x00
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 协议标识符为0x000x00
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 协议标识符为0x000x00
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 协议标识符为0x000x00
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