自学内容网 自学内容网

网络高级day01(Modbus 通信协议)

目录

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);
}

今天的分享就到这里结束啦,如果有哪里写的不好的地方,请指正。
如果觉得不错并且对你有帮助的话点个关注支持一下吧!


原文地址:https://blog.csdn.net/dghbs/article/details/142368016

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!