libmodbus在Windows端Qt 5上的使用注意事项
本人在制作软件时,要用到modbus通讯,就找了有关modbus通讯的资料,但是看过后觉得有些问题跟别人遇到的不太一样,所以就自己整理一下。
libmodbus库文件的下载和编译
libmodbus作为modbus的库,在官网上可直接下载,网上资料建议都是下载libmodbus-3.0.6.tar.gz版本,不过在官网上只找到了3.1.6的版本,介绍讲是长期稳定的,遂下载。解压libmodbus压缩包,是两个文件夹,libmodbus-3.1.6和PaxHeaders.27399。打开libmodbus-3.1.6文件夹,找到configure配置脚本,需要利用工具生成config.h文件。
下载和安装msys工具
在网上搜了一些资料,其中有一篇博客讲解的比较详细,我是照他的步骤一步步来的。网址我贴在下边了:
我是直接在github上下载msys的,资源挺多,我选择的是msys2-installer,主要是电脑是64位的,就选择了64位的安装包,下载很快,90M也就十几秒。双击exe文件,安装即可,比想象中的要简单,安装完成后,在开始菜单栏,发现有三种启动方式,先不用管它,随便一种方式打开;
下载网址:https://github.com/msys2/msys2-installer
我是根据自己的需求,选择性的安装了功能,分别如下:
**1.**实现 cd +文件名进入目录:打开/etc/profile文件,添加一个关键变量:“export MSYS="winsymlinks:lnk”
**2.**配置国内镜像,方便快捷下载资源:在/etc/pacman.d文件中对3个镜像文件mirrorlist文件进行修改,我采用的是博主挂出的配置。
**3.**安装git和vim编辑器:有点像linux了, 安装命令:pacman -S git;
**4.**安装配置zsh:在linux下,默认采用的是bash,但是现在大多数人都用zsh,方便快捷。安装zsh的命令:pacman -S zsh ;zsh主题采用oh-my-zsh,需要链接到相应的github网址,进行下载,我采用的是博主发的第二种途径,有效。
**5 .**修改默认shell为zsh:在msys2_shell.cmd文件中,将LOGINSHELL的变量,改为zsh,则启动msys2后自动进入zsh,并自己选择oh-my-zsh中的主题,我采用了ys。采用vim编辑器在根目录下,打开zshrc文件,修改其中的ZSH_THEME的名字,完成修改。
**6 .**最重要的部分:gcc的安装和环境配置:我安装的是mingw64 mingw-x86_64-gcc版本。安装命令:pacman -S mingw -w64-x86_64-gcc.安装完成。
config.h的生成
打开msys2,找到之前解压文件下的configure文件,并运行,然后将libmodbus3.1.6文件夹下src文件夹中所有的.c和.h文件复制到一个空文件夹中,将生成的config.h文件也复制进去,将文件夹命名为libmodbus。至此,libmodbus库文件生成。
Qt 新建项目
在Windows端打开Qt,我的Qt的版本是5.13.2,QtCreater的版本是4.10.1,打开后,新建工程文件后,在添加文件选项中,将上述libmodbus文件中所有的文件选中,添加进去,特别需要注意的是config.h路径的问题,config.h的路径为你的libmodbus文件中config.h的路径,因此需要对比如modbus-private.h、modbus.c等头文件中含有config.h路径的文件。
添加dll文件
此外,modbus库依赖于window系统的ws2_32.dll库文件,在c盘中搜索,复制到所建立的项目文件夹下,命名为dll文件夹,并在项目文件.pro中添加“LIBS += -Ldll -lws2_32”,完成,开始运行程序。
测试代码
测试代码是参考一篇博主的,但是博主贴的代码是5年前的,另外只贴了工程文件.pro和.cpp文件,所以我在现有编译的libmodbus库的基础上,根据博主之间的代码做了些调整,因为在运行的过程中,发现了modbus.h文件中的一些API函数的定义发生了变,导致Qt文件在编译的过程中出现了一些错误。
以下是5年前博主生成的libmodbus库中modbus.h文件中的关于响应超时的API是这样的:
void modbus_get_response_timeout(modbus_t *ctx, struct timeval *timeout);
void modbus_set_response_timeout(modbus_t *ctx, const struct timeval *timeout);
然而我现在编译出的libmodbus库中modbus.h文件中关于响应超时API的定义是这样的:
MODBUS_API int modbus_get_response_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec);
MODBUS_API int modbus_set_response_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec);
首先是返回值的数据类型不一致,然后就是函数的参数不一致,所以在引用博主的代码测试时,需要做一些额外的修改。
1.工程文件代码 libmodbus.pro
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
LIBS += -Ldll -lws2_32
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
H:/libmodbus/modbus-data.c \
H:/libmodbus/modbus-rtu.c \
H:/libmodbus/modbus-tcp.c \
H:/libmodbus/modbus.c \
main.cpp \
mainwindow.cpp
HEADERS += \
H:/libmodbus/config.h \
H:/libmodbus/modbus-private.h \
H:/libmodbus/modbus-rtu-private.h \
H:/libmodbus/modbus-rtu.h \
H:/libmodbus/modbus-tcp-private.h \
H:/libmodbus/modbus-tcp.h \
H:/libmodbus/modbus-version.h \
H:/libmodbus/modbus.h \
mainwindow.h
FORMS += \
mainwindow.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${
TARGET}/bin
else: unix:!android: target.path = /opt/$${
TARGET}/bin
!isEmpty(target.path): INSTALLS += target
2.主窗口头文件 mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include<QPushButton>
#include "H:/libmodbus/modbus.h"
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
struct timeval
{
uint32_t tv_sec;
uint32_t tv_usec;
};
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
QPushButton *pbRtu;
QPushButton *pbTcp;
private slots:
void doRtuQuery();
void doTcpQuery();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
3.mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QVBoxLayout>
#include <QMessageBox>
#include "H:/libmodbus/modbus.h"
#include <QPushButton>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent),ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->resize(200, 60);
this->setWindowTitle("libmodbus slave test");
pbRtu=new QPushButton("以modbus rtu方式读取地址1中前20个寄存器值", this);
pbRtu->resize(200, 30);
pbTcp=new QPushButton("以modbus tcp方式读取地址1中前20个寄存器值", this);
pbRtu->resize(200, 30);
QWidget *w=new QWidget();
QVBoxLayout *layout=new QVBoxLayout();
layout->addWidget(pbRtu);
layout->addWidget(pbTcp);
w->setLayout(layout);
this->setCentralWidget(w);
connect(pbRtu, SIGNAL(clicked()), this, SLOT(doRtuQuery()));
connect(pbTcp, SIGNAL(clicked()), this, SLOT(doTcpQuery()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::doRtuQuery()
{
//RTU
modbus_t *mb;
uint16_t tab_reg[32]={
0};
mb = modbus_new_rtu("COM3", 9600, 'N', 8, 1); //相同的端口只能同时打开一个
modbus_set_slave(mb, 1); //设置modbus从机地址
modbus_connect(mb);
struct timeval t;
t.tv_sec=0;
t.tv_usec=1000000; //设置modbus超时时间为1000毫秒
modbus_set_response_timeout(mb, t.tv_sec,t.tv_usec);
int regs=modbus_read_registers(mb, 0, 10, tab_reg);
QMessageBox::about(NULL, "报告", QString("RTU 读取寄存器的个数: %1").arg(regs));
modbus_close(mb);
modbus_free(mb);
}
void MainWindow::doTcpQuery()
{
//TCP
modbus_t *mb;
uint16_t tab_reg[32]={
0};
mb = modbus_new_tcp("127.0.0.1", 502); //由于是tcp client连接,在同一个程序中相同的端口可以连接多次。
modbus_set_slave(mb, 1); //从机地址
modbus_connect(mb);
struct timeval t;
t.tv_sec=0;
t.tv_usec=1000000; //设置modbus超时时间为1000毫秒,注意:经测试,如果没有成功建立tcp连接,则该设置无效。
modbus_set_response_timeout(mb, t.tv_sec,t.tv_usec);
int regs=modbus_read_registers(mb, 0, 10, tab_reg);
QMessageBox::about(NULL, "报告", QString("TCP 读取寄存器的个数: %1").arg(regs));
modbus_close(mb);
modbus_free(mb);
}