查看: 4202|回复: 1

【EVB-335X-II】C/S架构软件开发

[复制链接]
  • TA的每日心情
    难过
    2021-2-27 22:16
  • 签到天数: 1568 天

    连续签到: 1 天

    [LV.Master]伴坛终老

    发表于 2018-1-16 21:58:44 | 显示全部楼层 |阅读模式
    分享到:
    【EVB-335X-II】之基于libmodbus库的Modbus-RTU从站的C/S架构软件开发(QT多线程监听服务)

    介绍了如何在将libmodbus TCP库应用在EVB-335X-II开发板上,实现了以树莓派3B为主机,EVB-335X-II为从机,由EVB-335X-II开发板为树莓派提供服务的例程。
    在一些工业应用中,有些场合是不适合使用以太网的,多数是采用RS485电气接口,结合Modbus RTU通讯协议实现对底层数据采集器的联网。这篇试用报告我们将介绍如何在EVB-335X-II开发板上应用libmodbus库的RTU功能模块,实现EVB-335X-II开发板作为服务提供端,为上层应用提供底层采集到的数据。
    相对于上一篇使用报告,由于我们没有采用多线程技术,在EVB-335X-II开发板上启动RTU服务后,整个GUI处于for循环中,无法接收用户的其他操作,服务次数也无法动态显示,只有在Modbus主机结束连接后,才显示出EVB-335X-II到底服务了几次。为了解决上述存在的问题,这篇报告中,我们设计一个多线程类,由该类去承载Modbus RTU服务,使得服务监听与主界面GUI并行执行,达到动态显示服务次数的目的。
    1. 硬件搭建
    由于树莓派3B的本身特点,蓝牙模块与串口的冲突,这次实验我采用友善之臂的NanoPi M2作为Modbus RTU主机,硬件连接原理如图所示:

    1.png

    树莓派UART3对应的驱动未/dev/ttyAMA2。在EVB-335X-II端,由于串口0用于调试作用,串口2-4即可用于TTL,也可用于RS232,而串口1只支持RS232,为了调试方便,我这里就直接选用串口1,串口1的驱动为:/dev/ttyO1
    硬件连接如图所示:
    NanoPi M2侧:

    2.png

    EVB-335X-II侧:

    3.png

    2. EVB-335X-II侧libmodbus rtu服务功能程序
    1)服务代码
    我们只需要将上一篇试用报告中的TCP换成RTU,并设置串口的驱动程序、波特率、数据位、停止位和奇偶校验位,以及设置RS232还是RS485模式。
    代码如下:
    int s = -1;
       modbus_t *ctx;
       modbus_mapping_t *mb_mapping;
       int rc;
       int i;
       int use_backend;
       uint8_t *query;
       int header_length;
       int service_count = 0;
        use_backend = RTU;
       if (use_backend == TCP) {
           ctx = modbus_new_tcp("192.168.1.104", 1502);
           query = (uint8_t *)malloc(MODBUS_TCP_MAX_ADU_LENGTH);
        }else if (use_backend == TCP_PI) {
           ctx = modbus_new_tcp_pi("::0", "1502");
           query = (uint8_t *)malloc(MODBUS_TCP_MAX_ADU_LENGTH);
        }else {
           ctx = modbus_new_rtu("/dev/ttyO1",9600, 'N', 8, 1);
            modbus_set_slave(ctx,SERVER_ID);
           modbus_rtu_set_serial_mode(ctx,MODBUS_RTU_RS232);
            query = (uint8_t*)malloc(MODBUS_RTU_MAX_ADU_LENGTH);
        }
        header_length= modbus_get_header_length(ctx);
       modbus_set_debug(ctx, TRUE);
       mb_mapping = modbus_mapping_new_start_address(
           UT_BITS_ADDRESS, UT_BITS_NB,
           UT_INPUT_BITS_ADDRESS, UT_INPUT_BITS_NB,
           UT_REGISTERS_ADDRESS, UT_REGISTERS_NB_MAX,
           UT_INPUT_REGISTERS_ADDRESS, UT_INPUT_REGISTERS_NB);
       if (mb_mapping == NULL) {
           fprintf(stderr, "Failed to allocate the mapping: %s\n",
                    modbus_strerror(errno));
           modbus_free(ctx);
           return; //-1;
        }
       /* Examples from PI_MODBUS_300.pdf.
          Only the read-only input values are assigned. */
       /* Initialize input values that's can be only done server side. */
       modbus_set_bits_from_bytes(mb_mapping->tab_input_bits, 0,UT_INPUT_BITS_NB,
                                  UT_INPUT_BITS_TAB);
       for (i=0; i < UT_REGISTERS_NB; i++) {
           mb_mapping->tab_registers = UT_REGISTERS_TAB;;
        }
       /* Initialize values of INPUT REGISTERS */
       for (i=0; i < UT_INPUT_REGISTERS_NB; i++) {
           mb_mapping->tab_input_registers = UT_INPUT_REGISTERS_TAB;
        }
       if (use_backend == TCP) {
           s = modbus_tcp_listen(ctx, 1);
           modbus_tcp_accept(ctx, &s);
        }else if (use_backend == TCP_PI) {
           s = modbus_tcp_pi_listen(ctx, 1);
           modbus_tcp_pi_accept(ctx, &s);
        }else {
           rc = modbus_connect(ctx);
           if (rc == -1) {
               fprintf(stderr, "Unable to connect %s\n", modbus_strerror(errno));
               modbus_free(ctx);
               return; //-1;
           }
        }
       for (;;) {
           do {
               rc = modbus_receive(ctx, query);
               /* Filtered queries return 0 */
           } while (rc == 0);
           /* The connection is not closed on errors which require on reply such as
              bad CRC in RTU. */
           if (rc == -1 && errno != EMBBADCRC) {
               /* Quit */
               break;
           }
           /* Special server behavior to test client */
           if (query[header_length] == 0x03) {
               /* Read holding registers */
               if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 3)
                    == UT_REGISTERS_NB_SPECIAL) {
                    printf("Set an incorrectnumber of values\n");
                    MODBUS_SET_INT16_TO_INT8(query,header_length + 3,
                                            UT_REGISTERS_NB_SPECIAL - 1);
               } else if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 1)
                           == UT_REGISTERS_ADDRESS_SPECIAL){
                    printf("Reply to thisspecial register address by an exception\n");
                    modbus_reply_exception(ctx,query,
                                          MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY);
                    continue;
               } else if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 1)
                           ==UT_REGISTERS_ADDRESS_INVALID_TID_OR_SLAVE) {
                    const int RAW_REQ_LENGTH = 5;
                    uint8_t raw_req[] = {
                        (use_backend == RTU) ? INVALID_SERVER_ID :0xFF,
                        0x03,
                        0x02, 0x00, 0x00
                    };
                    printf("Reply with aninvalid TID or slave\n");
                    modbus_send_raw_request(ctx,raw_req, RAW_REQ_LENGTH * sizeof(uint8_t));
                    continue;
               } else if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 1)
                           ==UT_REGISTERS_ADDRESS_SLEEP_500_MS) {
                    printf("Sleep 0.5 s beforereplying\n");
                    usleep(500000);
               } else if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 1)
                           ==UT_REGISTERS_ADDRESS_BYTE_SLEEP_5_MS) {
                    /* Test low level onlyavailable in TCP mode */
                    /* Catch the reply and sendreply byte a byte */
                    uint8_t req[] ="\x00\x1C\x00\x00\x00\x05\xFF\x03\x02\x00\x00";
                    int req_length = 11;
                    int w_s =modbus_get_socket(ctx);
                    if (w_s == -1) {
                       fprintf(stderr,"Unable to get a valid socket in special test\n");
                        continue;
                    }
                    /* Copy TID */
                    req[1] = query[1];
                    for (i=0; i < req_length;i++) {
                        printf("(%.2X)", req);
                        usleep(5000);
                        rc = send(w_s, (constchar*)(req + i), 1, MSG_NOSIGNAL);
                        if (rc == -1) {
                            break;
                        }
                    }
                   continue;
               }
           }
           service_count++;
           le->setText(QString::number(service_count));
           rc = modbus_reply(ctx, query, rc, mb_mapping);
           if (rc == -1) {
               break;
           }
        }
       printf("Quit the loop: %s\n", modbus_strerror(errno));
       if (use_backend == TCP) {
           if (s != -1) {
               //close(s);
           }
        }
       modbus_mapping_free(mb_mapping);
       free(query);
       /* For RTU */
       modbus_close(ctx);
       modbus_free(ctx);
    2)QT多线程
    创建一个Mythread线程类,该类继承自QThread,在类定义文件中设计一个指向QLineEdit空间的指针,创建该类时,通过构造函数使QLineEdit指针指向QWidget界面中的实际创建的QLineEdit控件,使得在新创建的现场函数中能够动态调整系统的“服务次数”。
    线程类的头文件:

    4.png

    线程的cpp文件,我们在构造函数中设置QLineEdit控件指针,在run函数中设置Modbus服务函数。
    构造函数如下所示:

    5.png

    run函数的代码为我们上面贴的关键代码(完整代码见附件,完成的工程文件)
    3)多线变量定义与启动
    我们在GUI的主窗口类中定义线程变量,在构造函数中创建线程,在启动服务的按钮处理函数中启动线程。
    主界面类定义:

    6.png

    构造函数与线程启动函数:

    7.png

    4)波特率
    在测试过程中,当波特率设置为115200时,通讯无法连接,可能是我的连线较长,也可能是开发板的驱动还不够稳定,在测试注意将波特率设置为19200或9600,波特率低一些对实际应用影响不大,因为通常,工业应用中,通讯距离较远,9600是最常用的速度参数。
    3. NanoPi M2侧Modbus主机的程序设计
    Modbus上下文对象定义,以及通讯连接函数如图所示:

    8.png

    Modbus读寄存器函数:

    9.png

    Modbus写寄存器函数:

    10.png

    在读写函数中的,352为寄存器的起始地址,其16进制为0x160,对于与Modbus RTU服务程序unit-test.h文件中,如图所示:

    11.png

    4.上电测试
    1)EVB-335X-II上电
    给EVB-335X-II开发板上电,执行命令:
    ifconfig eth0 192.168.1.112
    mount -t nfs 192.168.1.109:/nfsshare /mnt-o nolock
    cd /mnt
    ./libmodbustest_rtu
    命令执行结果如图所示:

    21.png

    点击服务启动按钮,启动服务。
    2)NanoPi M2上电
    给NanoPi M2上电,编辑代码,设置串口驱动程序,及通讯参数:

    22.png

    执行命令设置ttyAMA2的权限:
    chmod 777 /dev/ttyAMA2
    编译Modbus主机程序,运行程序:

    23.png

    3)测试程序
    在NanoPi M2上点击读取寄存器按钮,执行结果如图所示:

    24.png

    说明我们成功的读取了3个数据,数据内容如图所示:

    25.png

    连续再执行两次。然后我们点击将数据写入寄存器按钮,执行结果如图所示:

    26.png

    我们再次点击读取寄存器数据,结果如图所示:

    27.png

    说明,我们成功的修改了服务器端寄存器中保存的数据。
    我们在看看服务器端记录的服务次数:

    28.png

    在操作的过程中,我们可以发现,服务次数编辑框中的数字是在动态变化的,说明我们的基于QT GUI的多线程设计也是成功的。
    5. 小结
    通过上述的程序设计,我们实现了基于libmodbus的RTU通讯主、从功能,并且较好的利用了QThread的多线程编程模型,使得Modbus RTU服务监听功能与QT主界面线程并行执行,改善了用户QT GUI界面的操作体验。


    回复

    使用道具 举报

  • TA的每日心情
    开心
    2018-5-10 14:30
  • 签到天数: 6 天

    连续签到: 2 天

    [LV.2]偶尔看看I

    发表于 2018-2-1 11:24:52 | 显示全部楼层
    都是对屏幕调试的~不错
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /2 下一条

    手机版|小黑屋|与非网

    GMT+8, 2024-4-20 02:52 , Processed in 0.158643 second(s), 25 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.