前题
项目需要使用到 Modbus 协议,不想自己写一个协议栈,虽然 Modbus 协议比较简单。
可用的 Modbus 协议栈
libModubs
开源代码。官网地址为:https://libmodbus.org。
freeModubs
开源代码。官网地址为:https://www.embedded-solutions.at/en/freemodbus/。可以从 github 中下载,https://github.com/cwalter-at/freemodbus。
emModubs
segger 公司的。商业代码。不出钱是没法用的。https://www.segger.com/downloads/emmodbus/。
个人选择
emModbus,商业代码。穷逼买不起,放弃。
freeModbus以前用过,但是好久没有更新了,放弃。最近更新是三年前。
libModbus还在正常维护。最新版本是 3.1.6,更新日期为 2019 年 9 月。
编译 libModbus
环境
Win10系统,编译器为 Visual Studio 2019。没有使用 MSYS2 或者 CYGWin 来编译。
生成 sln
这个不是我手动生成的,老版本里有。从老版本中拷贝过来即可。对应的文件在 libmodbus-3.1.6/src/win32 这个目录。
编译 DLL
错误1:缺少 config.h
将 libmodbus-3.1.6 目录下 config.h.in 拷贝过来,改名为 config.in 即可。
错误2:LNK111
如下:
是因为设置的版本号为 1.0.0 导致的语法错误。
解决方案
删除版本号就可以解决问题。在设置(项目>设置>链接器)上删除“版本”条目。如下图。
删除上图中的 1.0.0,即可解决这个问题。
结果
如上图所示,modbus.dll 和 modbus.lib 就生成成功。
测试libModbus
必须软件
Modbus Master
推荐使用 Modbus Poll 软件。
Modbus Slave
推荐使用 Modbus Slave 软件。
虚拟串口
如果没有现成的 Modbus 设备或者第二台计算机。那么虚拟串口就是必须的。
推荐使用:vspd虚拟串口软件。该软件可以虚拟出配对的两个串口。这样我们就可以运行 Modbus 软件来测试了。
软件安装好,运行起来如下图:
如上图,我的工控机有物理串口
7
7
7 个。虚拟串口一对。如果没有虚拟串口,选择添加即可。
虚拟串口测试
Modbus Master
如上图,打开 COM9,配置为 9600 N 8 1,使用 Modbus RTU 协议。
Modbus Slave
如上图,使用 COM10,9600 N 8 1,Modbus RTU 协议。
这样就可以发现两个软件成功开始通信。
Modbus Master 测试
VSDP + Modbus Poll + 自己的程序作为一个主机。这样我们只需要向串口发送 03 00 00 00 0A C5 CD 这个指令就可以了。
添加 Modbus Master RTU 项目
在 Visual Studio 2019 中选择“文件”->“新建”->“项目”。选择 C++ 空项目。配置如下图所示:
项目建立后。如下图所示。
RTU代码
在 RTU_Master 中添加一个空白的 cpp 文件,名字为 rtu_master.cpp,并加入到项目中。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
//#include <windows.h>
#include "modbus.h"
#pragma comment(lib, "modbus.lib")
#pragma comment(lib, "ws2_32.lib")
int main(int argc, char* argv[]) {
modbus_t* ctx;
int rc;
uint16_t tab_reg[64];
//打开
ctx = modbus_new_rtu("COM9", 9600, 'N', 8, 1);
if (modbus_connect(ctx) == -1) {
fprintf(stderr, "Connection failed: %s\n",
modbus_strerror(errno));
modbus_free(ctx);
}
//打开调试模式
modbus_set_debug(ctx, 1);
modbus_set_response_timeout(ctx, 1, 0);//设置响应时间
//设置从机地址
modbus_set_slave(ctx, 1);
for (int i = 1; i <= 100; i++) {
rc = modbus_read_registers(ctx, 0x00, 0x0A, tab_reg);
//rc = modbus_read_input_registers(ctx, 0x00, 0x0A, tab_reg);
if (rc == -1) {
fprintf(stderr, "%s\n", modbus_strerror(errno));
return -1;
}
//输出结果
for (int j = 0; j < rc; j++) {
printf("reg[%d]=%d (0x%X)\n", i, tab_reg[i], tab_reg[i]);
}
Sleep(1000);
}
//关闭设备
modbus_close(ctx);
modbus_free(ctx);
return 0;
}
配置头文件路径
这个程序中,我们需要使用到 modbus.h。所以需要指定对应的路径。如下图所示。
配置库文件路径
这个程序中,我们需要使用到 modbus.lib。所以需要指定对应的路径。如下图所示。
配置DLL文件路径
这个程序中,我们需要使用到 modbus.dll。所以需要指定对应的路径。如下图所示。
运行结果
上图的数据解码有问题。先不管了。
添加 Modbus Master TCP 项目
TCP代码
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
//#include <windows.h>
#include "modbus.h"
#ifdef _WIN32
# include <winsock2.h>
#else
# include <sys/socket.h>
#endif
#pragma comment(lib, "modbus.lib")
#pragma comment(lib, "ws2_32.lib")
int main(int argc, char* argv[]) {
modbus_t* ctx;
int rc;
uint16_t tab_reg[64];
//打开
ctx = modbus_new_tcp("127.0.0.1", 1502);
//打开调试模式
modbus_set_debug(ctx, 1);
modbus_set_response_timeout(ctx, 1, 0);//设置响应时间
//设置从机地址
modbus_set_slave(ctx, 1);
if (modbus_connect(ctx) == -1) {
fprintf(stderr, "Connection failed: %s\n",
modbus_strerror(errno));
modbus_free(ctx);
}
#if 0
int server_socket = modbus_tcp_listen(ctx, 1);
if (server_socket == -1) {
fprintf(stderr, "Unable to listen TCP: %s\n",
modbus_strerror(errno));
modbus_free(ctx);
}
//modbus_tcp_accept(ctx, &server_socket);
#endif
for (int i = 1; i <= 100; i++) {
rc = modbus_read_registers(ctx, 0x00, 0x0A, tab_reg);
//rc = modbus_read_input_registers(ctx, 0x00, 0x0A, tab_reg);
if (rc == -1) {
fprintf(stderr, "%s\n", modbus_strerror(errno));
return -1;
}
//输出结果
for (int j = 0; j < rc; j++) {
printf("reg[%d]=%d (0x%X)\n", i, tab_reg[i], tab_reg[i]);
}
Sleep(1000);
}
//if (server_socket != -1) {
//close(server_socket);
//}
//关闭设备
modbus_close(ctx);
modbus_free(ctx);
return 0;
}
运行结果
上图的数据解码有问题。先不管了。
Modbus Slave 测试
待补充。今天先测试到这里,还有别的事情需要去搬砖。