文章目录
1. Modbus 简介
Modbus 属于串行通信协议,数据一位一位顺序依次传送 。
- Modbus 属于应用层协议,只定义了数据包组织结构和内容的公共、格式。依赖其他物理层和数据链路层来传输数据。
- Modbus 目前分别定义了基于串口传输的 Modbus-RTU 和基于以太网传输的 Modbus-TCP 数据传输规则。
- Modbus 的工作模式是主从通信,在所有节点中,其中一个为 Master 节点,其余为 Slave 节点。(Master 至少且只有 1 个)Modbus 使用请求/应答机制。所以不能同步通信,属于半双工。
- Modbus-TCP 模式下,主站被称为客户端(Client),从站被称为服务器(Server)IANA 互联网编号分配管理机构给 Modbus 协议赋予了 TCP 端口号为 502。
- Modbus 数据类型有:
- 输出线圈(0x1 Coils),1 Bit,数值范围 ON 或 OFF,可读可写。地址范围:00001 - 09999
- 输入离散量(0x2 Discrete Input),1 Bit,数值范围 ON 或 OFF,只读。地址范围:10001 - 19999
- 保持寄存器(0x3 Holding Registers),16 Bit 的寄存器,可读可写。地址范围:30001 - 39999
- 输入寄存器(0x4 Input Registgers),16 Bit 的寄存器,只读。地址范围:40001 - 49999
2. Modbus-RTU 报文格式
3. Modbus-TCP 报文格式
ModbusRTU如何判断开始与结束
-
整个报文帧必须以连续的字符流发送。如果两个字符之间的空闲间隔大于 1.5 个字符时间,则报文帧被认为不完整应该被接收节点丢弃。
-
ModbusRTU协议中,需要用时间间隔来判断一帧报文的开始和结束,协议规定的时间为3.5个字符周期。
-
在一帧报文开始前,必须有大于3.5个字符周期的空闲时间,一帧报文结束后,也必须要有3.5个字符周期的空闲时间,否则就会出现粘包的请况。3.5个字符周期是一个具体时间,与波特率有关。
4. Modbus-RTU 转 Modbus-TCP
uint8_t *rtu_to_tcp(uint8_t *data, uint32_t seq, uint32_t len, size_t *out_len)
{
size_t pdu_len = len - 2;
size_t total_len = pdu_len + 6;
size_t total_len = pdu_len;
uint8_t *result = (uint8_t *)malloc(total_len);
if (!result) {
printf("rtu_to_tcp: Momery alloc failed\n");
*out_len = 0;
return NULL;
}
result[0] = (seq >> 8) & 0xFF;
result[1] = seq & 0xFF;
result[2] = 0x00;
result[3] = 0x00;
result[4] = (pdu_len + 0) >> 8;
result[5] = (pdu_len + 0) & 0xFF;
memcpy(result , data, pdu_len);
*out_len = total_len;
return result;
}
5. Modbus-TCP 转 Modbus-RTU
uint8_t * tcp_to_rtu(uint8_t *data, size_t len, size_t *out_len)
{
uint8_t pdu_len = data[5];
if (pdu_len + 6 != len) {
printf("Invalid PDU len\n");
return NULL;
}
uint8_t *pdu_with_crc = (uint8_t *)malloc(pdu_len + 0);
if (!pdu_with_crc) {
printf("Momery alloc failed\n");
return NULL;
}
memcpy(pdu_with_crc, data + 6, pdu_len);
uint16_t crc = modbus_crc16(pdu_with_crc, pdu_len);
uint8_t crc_bytes[2];
crc_bytes[0] = crc & 0xFF;
crc_bytes[1] = (crc >> 8) & 0xFF;
memcpy(pdu_with_crc + pdu_len, crc_bytes, 2);
*out_len = pdu_len + 0;
return pdu_with_crc;
}
- modbus_crc 函数实现可以参考这里
6. Modbus 常用功能码
7. Modbus 调试软件使用说明
Modbus 调试软件获取
Modbus-Poll
Modbus-Slave
Modbus 调试软件配置
8. Modbus 结构体
_modbus
struct _modbus {
/* Slave address */
int slave; // 从站地址
/* Socket or file descriptor */
int s; // Socket 套接字(Modbus-TCP) or 串口句柄(Modbus-RTU)
int debug;
int error_recovery;
struct timeval response_timeout; // 响应超时设置
struct timeval byte_timeout; // 字节超时设置
const modbus_backend_t *backend;
void *backend_data;
}
modbus_backend_t
typedef struct _modbus_backend {
unsigned int backend_type;
unsigned int header_length;
unsigned int checksum_length;
unsigned int max_adu_length;
int (*set_slave) (modbus_t *ctx, int slave);
int (*build_request_basis) (modbus_t *ctx, int function, int addr,
int nb, uint8_t *req);
int (*build_response_basis) (sft_t *sft, uint8_t *rsp);
int (*prepare_response_tid) (const uint8_t *req, int *req_length);
int (*send_msg_pre) (uint8_t *req, int req_length);
ssize_t (*send) (modbus_t *ctx, const uint8_t *req, int req_length);
int (*receive) (modbus_t *ctx, uint8_t *req);
ssize_t (*recv) (modbus_t *ctx, uint8_t *rsp, int rsp_length);
int (*check_integrity) (modbus_t *ctx, uint8_t *msg,
const int msg_length);
int (*pre_check_confirmation) (modbus_t *ctx, const uint8_t *req,
const uint8_t *rsp, int rsp_length);
int (*connect) (modbus_t *ctx);
void (*close) (modbus_t *ctx);
int (*flush) (modbus_t *ctx);
int (*select) (modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length);
void (*free) (modbus_t *ctx);
} modbus_backend_t;
modbus_mapping_t
- Modbus 各区 寄存器 集合的指针。
typedef struct {
int nb_bits;
int start_bits;
int nb_input_bits;
int start_input_bits;
int nb_input_registers;
int start_input_registers;
int nb_registers;
int start_registers;
uint8_t *tab_bits;
uint8_t *tab_input_bits;
uint16_t *tab_input_registers;
uint16_t *tab_registers;
} modbus_mapping_t;
9. Modbus 一些函数说明
modbus_new_rtu()
modbus_t *modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit);
-
功能:
函数应该分配和初始化一个modbus_t结构,以便在串行线上以RTU模式通信。 -
参数:
- device:参数指定操作系统处理的串口的名称,
例如。“/ dev / ttyS0”或“/ dev/ttyUSB0”。 - baud:指定通信的波特率,例如。9600、19200、57600、115200等
- parity:奇偶校验参数可以有以下值之一: N没有 E为偶校验 O为奇校验
- data_bits参数指定数据的位数,允许的值为5、6、7和8。
- stop_bits参数指定stop的位,允许的值是1和2。
一旦modbus_t结构被初始化,你必须用modbus_set_slave(slave_addr)设置你的设备的从机,并用modbus_connect(slave_addr)连接到串行总线。
- device:参数指定操作系统处理的串口的名称,
-
返回值:
- 如果成功,函数将返回一个指向modbus_t结构体的指针。否则,它将返回NULL并将errno设置为EINVAL(给出了一个无效参数)
modbus_new_tcp()
modbus_t *modbus_new_tcp(const char *ip, int port);
-
功能:
- modbus_new_tcp() 函数应该分配和初始化一个 modbus_t 结构体来与Modbus TCP IPv4服务器通信。
-
参数:
- ip指定客户端想要建立连接的服务器的ip地址。NULL 值可以用来监听服务器模式下的任何地址。
- port参数是要使用的 TCP 端口。将端口设置为MODBUS_TCP_DEFAULT_PORT,使用默认的 502 端口。使用大于或等于 1024 的端口号很方便,因为它不需要拥有管理员权限。
-
返回:
- 如果成功,函数将返回一个指向 modbus_t 结构体的指针。否则,它将返回 NULL 并将 errno 设置为 EINVAL。
modbus_connect()
int modbus_connect(modbus_t *ctx);
- 功能: 建立到Modbus服务器、网络或总线的连接。
modbus_close()
void modbus_close(modbus_t *ctx);
- 功能: 函数应该关闭与上下文中设置的后端建立的连接。
modbus_flush()
int modbus_flush(modbus_t *ctx);
- 功能: 函数将丢弃接收到的数据,但不将数据读到套接字或与上下文ctx相关的文件描述符中。
modbus_rtu_set_customer_rts()
int modbus_rtu_set_customer_rts(modbus_t *ctx, void *(set_rts)(modbus_t *ctx, int on));
- 功能: 设置传输前后设置RST PIN要调用的自定义函数。
- 示例:(我使用 RS485 需要配置控制引脚)
static void custom_rts_rtu(modbus_t *ctx, int on)
{
if (on) {
rs485_set_gpio_value(RS485_CTRL_GPIO, GPIO_OUT_MODE);
} else {
rs485_set_gpio_value(RS485_CTRL_GPIO, GPIO_IN_MODE);
}
}
if (serial_mode == RS485_MODE) {
if (modbus_rtu_set_custom_rts(rtu_ctx, custom_rts_rtu) == -1) {
printf("Set custom rts fun failed %s \n", modbus_strerror(errno));
return -1;
}
modbus_rtu_set_rts(rtu_ctx, MODBUS_RTU_RTS_UP);
}