目录
1》modbus分类
1> Modbus RTU
2> Modbus ASCLL
3> Modbus TCP
2》Modbus TCP的特点
3》Modbus TCP协议
1> 报文头(一共7个字节)
2> 寄存器
3> 功能码
4> 数据
01H 功能码分析
05H 功能码分析
0FH 功能码分析
1》modbus分类
1> Modbus RTU
运行在串口上的协议,采用二进制表现形式以及紧凑的数据结构,通信效率较高,应用比较广泛
2> Modbus ASCLL
运行在串口上的协议,采用ASCLL码进行传输,并且每个字节的开始和结束都有特殊字符作为标志,传输效率远远低于Modbus RTU,一般只有通讯量比较少的时候才会考虑它。注:在ASCII模式下,每个8位的字节被拆分成两个ASCII字符进行发送,比如十六进制0xAF(1010 1111),会被分解成ASCII字符“A”(0100 0001)和”F”(0100 0110)进行发送,其发送量显然比RTU增加一倍。
3> Modbus TCP
运行在以太网上的协议
2》Modbus TCP的特点
1> 主从应答式通信
2> Modbus TCP是应用层协议,基于传输层的TCP进行通信
注:更好的理解网络模型的分层特点:
各层之间独立,每一层不需要知道下一层如何实现
当任何一层发生变化时,只要层间接口关系保持不变,则这层以上或以下层不受影响。
3> Modbus TCP 端口号默认为502
3》Modbus TCP协议
Modbus TCP 协议包含三部分:报文头、功能码、数据
MBAP:Modbus Application Protocol (modbus报文头)
PDU:Protocol Data Unit(协议数据单元)
Modbus TCP/IP协议最大数据帧长度为260字节
1> 报文头(一共7个字节)
2> 寄存器
一共有四种类型的寄存器,分别是:线圈、离散量输入、输入寄存器、保持寄存器。
离散量和线圈其实就是位寄存器(每个寄存器数据占1字节),工业上主要用于控制IO设备。输入和保持寄存器是字寄存器(每个寄存器数据占2个字节),工业上主要用于存储工业设备的值。
1.离散量和线圈其实就是位寄存器(每个寄存器数据占1字节),工业上主要用于控制IO设备。
线圈寄存器,类比为开关量。 线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。
读单个或多个 写单个 写多个 3个功能码实现
离散输入寄存器,离散输入寄存器就相当于线圈寄存器的只读模式,他也是8个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。
对应功能码1个 读单个或多个
2. 输入和保持寄存器是字寄存器(每个寄存器数据占2个字节),工业上主要用于存储工业设备的值。
保持寄存器,这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。比如空调检测到的室温,这是不可以修改的,因为室温是根据实际的物理环境决定的。
功能码有对应的三个
输入寄存器,这个和保持寄存器类似,但是也是只支持读而不能写。一个寄存器也是占据两个byte的空间。类比我我通过读取输入寄存器获取现在的温度传感器的值
对应的功能码也就一个
3> 功能码
4> 数据
01H 功能码分析
读数据:
主机-》从机
报文头---功能码---起始地址---数量
从机-》主机
报文头---功能码---字节计数---数据
05H 功能码分析
写单个:
主机--》从机
报文头---功能码---地址---通断标志/数据
从机--》主机
原文返回
0FH 功能码分析
写多个:
主机-->从机
报文头---功能码---起始地址---数量---字节计数---数据
从机--->主机
报文头---功能码---起始地址---数量
练习
封装函数实现03,05功能码的作用
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
void set_slaveid(uint8_t *p, uint8_t id);
int sockfd;
void read_registers(uint8_t *p, uint16_t addr, uint16_t num, uint8_t *dest);
void write_coil(uint8_t *p, uint16_t addr, int op, uint8_t *dest);
int main(int argc, char const *argv[])
{
if (argc != 2)
{
printf("usage:<ip>\n");
return -1;
}
// 1.创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
printf("sockfd:%d\n", sockfd);
// 2.填充结构体
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(502);
saddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t addrlen = sizeof(saddr);
// 3.连接
if (connect(sockfd, (struct sockaddr *)&saddr, addrlen) < 0)
{
perror("connect err");
return -1;
}
// 创建数组
uint8_t req[32] = {0};
uint8_t date[128] = {0};
set_slaveid(req, 0x01);
read_registers(req, 0x0000, 0x0002, date);//03
for (int i = 0; i < date[8]; i++)
{
printf("%02x ", date[9 + i]);
}
putchar(10);
write_coil(req,0x0000,1,date);//05
printf("seq:%02x\n",date[1]);
return 0;
}
void set_slaveid(uint8_t *p, uint8_t id)
{
p[6] = id;
}
void read_registers(uint8_t *p, uint16_t addr, uint16_t num, uint8_t *dest)
{
p[5] = 0x06;
p[7] = 0x03;
p[9] = addr;
p[8] = addr >> 8;
p[11] = num;
p[10] = num >> 8;
send(sockfd, p, 12, 0);
recv(sockfd, dest, 128, 0);
}
void write_coil(uint8_t *p, uint16_t addr, int op, uint8_t *dest)
{
p[1]=0x01;
p[5] = 0x06;
p[7] = 0x05;
p[9] = addr;
p[8] = addr >> 8;
if (op == 1)
{
p[10] = 0xFF;
}
else
{
p[10] = 0x00;
}
p[11] = 0x00;
send(sockfd, p, 12, 0);
recv(sockfd, dest, 128, 0);
}
今天的分享就到这里结束啦,如果有哪里写的不好的地方,请指正。
如果觉得不错并且对你有帮助的话点个关注支持一下吧!