完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
【EVB-335X-II试用体验】之基于libmodbus库的Modbus-TCP从站的C/S架构软件开发 EVB-335X-II开发板具有优秀的工业级特性,其核心板、底板的接口设计均充分考虑了EMI电磁兼容性设计,把EVB-335X-II开发板作为车间级数据采集服务器,从硬件稳定性和系统稳定性上来讲,都能满足实际的功能需要。 车间级的数据采集服务器必然离不开网络,我们可以采用基于TCP/IP协议,编写Socket类型的C/S数据Produce和数据Consume的应用软件,达到客户端获取服务器端数据的功能。但在车间中更加常用的方式是以现场总线为传输媒介,以RS485为电气接口,以Modbus为通讯协议,实现数据采集服务器与数据处理客户端的互动。 在Nanopi M2的试用报告中,我们写了两篇基于libmodbus的Modbus RTU和Modbus TCP主站,这里成为主站,也就是Nanopi M2去主动获取Modbus从站的数据,即Modbus从站是服务器,即Server,而Nanopi M2主站消费数据,即Client。上Nanopi M2的项目中,主要是通过Modbus协议读取称重传感器仪表的动态数据,所以采用Modbus主站的模式,而对于EVB-335X-II的试用,其主要作为车间数据服务器,即EVB-335X-II为其他主机提供数据,在这里作为从站使用,即作为Server。 这篇试用报告主要介绍如何在EVB-335X-II开发板实现基于QT GUI界面的Modbus TCP从站功能。 关于如何下载、配置、移植libmodbus库,我在Nanopi M2的试用报告中已经有详细描述,具体可参考链接:https://bbs.elecfans.com/forum.php?mod=viewthread&tid=736180&extra= 1. libmodbus从站固件数据结构和函数分析 libmodbus库的关键函数介绍在如下网站:http://libmodbus.org/docs/v3.1.4/ 1)modbus_new_rtu 该函数负责创建一个Modbus RTU的上下文对象,相当于一个文件句柄,在modbus服务的整个生命周期中必须有效。 2)modbus_new_tcp 该函数负责创建一个Modbus TCP的上下文对象,相当于一个文件句柄,在modbus服务的整个生命周期中必须有效。 3)modbus_set_slave 改函数主要针对RTU通讯协议,指定从站的地址,以进行对接收到主站信号的匹配检查,判断主站发出的数据请求是否是发给自己的。 4)关键的数据转换函数 · MODBUS_GET_HIGH_BYTE(data), extractsthe high byte from a byte · MODBUS_GET_LOW_BYTE(data), extracts thelow byte from a byte · MODBUS_GET_INT64_FROM_INT16(tab_int16,index), builds an int64 from the four first int16 starting at tab_int16[index] · MODBUS_GET_INT32_FROM_INT16(tab_int16,index), builds an int32 from the two first int16 starting at tab_int16[index] · MODBUS_GET_INT16_FROM_INT8(tab_int8,index), builds an int16 from the two first int8 starting at tab_int8[index] · MODBUS_SET_INT16_TO_INT8(tab_int8,index, value), set an int16 value into the two first bytes starting attab_int8[index] · MODBUS_SET_INT32_TO_INT16(tab_int16,index, value), set an int32 value into the two first int16 starting attab_int16[index] · MODBUS_SET_INT64_TO_INT16(tab_int16,index, value), set an int64 value into the four first int16 starting attab_int16[index] 5) modbus_mapping_new 这个函数是Modbus从站独有的,从mapping的字面意思,我们可以推测是做某种映射。该函数主要实现对modbus协议中规定的线圈、输入线圈、保持寄存器、输入寄存器的起始地址、元素个数等的定义,为数据的发送和写入地址定义映射关系。 2. 基于QT的libmodbus 从站代码分析 2.1 头文件 包含libmodbus头文件,如下: #include #include #include #include #include #include "libmodbus/modbus.h" #ifdef _WIN32 # include #else # include #endif /* For MinGW */ #ifndef MSG_NOSIGNAL # define MSG_NOSIGNAL 0 #endif #include "unit-test.h" enum { TCP, TCP_PI, RTU }; 2.2 核心代码 核心代码主要包括定义modbus cortext,绑定IP地址和端口号,启动服务监听,等待Modbus主站的连接,接受主站连接,提供数据服务等。 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 = TCP; 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/ttyUSB0", 115200, 'N', 8, 1); modbus_set_slave(ctx, SERVER_ID); 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 toconnect %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 anincorrect number 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 ***efore replyingn"); 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 printf("(%.2X)", req); usleep(5000); rc = send(w_s, (constchar*)(req + i), 1, MSG_NOSIGNAL); if (rc == -1) { break; } } continue; } } service_count++; ui->service_count->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.3 寄存器的配置 线圈起始地址、大小,寄存器其实地址、大小等在unit-test.h的头文件中定义,如下所示: #ifndef _UNIT_TEST_H_ #define _UNIT_TEST_H_ /* Constants defined by configure.ac */ #define HAVE_INTTYPES_H 1 #define HAVE_STDINT_H 1 #ifdef HAVE_INTTYPES_H #include #endif #ifdef HAVE_STDINT_H # ifndef _MSC_VER # include # else # include "stdint.h" # endif #endif #define SERVER_ID 17 #define INVALID_SERVER_ID 18 const uint16_t UT_BITS_ADDRESS = 0x130; const uint16_t UT_BITS_NB = 0x25; const uint8_t UT_BITS_TAB[] = { 0xCD, 0x6B,0xB2, 0x0E, 0x1B }; const uint16_t UT_INPUT_BITS_ADDRESS =0x1C4; const uint16_t UT_INPUT_BITS_NB = 0x16; const uint8_t UT_INPUT_BITS_TAB[] = { 0xAC,0xDB, 0x35 }; const uint16_t UT_REGISTERS_ADDRESS =0x160; const uint16_t UT_REGISTERS_NB = 0x3; const uint16_t UT_REGISTERS_NB_MAX = 0x20; const uint16_t UT_REGISTERS_TAB[] = {0x022B, 0x0001, 0x0064 }; /* Raise a manual exception when thisaddress is used for the first byte */ const uint16_t UT_REGISTERS_ADDRESS_SPECIAL= 0x170; /* The response of the server will containsan invalid TID or slave */ const uint16_tUT_REGISTERS_ADDRESS_INVALID_TID_OR_SLAVE = 0x171; /* The server will wait for 1 second beforereplying to test timeout */ const uint16_tUT_REGISTERS_ADDRESS_SLEEP_500_MS = 0x172; /* The server will wait for 5 ms beforesending each byte */ const uint16_tUT_REGISTERS_ADDRESS_BYTE_SLEEP_5_MS = 0x173; /* If the following value is used, a badresponse is sent. It's better to test with a lower value than UT_REGISTERS_NB_POINTS to try to raise a segfault. */ const uint16_t UT_REGISTERS_NB_SPECIAL =0x2; const uint16_t UT_INPUT_REGISTERS_ADDRESS =0x108; const uint16_t UT_INPUT_REGISTERS_NB = 0x1; const uint16_t UT_INPUT_REGISTERS_TAB[] = {0x000A }; const float UT_REAL = 123456.00; const uint32_t UT_IREAL_ABCD = 0x0020F147; const uint32_t UT_IREAL_DCBA = 0x47F12000; const uint32_t UT_IREAL_BADC = 0x200047F1; const uint32_t UT_IREAL_CDAB = 0xF1470020; /* const uint32_t UT_IREAL_ABCD =0x47F12000); const uint32_t UT_IREAL_DCBA = 0x0020F147; const uint32_t UT_IREAL_BADC = 0xF1470020; const uint32_t UT_IREAL_CDAB =0x200047F1;*/ #endif /* _UNIT_TEST_H_ */ 2.4 编译Server工程 modbus-tcp协议的主站设计界面如图所示: 点击“Start Libmodbus Service”按钮,启动Modbus Server服务,上面的Service count记录该服务器为客户端服务了几次。 编译结果如图所示: 将libmodbus_server_in_ARM拷贝到NFS共享目录。 3. Modbus主站QT软件设计 为了达到充分测试ARM软硬件开发环境,我这里的主机开发环境选基于树莓派3B的卡片电脑。 Modbus主站软件设计与Nanopi M2的试用报告中的设计基本相似,不过这侧主站包含对对从站的写入操作,软件结果如图所示: 第一个按钮是将数据写入modbus tcp 从站,第二个按钮则从modbus tcp从站读取3个寄存器中的数据。 关键代码如图所示: 头文件: 构造函数: 设置寄存器值函数: 读取寄存器值函数: 4. 启动树莓派和EVB-335X-II开发板 1)启动树莓派 连接树莓派3B与HDMI接口的显示器,编译Modbus主机工程,运行主机软件,如图所示: 2)启动EVB-335X-II开发板 启动EVB-335X-II开发板,执行如下命令: ifconfig eth0 192.168.1.104 mount -t nfs 192.168.1.102:/nfsshare /mnt-o nolock cd /mnt ./libmodbus_server_in_arm 执行结果如图所示: EVB-335X-II开发板上显示Modbus从站界面,我们点击按钮,开启Modbus从机服务。 3)树莓派端测试 在树莓派界面上点击读取寄存器按钮,显示如下: 上面显示3,说明系统成功的读取到了3个数据,点击OK按钮,显示读取到结果,如图所示: 我们再次重复执行上述的读取动作。然后,点击第一个按钮,即将3个新数据写入寄存器,我们再次点击第二个按钮,显示数据如下: 说明我们的寄存器设置成功。 3)EVB-335X-II端显示 我们在树莓派上执行了3次读取操作,和1次寄存器数据写入操作,说明Modbus从站为树莓派端的Modbus主站服务了4次,我们看下EVB-335X-II端的显示是否正确:
|
|
相关推荐
|
|
只有小组成员才能发言,加入小组>>
【盈鹏飞RK3399安卓主板 XPC-3399Pro免费试用】+烧写出厂固件
10324 浏览 0 评论
【盈鹏飞EVB-T335开发板试用体验】debian系统烧写
3355 浏览 1 评论
【盈鹏飞I.MX6UL工控开发板试用体验】linux can 测试
3228 浏览 0 评论
308浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-25 02:06 , Processed in 0.854074 second(s), Total 67, Slave 50 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号