精华内容
下载资源
问答
  • linux tcpmodbus服务端

    2018-07-25 09:49:48
    linux tcpmodbus服务端 输入寄存器(0x04)离散输入寄存器(0x02)保持寄存器(0x03(读) 0x06(单个写) 0x10(多个写))
  • linux tcpmodbus客户端

    2018-07-25 13:33:50
    linux tcpmodbus客户端 输入寄存器(0x04)离散输入寄存器(0x02)保持寄存器(0x03(读) 0x06(单个写) 0x10(多个写))
  • arm9 + linux modbus tcp 运行文件,可用于测试
  • linux 平台, modbus tcp 协议的简单实现
  • qt_modbusTCP_demo.rar

    2021-05-26 16:50:51
    Linux下qt实现C++和PLC通过modbusTCP通讯,读写线圈、存储寄存器。
  • /****LinuxmodbusTCP测试程序****/ #include<stdio.h> #include<stdlib.h> #include"modbus.h" int main(void) { modbus_t *mb; uint16_t tab_reg[32]={0}; mb = modbus_new_tcp("192.168....
    /****Linux下modbusTCP测试程序****/
    
    #include<stdio.h>
    #include<stdlib.h>
    #include"modbus.h"
     
    int main(void)
    {
      modbus_t *mb;
      uint16_t tab_reg[32]={0};
     
      mb = modbus_new_tcp("192.168.1.222",601);//由于是tcp client 连接,在同一程序中相同的端口可以连接多次
      modbus_set_slave(mb,1);//从机地址
      modbus_connect(mb);
     
      struct timeval t;
      t.tv_sec=0;
      t.tv_usec=1000000;  //设置modbus超时时间为1000毫秒  
      modbus_set_response_timeout(mb,&t);
     
      int regs=modbus_read_registers(mb,0,20,tab_reg);
     
      regs=modbus_write_register(mb,0x00,94);
     
      printf("%d %d %d %d %d %d %d %d\n",regs,tab_reg[0],tab_reg[1],tab_reg[2],tab_reg[3],tab_reg[4],tab_reg[5],tab_reg[6],tab_reg[7]);
     
      modbus_close(mb);
      modbus_free(mb);
      return 0;
    }
    /****Linux下modbusRTU测试程序****/
    
    #include<stdio.h>
    #include<stdlib.h>
    #include"modbus.h"
     
    int main(void)
    {
      modbus_t *mb;
      uint16_t tab_reg[32]={0};
      mb = modbus_new_rtu("/dev/ttySAC2",19200,'N',8,1);//open port
      modbus_set_slave(mb,1);//set slave address
     
      modbus_connect(mb);
     
      struct timeval t;
      t.tv_sec=0;
      t.tv_usec=1000000;//set modbus time 1000ms
      modbus_set_response_timeout(mb,&t);
     
      int regs=modbus_read_registers(mb, 0, 20, tab_reg); 
     
      printf("%d %d %d %d %d\n",regs,tab_reg[0],tab_reg[1],tab_reg[2],tab_reg[3]);
      modbus_close(mb);  
      modbus_free(mb);
      return 0;
    }
    交叉编译链接命令:arm-linux-gcc -o modbus_rtu_test modbus_rtu_test.c -L. -lmodbus ;注意libmodbus.so与modbus_rtu_test.c应当放在同一文件夹下。

     

    展开全文
  • 环境 win7 Anaconda2一、安装pyserial和modbus-tk: C:\Users\admin>cd C:\Anaconda2C:\Anaconda2>easy_install pyserial Searching for pyserial Best match: pyserial 3.4 Processing pyserial-3.4-py2.7.egg ...

    环境 win7 Anaconda2

    一、安装pyserial和modbus-tk:
    C:\Users\admin>cd C:\Anaconda2

    C:\Anaconda2>easy_install pyserial
    Searching for pyserial
    Best match: pyserial 3.4
    Processing pyserial-3.4-py2.7.egg
    pyserial 3.4 is already the active version in easy-install.pth
    Installing miniterm.py script to C:\Anaconda2\Scripts

    Using c:\anaconda2\lib\site-packages\pyserial-3.4-py2.7.egg
    Processing dependencies for pyserial
    Finished processing dependencies for pyserial

    C:\Anaconda2>python
    Python 2.7.12 |Anaconda 4.2.0 (64-bit)| (default, Jun 29 2016, 11:07:13) [MSC v.1500 64 bit (AMD64)] on win32
    Type “help”, “copyright”, “credits” or “license” for more information.
    Anaconda is brought to you by Continuum Analytics.
    Please check out: http://continuum.io/thanks and https://anaconda.org

    import serial

    C:\Anaconda2>easy_install modbus-tk
    Searching for modbus-tk
    Reading https://pypi.python.org/simple/modbus-tk/
    Downloading https://pypi.python.org/packages/80/71/3d8a6596cd65670d4d2beee262d5964deb933614fd7f58a739d5aa0f0332/modb
    us_tk-0.5.7.tar.gz#md5=74c02b9b57dc32913da52d020eaf11d3
    Best match: modbus-tk 0.5.7
    Processing modbus_tk-0.5.7.tar.gz
    Writing c:\users\admin\appdata\local\temp\easy_install-z7i_tm\modbus_tk-0.5.7\setup.cfg
    Running modbus_tk-0.5.7\setup.py -q bdist_egg –dist-dir c:\users\admin\appdata\local\temp\easy_install-z7i_tm\modbu
    s_tk-0.5.7\egg-dist-tmp-4bmk1v
    zip_safe flag not set; analyzing archive contents…
    Moving modbus_tk-0.5.7-py2.7.egg to c:\anaconda2\lib\site-packages
    Adding modbus-tk 0.5.7 to easy-install.pth file
    Installed c:\anaconda2\lib\site-packages\modbus_tk-0.5.7-py2.7.egg
    Processing dependencies for modbus-tk
    Finished processing dependencies for modbus-tk

    C:\Anaconda2>python
    Python 2.7.12 |Anaconda 4.2.0 (64-bit)| (default, Jun 29 2016, 11:07:13) [MSC v.1500 64 bit (AMD64)] on win32
    Type “help”, “copyright”, “credits” or “license” for more information.
    Anaconda is brought to you by Continuum Analytics.
    Please check out: http://continuum.io/thanks and https://anaconda.org

    import modbus_tk

    二、
    lwip-win32-msvc-0.1
    已经解决的问题:
    一:下载lwip的源码,在windows上重新组织文件架构,然后进行编译。刚好美国已经有人做了这样面的工作,所以

    就先用一下咯。毕竟一开始要做这方面的工作,还是有难度的。可以下载lwip-win32-msvc-0.1.zip文件。

    网络上有很多这样的下载连接,但是都不好用(动不动就要你去注册,花了时间注册好了本以为可以下载了,没想到下载时候提示,积分不够,需要积分可以啊,买卡充值什么的,烦哪。)。官方网也连接不上,后来发现在sina爱问里面的共享里面可以下载,很方便的哦。

    下载完了之后解压就可以用了。用vc6打开就可以了。里面有两个工程,一个是Lwip4工程,编译后是lib库,供上层应用程序使用;另一个工程是test,是测试程序。分别编译,lwip4可以顺利的编译通过,但是test工程编译的时候会遇到问题:

    (1)找不到packet32.h文件。解决的办法是到官网http://www.winpcap.org/devel.htm上下载winpcap的开发包Download WinPcap 4.0.2 Developer’s Pack,连接是http://www.winpcap.org/install/bin/WpdPack_4_0_2.zip 。下载之后解压即能看到一系列的目录,从include目录下就能找到packet32.h,以及packet32.h文件里include的devioctl.h,一起拷贝过来到工程目录里面。另外还要到Lib文件夹中拷贝packet32.h的实现lib文件packet.lib,然后加到工程中来。问题就迎刃而解了。

    (2)typedef struct npf_if_addr {
    struct sockaddr_storage IPAddress; ///< IP address.
    struct sockaddr_storage SubnetMask; ///< Netmask for that address.
    struct sockaddr_storage Broadcast; ///< Broadcast address.
    }npf_if_addr;

    会报packet32.h中上述结构体中的sockaddr_storage未定义:error C2079: ‘IPAddress’ uses undefined struct ‘sockaddr_storage

    解决这个问题,可以自己在packet32.h中定义该结构体:

    ifndef _SS_PAD1SIZE

    struct sockaddr_storage {
    u_char sa_len;
    u_char sa_family;
    u_char padding[128];
    };

    endif

    或者sockaddr_storage 改成 sockaddr 也可以解决问题。

    (3)pktif.c(191) : error C2065: ‘OID_802_3_PERMANENT_ADDRESS’ : undeclared identifier

    pktif.c(199) : error C2065: ‘NDIS_PACKET_TYPE_ALL_LOCAL’ : undeclared identifier

    报上述两个错误,是因为这两个变量是在Ntddndis.h文件中定义的,该头文件也是winpcap开发包中的文件,所以如同(1)需要从开发包inlude目录下把该文件拷贝到工程里来,同时在pktif.c文件头加上#include

    if LWIP_COMPAT_SOCKETS

    define accept(a,b,c) lwip_accept(a,b,c)

    define bind(a,b,c) lwip_bind(a,b,c)

    define shutdown(a,b) lwip_shutdown(a,b)

    define closesocket(s) lwip_close(s)

    define connect(a,b,c) lwip_connect(a,b,c)

    define getsockname(a,b,c) lwip_getsockname(a,b,c)

    define getpeername(a,b,c) lwip_getpeername(a,b,c)

    define setsockopt(a,b,c,d,e) lwip_setsockopt(a,b,c,d,e)

    define getsockopt(a,b,c,d,e) lwip_getsockopt(a,b,c,d,e)

    define listen(a,b) lwip_listen(a,b)

    define recv(a,b,c,d) lwip_recv(a,b,c,d)

    define recvfrom(a,b,c,d,e,f) lwip_recvfrom(a,b,c,d,e,f)

    define send(a,b,c,d) lwip_send(a,b,c,d)

    define sendto(a,b,c,d,e,f) lwip_sendto(a,b,c,d,e,f)

    define socket(a,b,c) lwip_socket(a,b,c)

    define select(a,b,c,d,e) lwip_select(a,b,c,d,e)

    define ioctlsocket(a,b,c) lwip_ioctl(a,b,c)

    if LWIP_POSIX_SOCKETS_IO_NAMES

    define read(a,b,c) lwip_read(a,b,c)

    define write(a,b,c) lwip_write(a,b,c)

    define close(s) lwip_close(s)

    define fcntl(a,b,c) lwip_fcntl(a,b,c)

    endif /* LWIP_POSIX_SOCKETS_IO_NAMES */

    endif /* LWIP_COMPAT_SOCKETS */

    int socket(int domain, int type, int protocol);
    服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket。
    domain:协议族,常用的有AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE其中AF_INET代表使用ipv4地址
    type:socket类型,常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等
    protocol:协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    把一个地址族中的特定地址赋给socket
    sockfd:socket描述字,也就是socket引用
    addr:要绑定给sockfd的协议地址
    addrlen:地址的长度
    通常服务器在启动的时候都会绑定一个地址(如ip地址+端口号),用于提供服务。有些端口号是约定俗成的不能乱用,如80用作http,502用作modbus。

    int listen(int sockfd, int backlog);
    监听socket
    sockfd:要监听的socket描述字
    backlog:相应socket可以排队的最大连接个数

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    连接某个socket
    sockfd:客户端的socket描述字
    addr:服务器的socket地址
    addrlen:socket地址的长度

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    TCP服务器监听到客户端请求之后,调用accept()函数取接收请求
    sockfd:服务器的socket描述字
    addr:客户端的socket地址
    addrlen:socket地址的长度

    size_t read(int fd, void *buf, size_t count);
    读取socket内容
    fd:socket描述字
    buf:缓冲区
    count:缓冲区长度

    size_t write(int fd, const void *buf, size_t count);
    向socket写入内容,其实就是发送内容
    fd:socket描述字
    buf:缓冲区
    count:缓冲区长度

    int close(int fd);
    socket标记为以关闭 ,使相应socket描述字的引用计数-1,当引用计数为0的时候,触发TCP客户端向服务器发送终止连接请求。

    1. 使用socket创建嵌入式WebServer
       要使用socket的前提是已经做好lwip和rtos的移植,如果低层驱动移植完毕,就可以使用socket快速创建应用。
      本例是一个简单的WebServer。

    const unsigned char htmldata[] = “\
    \
    LWIP\

    A WebServer Based on LwIP v1.4.1 Hello world!

    \
    “;
    const unsigned char errhtml[] = “\
    \
    \
    Error!\
    \
    \

    404 - Page not found

    \
    \
    “;

    /**
    * @brief serve tcp connection
    * @param conn: connection socket
    * @retval None
    */
    void http_server(int conn)
    {
    int buflen = 1500;
    int ret;
    unsigned char recv_buffer[1500];

    /* Read in the request */
    ret = read(conn, recv_buffer, buflen);
    if(ret <= 0)
    {
    close(conn);
    Printf(“read failed\r\n”);
    return;
    }

    Printf("http server response!\r\n");
    if(strncmp((char *)recv_buffer, "GET /lwip", 9) == 0)
    {
        write(conn, htmldata, sizeof(htmldata)-1);
    }
    else
    {
        write(conn, errhtml, sizeof(errhtml)-1);
    }
    /* Close connection socket */
    close(conn);
    

    }

    /**
    * @brief http_task
    * @param arg: pointer on argument(not used here)
    * @retval None
    */
    static void http_task(void *arg)
    {
    int sock, newconn, size;
    struct sockaddr_in address, remotehost;

    /* create a TCP socket */
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
    Printf(“can not create socket”);
    return;
    }

    /* bind to port 80 at any interface */
    address.sin_family = AF_INET;
    address.sin_port = htons(80);
    address.sin_addr.s_addr = INADDR_ANY;
    if (bind(sock, (struct sockaddr *)&address, sizeof (address)) < 0)
    {
    Printf(“can not bind socket”);
    close(sock);
    return;
    }

    /* listen for connections (TCP listen backlog = 1) */
    listen(sock, 1);
    size = sizeof(remotehost);
    while (1)
    {
    newconn = accept(sock, (struct sockaddr )&remotehost, (socklen_t )&size);
    if (newconn >= 0)
    {
    http_server(newconn);
    }
    else
    {
    close(newconn);
    }
    }
    }

    /****************************************************
    * void http_task_init(void)
    *
    * This function initializes the service.
    ****************************************************/
    void http_task_init(void)
    {
    sys_thread_new( CHARGEN_THREAD_NAME, http_task, 0, 0, TCPIP_THREAD_PRIO+1); //函数栈在移植sys_thread_new中实现
    }

    1. 使用socket创建Modbus TCP应用
      Modbus TCP在网络传输层次,就是一串有特定含义的数据包的交互,LwIP层次并不识别是什么数据。所以从这个角度来讲,Modbus TCP移植和其他TCP应用的移植没有任何差别。
      透过表面看本质,只有拨开外层重重包装看本质,我们才能从纷杂的事件中找到问题的重点,然后剥离不相关的部分,一次解决一个问题。基于这个思想,Modbus TCP应用可以直接划分为2个层次,底层是驱动部分,负责一包数据从网络上接收上来或发送出去,上层是Modbus的协议部分,就是Modbus寄存器的操作等。从这个角度来说,只要数据传输正确了,那么怎么处理就是另一个问题了,比如可以共用Modbus RS485的代码等。
      下面测试了Modbus TCP的数据传输。
      Modbus TCP设计有几个重要的点:
        1)Modbus是连续通信,不能和http一样完成一次连接后就断开,所以要不停的read,当读出错时在close(conn)关闭连接。
        2)Modbus可能存在通信失败情况,需要关闭socket后再重新建立socket。
        3)Modbus作为工业协议,应用场景下一般不会多个客户端连接一台机器,并且多个客户端连接一台机器,寄存器的读写互斥会是一个大问题,所以常见的做法是一旦连接成功,就关闭socket禁止其他连接进来。客户端主动断开后再重新建立socket然后进入listen状态。

    /**
    * @brief serve modbus_tcp connection
    * @param conn: connection socket
    * @retval None
    */
    void modbus_tcp_server(int conn)
    {
    int buflen = 1500;
    int ret;
    unsigned char recv_buffer[1500];
    int i;

    Printf(“start modbus tcp\r\n”);
    ret = read(conn, recv_buffer, buflen);
    while ( ret > 0 )
    {
    ret = read(conn, recv_buffer, buflen);
    Printf(“\r\n>:”); // debug print
    for(i=0; i

    展开全文
  • 该资源包整合了多个C/C++的modbustcp开发源程序,为你的开发提供一定思路
  • 之前有朋友反应modbusTcp代码中,变量名命名不规范,导致阅读困难。这个是修改版本。 下载了之前版本的朋友,私我免积分拿资料哈。谢谢大家交流!!
  • Freemodbus TCP协议栈使用笔记

    千次阅读 2018-11-19 18:07:40
    1.用命令行编译出modbus TCP的可执行文件 2.用eclipse编译出modbus TCP的可执行文件 本文待办内容: 1.测试编译出的可执行文件。 2、将这个协议栈移植到ARM上,用迅为的ITOP4412或者米尔的IMX6UL板子,做好了和...

    本文进行中,未完结。

    Freemodbus这个协议栈是比较有名的modbus开源协议站,支持在很多平台上运行。

     

    modbus TCP是最容易移植的demo,因为底层是socket,只要支持linux运行即可,修改的地方非常少。

     

    本文内容:

    1.用命令行编译出modbus TCP的可执行文件

    2.用eclipse编译出modbus TCP的可执行文件

    3.使用协议栈程序

     

     

    需要的基础:modbus协议基础,先自己学会使用,然后再来看协议栈,直接看协议栈难度太高;

    建议使用modsan和modslave这两款软件,再搭建虚拟机,两者之间通信。一主一从先玩会了再看协议栈。

     

    开发环境:

    gcc version 4.7.4 (Ubuntu/Linaro 4.7.4-3ubuntu12) 

    Ubuntu 16.04

     

    一、源代码结构分析

    官方的代码库https://github.com/cwalter-at/freemodbus.git

    从git获取代码后,进入目录下可以看到

    root@yjp-pc:/GIT_workplace/freemodbus_yjp# ls
    bsd.txt  Changelog.txt  demo  doc  gpl.txt  lgpl.txt  modbus  tools
    


    我们只需要demo和modbus即可。

    进入demo后,可以看到各个平台的支持文件,我们也只需要LINUXTCP即可,初学者看的乱的话,可以将其余的文件夹删掉

    root@yjp-pc:/GIT_workplace/freemodbus_yjp/demo# ls
    AT91SAM7X_ROWLEY  ATSAM3S  ATSAM3S_FREERTOS  AVR  BARE  HCS08  LINUX  LINUXTCP  LPC214X  MCF5235  MCF5235CW  MCF5235TCP  MSP430  STR71X  STR71XGCC  STR71XTCP  WIN32  WIN32TCP  Z8ENCORE
    

    modbus路径下的,ascii和rtu,本次也用不到,初学者可以删去。

    二、配置编译modbus TCP

    找到文件/modbus/include/mbconfig.h,修改如下,简单来讲就是关掉ASCII和RTU功能,开启TCP。

    三、修改makefile

    经过上述两个修改,进入demo/LINUXTCP下应该就可以直接make出来了,但是在ubuntu 16.04下还是会报错

    undefined reference to `pthread_create'

    查了原因是因为-lpthread在ubuntu中编译选项要放到最后,所以做在demo/LINUXTCP对makefile进行修改如下

    这个问题参考了大神的博客

    https://blog.csdn.net/dyzhen/article/details/79058554

    在freemodbus的文档中有讲到,但是我找不到在那儿了,说的是官方在redhat xx版本上验证的,所以我们要在ubuntu运行,需要修改一下。

    完成上述,就可以编译出tcpmodbus文件了,运行即可。

    root@yjp-pc:/GIT_workplace/freemodbus_yjp/demo/LINUXTCP# ls
    demo.c  demo.o  Makefile  port  tcpmodbus
    root@yjp-pc:/GIT_workplace/freemodbus_yjp/demo/LINUXTCP# ./tcpmodbus 
    Type 'q' for quit or 'h' for help!
    > 
    > 
    > 
    > h
    FreeModbus demo application help:
      'd' ... disable protocol stack.
      'e' ... enabled the protocol stack
      's' ... show current status
      'q' ... quit applicationr
      'h' ... this information
    
    Copyright 2007 Steven Guo <gotop167@163.com>
    > 
    

    四、eclipse编译

    我个人是比较喜欢图形界面编译,将精力集中于代码逻辑上,而不要浪费时间(哪怕一秒)在学习记忆指令和格式上。

    高手喜欢指令的请跳过本节

    我用的版本是eclipse oxygen

    1.分析demo/LINUXTCP下的makefile文件

    对于LINUXTCP需要的源文件,这里只有c文件

    CSRC        = demo.c port/portother.c \
                  port/portevent.c port/porttcp.c \
                  ../../modbus/mb.c ../../modbus/tcp/mbtcp.c \
                  ../../modbus/functions/mbfunccoils.c \
                  ../../modbus/functions/mbfuncdiag.c \
                  ../../modbus/functions/mbfuncholding.c \
                  ../../modbus/functions/mbfuncinput.c \
                  ../../modbus/functions/mbfuncother.c \
                  ../../modbus/functions/mbfuncdisc.c \
                  ../../modbus/functions/mbutils.c 

    eclipse配置如图

    删除了modbus文件夹下的ascii和rut,并把其放到了linuxtcp目录下,这个是我个人习惯,随意放置即可。

    确保makefile中的c文件,全部加入了eclipse项目中。

    上述的config修改也请确认修改完毕

    然后编译,缺库问题可以见招拆招,本地库的路径很好找,也可以直接按我的来

    pthread问题解决

    到此即可编出可执行文件了,如果是通用库的问题,请百度,因为我没有遇到所以也没法写,可以先自己写一个hello和socket test,确保你的环境库,eclipse没问题,再来搞modbus TCP

    ------------------------------------------------------------------------------------------------------------------------------------------------------------

    如何使用LinuxTCP

     

    修改这个宏定义为自己的IP地址        #define    INADDR_ANY    

    下述这两个全局数组,是modbus 3X 4X两个区的寄存器存储,自行修改为测试数据即可。

    static USHORT   usRegInputBuf    [REG_INPUT_NREGS];

    static USHORT   usRegHoldingBuf    [REG_HOLDING_NREGS];

    REG_INPUT_START      3x区的起始地址

    REG_HOLDING_START     4x区的起始地址

     

    切记!!!!一定先用modscan 和modslave这两个软件自行对测一下,熟悉一个modbus 从站,需要配置什么,然后再来看代码。

    代码和modslave需要配置的地方都一样:从站IP地址,端口502写死的,3x4x区数据地址,数据内容即可。TCP的协议栈目前不支持0X1X区,其他demo支持,后面有时间我研究一下如何移植。

     

     

     

     

    展开全文
  • modbus tcp驱动

    2012-08-02 10:34:27
    linux系统上modbus tcp驱动的开发,实现读线圈、读离散输入、读输入寄存器、写线圈、写单一寄存器等操作。实现shell命令行交互,可作为modbus tcp学习的demo。
  • modbus tcp

    2018-01-10 13:07:44
    基于linux 操作系统 modbus tcp 测试源码。基于tcpmodbus
  • 这个是modbusTcp主站的代码框架,之前朋友反应变量命名不规范导致阅读困难,我对此做了修复。 之前下载了旧版本的朋友,私聊我免积分拿这个代码哈。谢谢大家交流!!!
  • QT下的Modbus TCP 通讯

    千次阅读 多人点赞 2020-08-24 17:51:59
    文章目录前言一、关于Modbus二、Modbus TCP Master的实现1.封装自己的Modbus类2.Modbus 通过TCP/IP进行连接3.Modbus 通过TCP/IP读取数据(1)读取线圈数据(2)读取保持寄存器数据(3)给线圈写入数据(4)给保持...


    前言

    Modbus在工业控制中的应用非常多,由于其免费使用加上一定的历史环境,Modbus在PLC上的通讯应用非常多,本文主要介绍Mosbus TCP master(主站)的实现。


    一、关于Modbus

    Modbus是由MODICON公司开发的一种工业现场总线协议标准,随后施耐德推出了基于TCP/IP的MOdbus协议:Modbus tcp;

    Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。

    标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口,采用master/slave方式通信。

    ####################################################
    Modbus有4种操作对象:线圈、离散输入、输入寄存器、保持寄存器
    Coils、DiscreteInputs、InputRegisters、HoldingRegisters
    线圈:PLC的输出位,开关量,在MOdbus中可读可写;
    离散输入:PLC的输入位,开关量,在Modbus中只读;
    输入寄存器:PLC中只能从模拟量输入端改变的寄存器,在MODBUS中只读
    保持寄存器:PLC中用于输出模拟量信号的寄存器,在MODBUS中可读可写

    由于是基于QT去实现的ModbusTCP通讯,所以对Modbus的功能码不需要做过多的掌握,了解即可。


    二、Modbus TCP Master的实现

    //主站的实现,一般都是上位机做主站,PLC做从站
    

    1.封装自己的Modbus类

    QT.pro文件中添加 serialbus 模块:
    QT += core gui sql serialbus


    让自定义类继承QObject,在头文件中添加相应的头文件

    QModbusTcpClient 类 和 QModbusDataUnit 类


    在自定义类中创建modbus TCP client 对象指针。
    QModbusTcpClient *My_client;


    2.Modbus 通过TCP/IP进行连接

    自定义类的构造函数中实例化Modbus tcp对象:

    My_client = new QModbusTcpClient();
    

    Modbus TCP/IP协议进行连接的时候需要通过IP + Port ;
    //端口号一般用502

    /********************************************
     * 函数名称:Connect_to_modbus(QString IP_address,int Port)
     * 功能:连接到modbus设备
     * 工作方式:
     * 参数:
            参数1:modbus设备的IP地址               QString 类型
            参数2:modbus设备的端口号(一般用502)     int 类型
     * 返回值:成功返回true,失败返回fasle。
     * 备注:
     * 修改记录
    *********************************************/
    bool My_modbus_tcp::Connect_to_modbus(QString IP_address,int Port)
    {
        if(!My_client){
            return false;
        }
    
        if (My_client->state() != QModbusDevice::ConnectedState) {       //判断当前连接状态是否为断开状态
    
            //配置modbus tcp的连接参数 IP + Port   modbus协议的端口号为502
            My_client->setConnectionParameter(QModbusDevice::NetworkAddressParameter,IP_address);
            My_client->setConnectionParameter(QModbusDevice::NetworkPortParameter,Port);
    
            if (!My_client->connectDevice()) {
                qDebug()<< "连接modbus设备失败";
                return false;
            }
            else {
                qDebug()<< "成功连接到modbs设备";
                return true;
            }
        }
    
        else {
            My_client->disconnectDevice();
            return false;
        }
    
    }
    

    上述代码qDebug()<< "成功连接到modbs设备"不太合理,判断连接成功其实应该以Modbus的连接状态来进行判断的。
    连接状态参考帮助手册 enum QModbusDevice::State:
    有UnconnectedState、ConnectingState、ConnectedState、ClosingState4种

    QModbusClient 类自带了状态改变的信号:QModbusClient::stateChanged
    //我在自定义类中添加了两个信号,statechange_on()和statechange_off();用于判断当前是否连接

    连接状态相关的代码如下:

    先在自定义类的构造函数中绑定信号(QModbusClient::stateChanged)和槽(自定义槽函数)

    connect(My_client, &QModbusClient::stateChanged,
                  this, &My_modbus_tcp::onStateChanged);//连接状态发生改变时处理函数(connect or discennect)
    
    

    实现状态改变的槽函数,状态进行变化时发出相应的信号,可以使用该信号进行信号与槽的绑定实现状态改变时的功能。

    /********************************************
     * 函数名称:onStateChanged()
     * 功能:监听TCP连接的状态,若状态发生改变,发出对应的信号
     * 工作方式:
     * 参数:无参数
     * 返回值:无返回值
     * 备注:
     * 修改记录:
    *********************************************/
    void My_modbus_tcp::onStateChanged()               //连接状态改变时的槽函数
    {
       if(My_client->state() == QModbusDevice::ConnectedState)
       {
          emit statechange_on();
       }
    
       else {
          emit statechange_off();
       }
    }
    

    3.Modbus 通过TCP/IP读取数据

    Modbus对象的4种数据都可以进行读取,线圈和离散输入都是位数据,结果只能是0/1;输入寄存器和保持寄存器可以实现0x00~0xFF;

    (1)读取线圈数据

    /********************************************
     * 函数名称:read_modbus_tcp_Coils(int start_add,quint16 numbers ,int Server_ID)
     * 功能:发送读取modbus设备线圈数据请求
     * 工作方式:
     * 参数:
     *      参数1:int start_add           读取的起始地址
     *      参数2:quint16 numbers         读取的个数
     *      参数3:int Server_ID           Modbus的设备ID
     * 返回值:成功返回true,失败返回fasle。
     * 备注:
     * 修改记录:
    *********************************************/
    bool My_modbus_tcp::read_modbus_tcp_Coils(int start_add,quint16 numbers,int Server_ID)
    {
        if (!My_client->state() == QModbusDevice::ConnectedState){
            return false;
        }
    
        QModbusDataUnit ReadUnit(QModbusDataUnit::Coils,start_add,numbers);
        qDebug()<< "配置ReadUnit完成";
         if (auto *reply = My_client->sendReadRequest(ReadUnit, Server_ID))     //1是Server_ID
         {
            if (!reply->isFinished())
            {  qDebug()<< "准备进行信号与槽连接";
               QObject::connect(reply, &QModbusReply::finished,this,&My_modbus_tcp::ReadReady_Coils);
               qDebug()<<"进入读取的槽函数 ";
               return true;
            }
            else
            {
                qDebug()<< "提前delete reply";
                delete reply;
                return false;
            }
    
         }
    
         else {
             qDebug()<< "提前退出";
             return false;
         }
    }
    
    /********************************************
     * 函数名称:ReadReady_Coils()
     * 功能:接收到读取请求后执行的槽函数
     * 工作方式:
     * 参数:无参数
     * 返回值:没有返回值
     * 备注:
     * 修改记录
    *********************************************/
    void My_modbus_tcp::ReadReady_Coils()
    {
        qDebug()<< "开始执行槽函数";
        QModbusReply *reply = qobject_cast<QModbusReply *>(sender());
           if (!reply){
               qDebug()<< "提前退出";
               return ;
           }
           if (reply->error() == QModbusDevice::NoError)
           {   qDebug()<< "接收数据";
               const QModbusDataUnit unit = reply->result();
               for(uint16_t i=0; i< unit.valueCount();  i++)
               {
                   /*
                    QByteArray  AllData =unit.values();	//一次性读完
                   */
    
                   uint16_t res=unit.value(i);			//一个一个读
                   Coils_Bufer[i] = static_cast<uint8_t>(res);
                   //读完将数据存储起来  Coils_Bufer[i] 自定的数组 用来存放数据
                
               }
    
           }
           else
           {
           }
    
           reply->deleteLater(); // delete the reply
           emit my_readC_finished();	//coils读取完成后emit 读取完成的信号;
    
    }
    

    读取离散变量时和读取线圈数据一样,唯一区别就是配置读取数据单元时换成
    QModbusDataUnit ReadUnit(QModbusDataUnit::DiscreteInputs,start_add,numbers);

    离散变量读取完成时发出自己的信号(自定义信号)。

    (2)读取保持寄存器数据

    /********************************************
     * 函数名称:read_modbus_tcp_HoldingRegisters(int start_add,quint16 numbers ,int Server_ID)
     * 功能:发送读取modbus设备HoldingRegisters数据请求
     * 工作方式:
     * 参数
     * 		参数1:读取数据的起始地址
     * 		参数2:读取多少个数据
     * 		参数3:SerVer ID号
     * 返回值:成功返回true,失败返回fasle。
     * 备注:
     *      QModbusDataUnit ReadUnit(QModbusDataUnit::HoldingRegisters,参数1,参数2);
     *      参数1:读取modbus设备的起始地址          int 类型
            参数2:读取几个modbus数据               quint16 类型
     * 修改记录:
    *********************************************/
    bool My_modbus_tcp::read_modbus_tcp_HoldingRegisters(int start_add,quint16 numbers ,int Server_ID)
    {
        QModbusDataUnit ReadUnit(QModbusDataUnit::HoldingRegisters,start_add,numbers);
    
         if (auto *reply = My_client->sendReadRequest(ReadUnit, Server_ID))     //1是Server_ID
         {
            if (!reply->isFinished())
            {
               QObject::connect(reply, &QModbusReply::finished,this,&My_modbus_tcp::ReadReady_InputRegisters);
    
            }
            else
            {
                delete reply;
            }
    
         }
    
    }
    
    /********************************************
     * 函数名称:ReadReady_HoldingRegisters()
     * 功能:槽函数,发送请求成功后,接收数据将其存储在Hold_Bufer[]数组中
     * 工作方式:
     * 参数:无参数
     * 返回值:没有返回值
     * 备注:
     * 修改记录
    *********************************************/
    void My_modbus_tcp::ReadReady_HoldingRegisters()
    {
        QModbusReply *reply = qobject_cast<QModbusReply *>(sender());
           if (!reply){
               return ;
           }
           if (reply->error() == QModbusDevice::NoError)
           {
               const QModbusDataUnit unit = reply->result();
    
               for(uint16_t i=0; i< unit.valueCount(); )
               {
                   uint16_t res=unit.value(i);
                   Input_Bufer[i] = static_cast<uint8_t>(res);
                   i++;
               }
            
           }
           else
           {
           }
    
           reply->deleteLater(); // delete the reply
           emit my_readH_finished();		//自定义的信号
    }
    

    输入寄存器类似,换掉配置单元的数据即可。

    (3)给线圈写入数据

    /********************************************
     * 函数名称: Write_modbus_tcp_Coils(QString str1,int star_add,int number)
     * 功   能: 将想要修改的数据写入到modbus设备某个(某些)地址的Coils中。
     * 工作方式:
     * 参   数:
     *          参数1:要写入的数据(例:1 0 1 0 1 0)   QString 类型
     *          参数2:写入数据的起始地址               int 类型
     *          参数3:写入数据的个数                   quint16
     * 返回值:没有返回值
     * 备注:一次性可以写入单个或者多个数据,取决于该函数执行时参数。
     * 修改记录
    *********************************************/
    bool My_modbus_tcp::Write_modbus_tcp_Coils(QString str1,int star_add,int number)
    {
        quint16 number1 = static_cast<quint16>(number); //C++中的数据类型转换
        QModbusDataUnit writeUnit(QModbusDataUnit::Coils,star_add,number1);
    
        for (uint i1 = 0; i1 < writeUnit.valueCount(); i1++) {
            int j1 = 2*i1;
            QString stt = str1.mid (j1,1);
            bool ok;
            quint16 hex1 =stt.toInt(&ok,16);//将textedit中读取到的数据转换为16进制发送
            writeUnit.setValue(i1,hex1);//设置发送数据
         }
    
        if (auto *reply = My_client->sendWriteRequest(writeUnit, 1)) {// ui->spinBox_SerAddress->value()是server address   sendWriteRequest是向服务器写数据
                if (!reply->isFinished()) {   //reply Returns true when the reply has finished or was aborted.
                    connect(reply, &QModbusReply::finished, this, [this, reply]() {
                        if (reply->error() == QModbusDevice::ProtocolError) {
                            qDebug() << (tr("Write response error: %1 (Mobus exception: 0x%2)")
                                .arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16),
                                5000);
                        } else if (reply->error() != QModbusDevice::NoError) {
                            qDebug()<< (tr("Write response error: %1 (code: 0x%2)").
                                arg(reply->errorString()).arg(reply->error(), -1, 16), 5000);
                        }
                        reply->deleteLater();
                    });
                } else {
                    // broadcast replies return immediately
                    reply->deleteLater();
                }
            } else {
                qDebug() << (tr("Write error: ") + My_client->errorString(), 5000);
            }
    
    }
    

    !!!写数据时必须转化位16进制写入

    (4)给保持寄存器写数据

    /********************************************
     * 函数名称: Write_modbus_tcp_HoldingRegisters(QString str1,int star_add,int number)
     * 功   能: 将想要修改的数据写入到modbus设备某个(某些)地址的HoldingRegisters中。
     * 工作方式:
     * 参   数:
     *          参数1:要写入的数据(例:FF A0 00等)   QString 类型
     *          参数2:写入数据的起始地址               int 类型
     *          参数3:写入数据的个数                   quint16
     * 返回值:没有返回值
     * 备注:一次性可以写入单个或者多个数据,取决于该函数执行时参数。
     * 修改记录
    *********************************************/
    bool My_modbus_tcp::Write_modbus_tcp_HoldingRegisters(QString str1,int star_add,int number)
    {
        qDebug()<< "准备写holding数据::";
        QByteArray str2 = QByteArray::fromHex (str1.toLatin1().data());//按十六进制编码接入文本
        QString str3 = str2.toHex().data();//以十六进制显示
    
        quint16 number1 = static_cast<quint16>(number);
        QModbusDataUnit writeUnit(QModbusDataUnit::HoldingRegisters,star_add,number1);
        int j1 = 0;
        for (uint i1 = 0; i1 < writeUnit.valueCount(); i1++) {
    
            if(i1 == 0){
                j1 = static_cast<int>(2*i1);
            }
            else {
               j1 = j1+3;
            }
            QString stt = str1.mid (j1,2);
            bool ok;
            quint16 hex1 =static_cast<quint16>(stt.toInt(&ok,16));//将textedit中读取到的数据转换为16进制发送
            writeUnit.setValue(static_cast<int>(i1),hex1);//设置发送数据
         }
    
        if (auto *reply = My_client->sendWriteRequest(writeUnit, 1)) {// ui->spinBox_SerAddress->value()是server address   sendWriteRequest是向服务器写数据
                if (!reply->isFinished()) {   //reply Returns true when the reply has finished or was aborted.
                    connect(reply, &QModbusReply::finished, this, [this, reply]() {
                        if (reply->error() == QModbusDevice::ProtocolError) {
                            qDebug() << (tr("Write response error: %1 (Mobus exception: 0x%2)")
                                .arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16),
                                5000);
                        } else if (reply->error() != QModbusDevice::NoError) {
                            qDebug()<< (tr("Write response error: %1 (code: 0x%2)").
                                arg(reply->errorString()).arg(reply->error(), -1, 16), 5000);
                        }
                        reply->deleteLater();
                    });
                } else {
                    // broadcast replies return immediately
                    reply->deleteLater();
                }
            } else {
                qDebug() << (tr("Write error: ") + My_client->errorString(), 5000);
            }
    }
    

    总结

    下班了 后续增加写入数据的详细解释

    展开全文
  • 用C++编写的基于TCP/IP的Modbus网络通讯工具源码,据参考价值。
  • ModbusTCP协议通信 C++实现源码举例

    千次阅读 2021-03-03 14:08:36
    Modbus协议允许在TCP连接下实现通信,使用ws2_32这个socket支持库,写法是普遍的网络编程TCP客户端连接写法 在Windows环境下,C++的TCP连接发起部分代码:作为主机(客户端),主动连接从机(服务端) WORD version...
  • codesys WINRT的modbusTCP服务器 我们都知道在以太网上可以使用modbusTCP来进行通讯。以太网通讯需要有服务器和客户端。一般的PLC是在控制器上建立一个服务器器。像上位机及高一层的设备可以连接PLC,然后最常用的是...
  • libMbopll库是面向linux平台设计的modbus主机协议栈,可以运行在x86平台以及各种嵌入式linux平台;协议栈提供了简单明了的API接口提供给程序员进行应用层modbus主机程序开发,对modbus协议通信与底层操作进行了封装...
  • ModbusTCP报文格式说明

    2020-02-18 17:33:17
    modbustcp数据报文结构 请求:00 00 00 00 00 06 09 03 00 00 00 01 响应:00 00 00 00 00 05 09 03 02 12 34 一次modbustcp读取保持寄存器的通信分析(省略了ip/tcp头):从左向右分析该数据报文: 请求: 0000为此次...
  • linux modbus 程序

    2016-11-10 15:47:59
    modbus 数据处理部分, 有modbus tcpmodbus rtu两种
  • ModbusTCP协议学习笔记

    万次阅读 多人点赞 2018-11-20 12:00:10
    1996年施耐德公司推出基于以太网TCP/IP的modbus协议:modbusTCP。 Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。 标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口...
  • modbus 用的是网上别人写的开源代码,地址: GitHub - stephane/libmodbus: A Modbus library for Linux, Mac OS X, FreeBSD, QNX and Windows
  • modbustcp 通讯

    2015-07-16 15:17:26
    西门子PLC1200 ,1500 modbusTCP通讯设置,程序案例
  • /********************************************************************************** FreeModbus LINUXTCP Compile ERROR* 说明:* 想使用FreeModbus TCP在Ubuntu上进行测试,结果其默认没有打开TCP功能。...
  • 由于目前广泛应用的DCS、FCS、SCADA 等传统计算机测控系统使用的智能仪表设备、分布式I/O模块、PLC 等设备来自于不同的生产商,而各生产商为了各自的商业利益,没有遵循统一的接口规范,造成了设备之间的通信异常...
  • Modbus Tcp Server

    千次阅读 2018-08-22 23:05:32
    // mbserver.c V2.1 1/18/...// example multi-session Modbus/TCP server supporting class 0 commands // This program should work under UNIX (as C or C++) or Win32 (as C or C++) // the symbol WIN32 wil...
  • modbus-utils(linux平台的modbus调试软件) Modbus client and server command line tools based on libmodbus. 代码仓库 编译 for ubuntu: sudo apt install libmodbus-dev for buildroot: BR2_PACKAGE_LIBMODBUS=y...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,029
精华内容 411
关键字:

linuxmodbustcp

linux 订阅