目录
一.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 01uint8_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失败:-1void 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.设置从机IDmodbus_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;
}