前置工作
学了这么多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;
// RTU
ctx = 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 memory
free(tab_rq_bits);
free(tab_rq_bits);
free(tab_rq_registers);
free(tab_rp_registers);
free(tab_rw_rq_registers);
// Close the connection
modbus_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中打断点进行调试,一步一步理解本篇文章的代码逻辑。
写在最后
本篇文章介绍了具体的调试过程,如果帮到了您,我将万分荣幸,还请不要吝啬您小小的点赞和收藏。当然,如果需要的话,可以将整个项目文件下载下去(下载地址),支持一下用爱发电的博主。