前置工作
学了这么多Modbus的知识,如果不进行实际的操作,总感觉懂的不透彻。基于此, 本篇博文就带各位读者来了解下如何通过编写程序来模拟与Modbus Slave仿真软件的通讯。当然了,这里有两个前提,如下:
1.请确保读者跟随我的第五篇博文进行了同等的操作,编译生成了modbus库。第五篇博文地址:libmodbus库的编译
2.使用VSPD创建一对串口:COM3和COM4。可参考:Modbus poll & slave仿真软件初体验
具体步骤
代码侧具体步骤
1.启动Visual Studio,创建一个新的工程项目:【File】→【New】→【Project】。在弹出的新建对话框左边中选择【Visual C++】→【Win32 Console Application】项,输入你自定义的应用程序名,设置完成后单击【确定】按钮。继续点击下一步后,具体项目配置勾选如下图:
2.工程创建完成后,找到第五篇博客中编译的libmodbus库文件,将之前生成的lib和dll文件以及几个必要的头文件都复制到本项目所在的目录,如图:
3.来到VS的主界面,在项目下的文件夹【源文件】上右键,选择 【添加】→【现有项】,在弹出的对话框中,选中modbus.h和modbus.lib文件(按住ctrl键多选),再点击【添加】按钮将这两个文件添加进项目中,如下图所示:
4.添加完之后,我们就可以使用libmodbus库中的各种接口函数了。接下来,我们在【源文件】文件夹上右键,【添加】→【新建项】,添加自己的Demo程序,暂且命名为SimRtuMaster.cpp吧,如下图:
5.现在就可以在SimRtuMaster.cpp中编写程序了,在此将代码分享如下:
#include <stdio.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif#include <string.h>
#include <stdlib.h>
#include <errno.h>#include "modbus.h"#define LOOP 1 //循环次数
#define SERVER_ID 17 //从端设备地址
#define ADDRESS_START 0 //测试寄存器起始地址
#define ADDRESS_END 99 //测试寄存器结束地址int main(void) {modbus_t *ctx;int rc;int nb_fail;int nb_loop;int addr;int nb;uint8_t *tab_rq_bits; // 用于保存发送或接收的数据uint8_t *tab_rp_bits;uint16_t *tab_rq_registers;uint16_t *tab_rp_registers;uint16_t *tab_rw_rq_registers;// RTUctx = modbus_new_rtu("COM3", 19200, 'N', 8, 1); // 创建一个RTU类型的容器modbus_set_slave(ctx, SERVER_ID); // 设置从端地址modbus_set_debug(ctx, TRUE); // 设置debug模式if (modbus_connect(ctx) == -1) // 建立连接{fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));modbus_free(ctx);return -1;}nb = ADDRESS_END - ADDRESS_START; // 计算需要测试的寄存器个数// 以下申请内存块用来保存发送和接收各数据tab_rq_bits = (uint8_t *)malloc(nb * sizeof(uint8_t));memset(tab_rq_bits, 0, nb * sizeof(uint8_t));tab_rp_bits = (uint8_t *)malloc(nb * sizeof(uint8_t));memset(tab_rp_bits, 0, nb * sizeof(uint8_t));tab_rq_registers = (uint16_t *)malloc(nb * sizeof(uint16_t));memset(tab_rq_registers, 0, nb * sizeof(uint16_t));tab_rp_registers = (uint16_t *)malloc(nb * sizeof(uint16_t));memset(tab_rp_registers, 0, nb * sizeof(uint16_t));tab_rw_rq_registers = (uint16_t *)malloc(nb * sizeof(uint16_t));memset(tab_rw_rq_registers, 0, nb * sizeof(uint16_t));nb_loop = nb_fail = 0;while (nb_loop++ < LOOP){// 从起始地址开始顺序测试for (addr = ADDRESS_START; addr < ADDRESS_END; addr++){int i;// 生成随机数, 用于测试for (i = 0; i < nb; i++) {tab_rq_registers[i] = (uint16_t)(65535.0 * rand() / (RAND_MAX + 1.0));tab_rw_rq_registers[i] = ~tab_rq_registers[i];tab_rq_bits[i] = tab_rq_registers[i] % 2;}nb = ADDRESS_END - addr;// 测试线圈寄存器的单个读写rc = modbus_write_bit(ctx, addr, tab_rq_bits[0]); // 写线圈寄存器if (rc != 1){printf("ERROR modbus_write_bit (%d) \n", rc);printf("Address = %d, value = %d\n", addr, tab_rq_bits[0]);nb_fail++;}else{// 写入之后,再读取并比较rc = modbus_read_bits(ctx, addr, 1, tab_rp_bits);if (rc != 1 || tab_rq_bits[0] != tab_rp_bits[0]){printf("ERROR modbus_read_bits single (%d) \n", rc);printf("address = %d\n", addr);nb_fail++;}}// 测试线圈寄存器的批量读写rc = modbus_write_bits(ctx, addr, nb, tab_rq_bits);if (rc != nb) {printf("ERROR modbus_write_bits (%d) \n", rc);printf("Address = %d, nb = %d\n", addr, nb);nb_fail++;}else {// 写入之后,再读取并比较rc = modbus_read_bits(ctx, addr, nb, tab_rp_bits);if (rc != nb) {printf("ERROR modbus_read_bits\n");printf("Address = %d, nb = %d\n", addr, nb);nb_fail++;}else {// 进行比较for (i = 0; i < nb; i++) {if (tab_rp_bits[i] != tab_rq_bits[i]){printf("ERROR modbus_read_bits\n");printf("Addr = %d, Val = %d (0x%X) != %d (0x%X\n", addr, tab_rq_bits[i], tab_rq_bits[i], tab_rp_bits[i], tab_rp_bits[i]);nb_fail++;}}}}// 测试保持寄存器的单个读写rc = modbus_write_register(ctx, addr, tab_rq_registers[0]);if (rc != 1) {printf("ERROR modbus_write_register (%d) \n", rc);printf("Addr = %d, Val = %d (0x%X) \n", addr, tab_rq_registers[0], tab_rq_registers[0]);nb_fail++;}else {// 写入后进行读取rc = modbus_read_registers(ctx, addr, 1, tab_rp_registers);if (rc != 1) {printf("ERROR modbus_write_registers (%d) \n", rc);printf("Address = %d\n", addr);nb_fail++;}else {// 读取后进行比较if (tab_rq_registers[0] != tab_rp_registers[0]) {printf("ERROR modbus_write_registers\n");printf("Addr = %d, Val = %d (0x%X) != %d (0x%X\n", addr, tab_rq_registers[i], tab_rq_registers[i], tab_rp_registers[i], tab_rp_registers[i]);nb_fail++;}}}// 测试线圈寄存器的批量读写rc = modbus_write_registers(ctx, addr, nb, tab_rq_registers);if (rc != nb) {printf("ERROR modbus_write_registers (%d) \n", rc);printf("Address = %d, nb = %d\n", addr, nb);nb_fail++;}else {// 进行读取测试rc = modbus_read_registers(ctx, addr, nb, tab_rp_registers);if (rc != nb) {printf("ERROR modbus_read_registers (%d) \n", rc);printf("Address = %d, nb = %d\n", addr, nb);nb_fail++;}else{for ( i = 0; i < nb; i++){if (tab_rq_registers[i] != tab_rp_registers[i]) {printf("ERROR modbus_read_registers\n");printf("Addr = %d, Val = %d (0x%X) != %d (0x%X)\n", addr, tab_rq_registers[i], tab_rq_registers[i], tab_rp_registers[i], tab_rp_registers[i]);nb_fail++;}}}}// 功能码23(0x17)读写多个寄存器的测试rc = modbus_write_and_read_registers(ctx,addr, nb, tab_rw_rq_registers,addr, nb, tab_rp_registers);if (rc != nb) {printf("ERROR modbus_write_and_read_registers(%d)\n", rc);printf("Address = %d, nb = %d\n", addr, nb);nb_fail++;}else {for (i = 0; i < nb; i++) {if (tab_rp_registers[i] != tab_rw_rq_registers[i]){printf("ERROR modbus_read_and_write_registers READ\n");printf("Addr = %d, Val = %d (0x%X) != %d (0x%X)\n", addr, tab_rp_registers[i], tab_rw_rq_registers[i], tab_rp_registers[i], tab_rw_rq_registers[i]);nb_fail++;}}rc = modbus_read_registers(ctx, addr, nb, tab_rp_registers);if (rc != nb) {printf("ERROR modbus_read_registers(%d)\n", rc);printf("Address = %d, nb = %d\n", addr, nb);nb_fail++;}else {for (i = 0; i < nb; i++){if (tab_rw_rq_registers[i] != tab_rp_registers[i]) {printf("ERROR modbus_read_and_write_registers READ\n");printf("Addr = %d, Val = %d (0x%X) != %d (0x%X)\n", addr, tab_rp_registers[i], tab_rw_rq_registers[i], tab_rp_registers[i], tab_rw_rq_registers[i]);nb_fail++;}}}}}printf("Test: ");if (nb_fail) {printf("%d Fails\n", nb_fail);}else{printf("Success\n");}}// Free the memoryfree(tab_rq_bits);free(tab_rq_bits);free(tab_rq_registers);free(tab_rp_registers);free(tab_rw_rq_registers);// Close the connectionmodbus_close(ctx);modbus_free(ctx);return 0;
}
模拟器侧具体步骤
1.打开Modbus Slave仿真软件(上面的代码是用于模拟Master,所以不再需要打开Modbus poll),在导航栏点击【Connection】→【Connect】,进行连接配置,注意各项配置要与代码中一致,才能保证串口通信,具体配置如下图:
2.连接设置完成之后,再在Modbus Slave主窗口中选择【File】→【New】,在下方新建的子窗口的空白处右键,弹出菜单,选择【Slave Definition】,按照下图进行配置:
3.设置完成后,新建另一个窗口,使用相同的配置,只将Function改为03,现在便可以开始测试了。
4.回到Visual Studio面板,在编译代码之前需要修改一个配置。在VS顶部导航栏选择【工具】→【选项】,然后在【调试】下找到【符号】,将Microsoft符号服务器的勾打上(需要联网),不然有可能会运行失败,如下图:
5.右键该项目,点击生成,进行编译,编译完成后,点击启动,然后查看Modbus Slave中是否有变化,当然,你也可以在VS中打断点进行调试,一步一步理解本篇文章的代码逻辑。
写在最后
本篇文章介绍了具体的调试过程,如果帮到了您,我将万分荣幸,还请不要吝啬您小小的点赞和收藏。当然,如果需要的话,可以将整个项目文件下载下去(下载地址),支持一下用爱发电的博主。