Modbus_RTU和Modbus库
目录
一.Modbus_RTU
1. 与Modbus TCP的区别
2. Modbus RTU特点
3. Modbus RTU协议格式
4. 报文详解
5. 代码实现RTU通信
1. 打开模拟的RTU从机
2. linux端使用代码实现和串口连接
2.1. 框架搭建
2.2 代码
二.Modbus库
1.库函数
一.Modbus_RTU
1. 与Modbus TCP的区别
在一般工业场景使用modbus RTU的场景还是更多一些,modbus RTU基于串行协议进行收发数据,包括RS232/485等工业总线协议。
与modbus TCP不同的是RTU没有报文头MBAP字段,保留从机地址,在尾部增加了两个CRC检验字节(CRC16),因为网络协议中自带校验,所以在 ModbusTCP协议中不需要使用CRC校验码。
RTU和TCP的总体使用方法基本一致,只是在创建modbus对象时有所不同,TCP需要传入网络socket信息;而RTU需要传入串口相关信息。
2. Modbus RTU特点
Modbus RTU也是主从问答协议,由主机发起,一问一答
3. Modbus RTU协议格式
ModbusRTU数据帧包含:从站地址 功能码 数据 CRC校验码
地址码:从机ID
功能码:同TCP
数据:起始地址 数量 数据
校验码:2个字节,对 地址码 功能码 数据进行校验,可以通过函数自动生成
4. 报文详解
03功能码为例
主机--》从机
01 03 00 00 00 01 84 0A
01 : 从机地址
03:功能码,读取保持寄存器
00 00 :起始地址
00 01:读取的个数,1个。
84 0A:CRC校验码
从机---》主机
01 03 02 00 14 B4 44
01:从机地址
03:功能码
02:字节计数
00 14:数据
B4 44 :CRC校验码
5. 代码实现RTU通信
1. 打开模拟的RTU从机
2. linux端使用代码实现和串口连接
2.1. 框架搭建
1. 打开COM1口
打开串口文件( /dev/ttyS1)
2. 串口初始化
使用专用函数即可
3. 创建要发送的03功能数组
4. 根据所要实现的功能,拼接好数组
5. 加上CRC校验码
6. 串口发送----向串口文件写数据
7. 串口接收----读取串口文件
8. 打印收到的数据
2.2 代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include "Crc_Calc.h"
#include <unistd.h>
int main(int argc, char const *argv[])
{
// 1.打开COM1口
// 打开串口文件( /dev/ttyS1)
int fd=open("/dev/ttyS1",O_RDWR);
if (fd < 0)
{
perror("open err");
return -1;
}
// 2.串口初始化
uart_init(fd);// 使用专用函数即可
// 3.创建要发送的03功能数组
// 4.根据所要实现的功能,拼接好数组
//RTU 01 03 00 00 00 01
uint8_t req[12]={0x01,0x03,0x00,0x00,0x00,0x01};
uint8_t buf[32]={0};
// 5.加上CRC校验码
uint16_t Crc;
Crc=GetCRC16(req,6);//CRC=5;
req[6]=Crc >> 8; // y=CRC+1;
req[7]=Crc;
// 6.串口发送----向串口文件写数据
write(fd,req,8);
// 7.串口接收----读取串口文件
int ret=read(fd,buf,sizeof(buf));
// 8.打印收到的数据
for (int i = 0; i < ret; i++)
{
printf("%02x ",buf[i]);
}
putchar(10);
return 0;
}
二.Modbus库
1.库函数
modbus_t* ctx= modbus_new_tcp(const char *ip, int port)
功能:以TCP方式创建Modbus实例,并初始化
参数:
ip :ip地址
port:端口号
返回值:成功:Modbus实例
失败:NULL
int modbus_set_slave(modbus_t *ctx, int slave)
功能:设置从机ID
参数:
ctx :Modbus实例
slave:从机ID
返回值:成功:0
失败:-1
int modbus_connect(modbus_t *ctx)
功能:和从机(slave)建立连接
参数:
ctx:Modbus实例
返回值:成功:0
失败:-1
int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest)
功能:读取线圈状态,可读取多个连续线圈的状态(对应功能码为0x01)
参数:
ctx :Modbus实例
addr :寄存器起始地址
nb :寄存器个数
dest :得到的状态值
int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest)
功能:读取离散输入状态,可读取多个连续输入的状态(对应功能码为0x02)
参数:
ctx :Modbus实例
addr :寄存器起始地址
nb :寄存器个数
dest :得到的状态值
返回值:成功:返回nb的值
int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
功能:读取保持寄存器的值,可读取多个连续保持寄存器的值(对应功能码为0x03)
参数:
ctx :Modbus实例
addr :寄存器起始地址
nb :寄存器个数
dest :得到的寄存器的值
返回值:成功:读到寄存器的个数
失败:-1
int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
功能:读输入寄存器的值,可读取多个连续输入寄存器的值(对应功能码为0x04)
参数:
ctx :Modbus实例
addr :寄存器起始地址
nb :寄存器个数
dest :得到的寄存器的值
返回值:成功:读到寄存器的个数
失败:-1
int modbus_write_bit(modbus_t *ctx, int addr, int status);
功能:写入单个线圈的状态(对应功能码为0x05)
参数:
ctx :Modbus实例
addr :线圈地址
status:线圈状态
返回值:成功:0
失败:-1
int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *src);
功能:写入多个连续线圈的状态(对应功能码为15)
参数:
ctx :Modbus实例
addr :线圈地址
nb :线圈个数
src :多个线圈状态
返回值:成功:0
失败:-1
int modbus_write_register(modbus_t *ctx, int addr, int value);
功能: 写入单个寄存器(对应功能码为0x06)
参数:
ctx :Modbus实例
addr :寄存器地址
value :寄存器的值
返回值:成功:0
失败:-1
int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *src);
功能:写入多个连续寄存器(对应功能码为16)
参数:
ctx :Modbus实例
addr :寄存器地址
nb :寄存器的个数
src :多个寄存器的值
返回值:成功:0
失败:-1
void modbus_free(modbus_t *ctx)
功能:释放Modbus实例
参数:ctx:Modbus实例
void modbus_close(modbus_t *ctx)
功能:关闭套接字
参数:ctx:Modbus实例
练习:使用库函数实现03功能码
#include<modbus.h>
#include<modbus-tcp.h>
#include<stdio.h>
int main(int argc, char const *argv[])
{
if (argc != 2)
{
printf("usage:<ip>\n");
return -1;
}
//1.创建实例
modbus_t * ctx=modbus_new_tcp(argv[1],502);
//2.设置从机ID
modbus_set_slave(ctx,1);
//3.创建连接
modbus_connect(ctx);
//4.03功能码函数
uint16_t dest[8]={0};
modbus_read_registers(ctx, 0, 2, dest);
//5.打印
for (int i = 0; i < 2; i++)
{
printf("%d ",dest[i]);
}
putchar(10);
return 0;
}