完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
【EVB-335X-II试用体验】之基于libmodbus库的Modbus-RTU从站的C/S架构软件开发(QT多线程监听服务) 在《【EVB-335X-II试用体验】之基于libmodbus库的Modbus-TCP从站的C/S架构软件开发》这篇试用报告中,我们介绍了如何在将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主机,硬件连接原理如图所示: 树莓派UART3对应的驱动未/dev/ttyAMA2。在EVB-335X-II端,由于串口0用于调试作用,串口2-4即可用于TTL,也可用于RS232,而串口1只支持RS232,为了调试方便,我这里就直接选用串口1,串口1的驱动为:/dev/ttyO1 硬件连接如图所示: NanoPi M2侧: EVB-335X-II侧: 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: %sn", 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 %sn", 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 valuesn"); 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 exceptionn"); 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 slaven"); 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 beforereplyingn"); 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[] ="x00x1Cx00x00x00x05xFFx03x02x00x00"; 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 testn"); 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: %sn", 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控件,使得在新创建的现场函数中能够动态调整系统的“服务次数”。 线程类的头文件: 线程的cpp文件,我们在构造函数中设置QLineEdit控件指针,在run函数中设置Modbus服务函数。 构造函数如下所示: run函数的代码为我们上面贴的关键代码(完整代码见附件,完成的工程文件) 3)多线变量定义与启动 我们在GUI的主窗口类中定义线程变量,在构造函数中创建线程,在启动服务的按钮处理函数中启动线程。 主界面类定义: 构造函数与线程启动函数: 4)波特率 在测试过程中,当波特率设置为115200时,通讯无法连接,可能是我的连线较长,也可能是开发板的驱动还不够稳定,在测试注意将波特率设置为19200或9600,波特率低一些对实际应用影响不大,因为通常,工业应用中,通讯距离较远,9600是最常用的速度参数。 3. NanoPi M2侧Modbus主机的程序设计 Modbus上下文对象定义,以及通讯连接函数如图所示: Modbus读寄存器函数: Modbus写寄存器函数: 在读写函数中的,352为寄存器的起始地址,其16进制为0x160,对于与Modbus RTU服务程序unit-test.h文件中,如图所示: 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 命令执行结果如图所示: 点击服务启动按钮,启动服务。 2)NanoPi M2上电 给NanoPi M2上电,编辑代码,设置串口驱动程序,及通讯参数: 执行命令设置ttyAMA2的权限: chmod 777 /dev/ttyAMA2 编译Modbus主机程序,运行程序: 3)测试程序 在NanoPi M2上点击读取寄存器按钮,执行结果如图所示: 说明我们成功的读取了3个数据,数据内容如图所示: 连续再执行两次。然后我们点击将数据写入寄存器按钮,执行结果如图所示: 我们再次点击读取寄存器数据,结果如图所示: 说明,我们成功的修改了服务器端寄存器中保存的数据。 我们在看看服务器端记录的服务次数: 在操作的过程中,我们可以发现,服务次数编辑框中的数字是在动态变化的,说明我们的基于QT GUI的多线程设计也是成功的。 5. 小结 通过上述的程序设计,我们实现了基于libmodbus的RTU通讯主、从功能,并且较好的利用了QThread的多线程编程模型,使得Modbus RTU服务监听功能与QT主界面线程并行执行,改善了用户QT GUI界面的操作体验。
|
|
相关推荐
1 个讨论
|
|
只有小组成员才能发言,加入小组>>
【盈鹏飞RK3399安卓主板 XPC-3399Pro免费试用】+烧写出厂固件
10324 浏览 0 评论
【盈鹏飞EVB-T335开发板试用体验】debian系统烧写
3353 浏览 1 评论
【盈鹏飞I.MX6UL工控开发板试用体验】linux can 测试
3228 浏览 0 评论
308浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-24 10:52 , Processed in 0.717252 second(s), Total 69, Slave 52 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号