首页 > 其他分享 >modbus TCP

modbus TCP

时间:2024-07-22 20:56:43浏览次数:17  
标签:00 addr clientfd 0x00 TCP modbus 寄存器 include

目录

1.modbus 

1.1 分类

1.2 特点

2. modbus TCP

2.1 组成

2.2 报文头

3. 寄存器

4. 功能码

4.1 功能码代码

4.2 寄存器操作

5. 练习

练习一

练习二


1.modbus 

Modbus通信协议具有多个变种,其中有支持串口,以太网多个版本,其中最著名的是Modbus RTU、Modbus ASCII和Modbus TCP三种

1.1 分类

1) Modbus RTU:

运行在串口上的协议,采用二进制表现形式以及紧凑型数据结构,通信效率高,应用广泛。

2) Modbus ASCII:

运行在串口上的协议,采用ASCII码传输,并且利用特殊字符作为其字节的开始与结束标识,其传输效率要远远低于Modbus RTU协议,一般只有在通信数据量较小的情况下才考虑使用Modbus ASCII通信协议

3) Modbus TCP:

运行在以太网上的协议

1.2 特点

1)采用主从问答式通信

2)Modbus TCP是应用层协议,基于传输层TCP协议实现

3)Modbus TCP端口号默认 502

应用层协议实质是应用发送消息的规定格式。

 

2. modbus TCP

2.1 组成

ModbusTcp协议包含三部分:报文头、功能码、数据

 

2.2 报文头

包含7个字节,分别是:

事务处理标识符:主机发什么,从机回什么,没有限制

协议标识符:00 00 (十六进制) 占 2字节

长度:占两个字节,需要 4位 16进制来表示

单元标识符:从机 ID 一个字节

3. 寄存器

包含四种:离散量输入、线圈、输入寄存器、保持寄存器

1)离散量和线圈其实就是位寄存器(每个寄存器数据占1字节),工业上主要用于控制IO设备。

线圈寄存器,类比为开关量,每一个bit都对应一个信号的开关状态。 线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。

读功能可以分为读单个和读多个,但是通过一个功能码实现。

对应上面的功能码也就是:0x01 0x05 0x0f

离散输入寄存器,离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。

比如我读取外部按键的按下还是松开。对应功能码,读单个或多个。

2)输入和保持寄存器是字寄存器(每个寄存器数据占2个字节),工业上主要用于存储工业设备的值

保持寄存器,这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。比如我我设置时间年月日,不但可以写也可以读出来现在的时间。写功能分为单个写和多个写。读单个或多个。

所以功能码有对应的三个:0x03 0x06 0x10

输入寄存器,这个和保持寄存器类似,但是也是只支持读而不能写。一个寄存器也是占据两个byte的空间。类比我我通过读取输入寄存器获取现在的AD采集值

对应的功能码也就一个 0x04

总结:

读都是可以一个功能码读多个或单个的。而写是分为写单个和写多个需要两个功能码。

所以对于只读的寄存器都只有一个功能码,而可读可写的寄存器都有三个功能码。

4. 功能码

4.1 功能码代码

15(十进制)转换为十六进制:0x0f

16(十进制)转换为十六进制:0x10

寄存器PLC地址和寄存器的对应关系:

线圈: 00001-09999

离散量输入:10001-19999

输入寄存器:30001-39999

保持寄存器:40001-49999

http://实例分享 | ModbusTCP报文详解

4.2 寄存器操作

总结模板:

读数据:

        主机-->从机:报文头(7)+ 功能码(1)+ 起始地址(2)+ 数量(2)

        从机-->主机:报文头(7)+ 功能码(1)+字节计数(1)+数据(?)

写单个:

        主机-->从机:报文头(7)+ 功能码(1)+ 地址(2)+ 断通标志或数据(2)

写多个:

        主机-->从机:报文头(7)+功能码(1)+起始地址(2)+数量(2) +字节计数(1) +数据(?)

例如1:

主机->从机:02 25 00 00 00 06 01 03 00 63 00 02

02 25 :事务处理标识符

00 00 :协议标识符

00 06: 字节长度

03:功能码

00 63:起始地址 0063 ---》 0~99 ----》 40100

00 02 : 数量

也就是读两个保持寄存器,40100 与 40101

从机->主机:02 25 00 00 00 07 01 03 04 32 13 30 08

02 25 :事务处理标识符

00 00 :协议标识符

00 07 :字节长度

03:功能码

04:返回的字节计数

32 13:40100 的数值

30 08:40101 的数值

例2. 控制IO设备开关的协议数据,操作1个线圈,置1。

主机:0000 0000 0006 01 05 0022 FF00

事务标识符:00 00

协议标识符:00 00

字节数量:00 06   六个字节

功能码:05 写线圈

起始地址:00 22

通断标识:FF 00

5. 练习

练习一

实现03功能,读单个或多个保持寄存器

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{

    // 1.创建流式套接字
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd < 0)
    {
        perror("clientfd socket error");
        return -1;
    }
    printf("client socket scuess\n");

    // 2.指定服务器的网络信息    struct sockaddr_in
    // 定义结构体变量 server_addr
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;                 // ipv4族
    server_addr.sin_addr.s_addr = inet_addr(argv[1]); // ip地址
    server_addr.sin_port = htons(atoi(argv[2]));      // 端口

    // 3.请求链接服务器   connect()
    socklen_t addrlen = sizeof(server_addr); // 长度

    // 客户端的socket连接服务器
    int ret = connect(clientfd, (struct sockaddr *)&server_addr, addrlen);
    if (ret < 0)
    {
        perror("connect err");
        return -1;
    }
    printf("connect scuess\n");

    // 发送03命令
    // 读取0000地址开始的三个寄存器的值
    uint8_t buf[32] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x05};
    ret = send(clientfd, buf, sizeof(buf), 0);
    if (ret < 0)
    {
        perror("send error");
        return -1;
    }

    recv(clientfd, buf, sizeof(buf), 0);
    for (int i = 0; i < buf[8]; i++)
    {
        printf("%02x ", buf[9 + i]);
    }
    printf("\n");

    uint8_t buf2[32] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x10, 0x00, 0x00, 0x00, 0x05};

    close(clientfd);

    return 0;
}

练习二

封装函数,实现03、05功能

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

int clientfd;

// 初始化服务器
void set_server_id(struct sockaddr_in *p)
{
    char buf1[32] = {0};
    char buf2[32] = {0};
    printf("请输入ip地址: ");
    scanf("%s", buf1);
    printf("请输入端口号: ");
    scanf("%s", buf2);

    p->sin_family = AF_INET;              // ipv4族
    p->sin_addr.s_addr = inet_addr(buf1); // ip地址
    p->sin_port = htons(atoi(buf2));      // 端口
}

// 读保持寄存器
// *p:功能码   addr 起始地址   num 读取的个数
void read_registers(uint8_t id, uint8_t g, uint16_t addr, uint16_t num)
{
    int res;
    uint8_t data[128] = {0};
    uint8_t buf[32] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06, id, g, addr >> 8, addr, num >> 8, num};
    for (int i = 0; i < 12; i++)
    {
        printf("%02x ", buf[i]);
    }
    printf("\n");

    res = send(clientfd, buf, 12, 0);
    if (res < 0)
    {
        perror("send read error");
        return;
    }

    res = recv(clientfd, data, sizeof(data), 0);
    if (res < 0)
    {
        perror("recv read error");
        return;
    }

    for (int i = 0; i < data[8]; i++)
    {
        printf("%02x ", data[9 + i]);
    }
    printf("\n");
}

// 写线圈寄存器
// id: 从机id   g: 功能码   addr: 起始地址   op:1,0
void write_coil(uint8_t id, uint8_t g, uint16_t addr, int op)
{
    int res;
    uint8_t data[128] = {0};
    uint8_t buf[32] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06, id, g, addr >> 8, addr};
    if (op == 1)
    {
        buf[10] = 0xFF;
    }
    else if (op == 0)
    {
        buf[10] = 0x00;
    }
    buf[11] = 0x00;

    for (int i = 0; i < 12; i++)
    {
        printf("%02x ", buf[i]);
    }
    printf("\n");

    res = send(clientfd, buf, sizeof(buf), 0);
    if (res < 0)
    {
        perror("send write error");
        return;
    }

    res = recv(clientfd, data, sizeof(data), 0);
    if (res < 0)
    {
        perror("recv write error");
        return;
    }

    for (int i = 0; i < 12; i++)
    {
        printf("%02x ", data[i]);
    }
    printf("\n");
}

int main(int argc, char const *argv[])
{
    // int clientfd;
    // 1.创建流式套接字
    clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd < 0)
    {
        perror("clientfd socket error");
        return -1;
    }
    printf("client socket scuess\n");

    // 2.指定服务器的网络信息    struct sockaddr_in
    // 定义结构体变量 server_addr
    struct sockaddr_in server_addr;
    set_server_id(&server_addr);

    // 3.请求链接服务器   connect()
    socklen_t addrlen = sizeof(server_addr); // 长度

    // 客户端的socket连接服务器
    int ret = connect(clientfd, (struct sockaddr *)&server_addr, addrlen);
    if (ret < 0)
    {
        perror("connect err");
        return -1;
    }
    printf("connect scuess\n");

    // 发送命令
    int id;
    printf("请输入从机id: ");
    scanf("%x", &id);

    read_registers(id, 0x03, 0x0000, 0x0002);
    write_coil(id, 0x05, 0x0000, 1);

    close(clientfd);
    return 0;
}

标签:00,addr,clientfd,0x00,TCP,modbus,寄存器,include
From: https://blog.csdn.net/weixin_67273669/article/details/140618943

相关文章

  • TCP协议之三次握手,四次挥手!
    VLSM:可变长子网掩码划分DOS攻击:拒绝服务攻击SYN:DDOS攻击(分布式拒绝服务攻击)私有IP严禁进行子网掩码划分TCP协议:三次握手:第一次握手:Client将标志位SYN置1,随机产生一个值seq=J,并将该数据包发给Server,Client进入SYN-SENT状态,等待Server确认。第二次握手:Server收到数据包......
  • 【tcpreplay】录包并回放(有待研究)
    一、场景   由于测试过程想对生产环境的数据进行录制。并回放给测试服务,所以进行尝试 二、工具   tcpreplay    三、录制数据包sudotcpdump-ienp4s0dstport8000-whttp.pcap-v  四、tcpreplay-edit进行回放sudotcpreplay-edit--unique-ip......
  • 网络基础 Modbus协议学习总结
    协议简介Modbus协议,首先从字面理解它包括Mod和Bus两部分,首先它是一种bus,即总线协议,总线就意味着有主机,有从机,这些设备在同一条总线上。Modbus支持单主机,多个从机,最多支持247个从机设备。关于Mod,因为这种协议最早被用在PLC控制器中,准确的说是Modicon公司的PLC控制器,这也是Modbus......
  • 1.8TCP的3次握手和4次挥手
    1.8TCP的3次握手4次挥手TCP协议非常重要,这里把它的连接和释放整理一下。首先是三次握手:1、客户端发起,像服务器发送的报文SYN=1,ACK=0,然后选择了一个初始序号:seq=x。SYN是干什么用的?在链接的时候创建一个同步序号,当SYN=1同时ACK=0的时候,表明这是一个连接请求的报文段。......
  • TCP协议
    TCP协议-----传输控制协议        一种面向连接的可靠传输协议。        TCP协议建立的连接是双向连接。面向连接:在数据传输之前,收发双方需要预先建立一条逻辑通路。无面向连接。TCP分段:因为IP分片后,TCP协议无法保证数据的......
  • TCP协议详解
    TCP协议是一种传输控制协议,一种面向连接的可靠传输协议。TCP协议建立的链接是双向连接。双向连接:在数据传输之前,收发双方需要预先建立一条逻辑通路。无面向连接。序列号SN确认序列号AN6位标志位(URG、ACK、PSH、RST、SYN、FIN)URG---告诉对方有紧急数据......
  • dpvs 调整tcp mss
    修改tcpoptions中mss值src/ipvs/ip_vs_proto_tcp.c因为tcp头部options中不同kind顺序是随机的,所以需要遍历找到kind值是mss2和length值是4,再修改后面的mssvalue。staticvoidtcp_out_adjust_mss(intaf,structtcphdr*tcph){unsignedchar*ptr;intlength;......
  • STM32被拔网线 LWIP的TCP无法重连解决方案
    目录一、问题描述二、项目构成三、问题解决1.问题代码2.解决思路3.核心代码: 四、完整代码1.监测网口插入拔出任务2.TCP任务3.创建tcp任务4.删除tcp任务五、总结一、问题描述最近遇到一个问题,就是我的stm32设备作为tcp客户端和上位机交互,如果在连接过程中网线......
  • keepalived绑定单播地址、非抢占模式及LVS的TCP模式的高可用【转】
    背景:keepalived默认是组播地址进行播放,且默认地址是224.0.0.18,如果配置多个keepalived主机,会导致虚拟IP地址存在冲突问题,这种问题怎么解决呢?解决办法:就是将keepalived主机的多播地址修改为单播地址,绑定固定IP地址,避免在多播模式下,通过VRRP进行广播地址,造成IP地址地址冲突。vrrp_......
  • socket 收发TCP/UDP
    一、c++个人测试记录,有问题还请指出,谢谢参考:C++开发基础之网络编程WinSock库使用详解TCP/UDPSocket开发_c++udp使用什么库-CSDN博客代码中Logger测试见文章: c++中spdlog的使用/python中logger的使用-CSDN博客1、main.cpp收发TCP信号:#include<iostream>#include<thr......