QT应用篇 四、window编译LibModbus库并用QT编写一个Modbus主机
QT应用篇
一、QT上位机串口编程
二、QML用Image组件实现Progress Bar 的效果
三、QML自定义显示SpinBox的加减按键图片及显示值效果
四、window编译LibModbus库并用QT编写一个Modbus主机
五、手把手教学用QT编写TCP上位机并显示温湿度
文章目录
前言
记录用qt自己做Modbus主机的的内容
一、下载LibModbus库和编译软件MSYS
方法一
下载libmodbus库和软件
1.源文件地址:https://github.com/stephane/libmodbus
2.libmodbus官网:https://libmodbus.org/
3.MSYS官网: https://www.msys2.org/
自行下载libmodbus和MSYS2软件和编译
方法二
我把编译好的库和软件放在云盘自行下载
链接:https://pan.baidu.com/s/1khOX6GMvMdGC3l-zH-rwqQ?pwd=2023
提取码:2023
–来自百度网盘超级会员V5的分享
直接跳过MSYS2编译过程
二、开始编译modbus库
1.修改镜像源
如果有科学上网理论上可以跳过此步骤
软件默认安装地址是 C:\msys64\etc\pacman.d
2.读入数据![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/492e670c69b095255abaaa22d5d19ebd.png)
找到这三个文件
第一个 mirrorlist.mingw32
用记事本打开修改Primary
## 2-bit Mingw-w64 repository mirrorlist
## Primary
Server = http://mirrors.ustc.edu.cn/msys2/mingw/i686/
Server = http://repo.msys2.org/mingw/i686
Server = http://downloads.sourceforge.net/project/msys2/REPOS/MINGW/i686
Server = http://www2.futureware.at/~nickoe/msys2-mirror/i686/
第二个 mirrorlist.mingw64
用记事本打开修改Primary
## 64-bit Mingw-w64 repository mirrorlist
## Primary
Server = http://mirrors.ustc.edu.cn/msys2/mingw/x86_64/
Server = http://repo.msys2.org/mingw/x86_64
Server = http://downloads.sourceforge.net/project/msys2/REPOS/MINGW/x86_64
Server = http://www2.futureware.at/~nickoe/msys2-mirror/x86_64/
Server = http://mirror.bit.edu.cn/msys2/REPOS/
第三个 mirrorlist.msys
用记事本打开修改Primary
## MSYS2 repository mirrorlist
## Primary
Server = http://mirrors.ustc.edu.cn/msys2/msys/$arch/
Server = http://repo.msys2.org/msys/$arch
Server = http://downloads.sourceforge.net/project/msys2/REPOS/MSYS2/$arch
Server = http://www2.futureware.at/~nickoe/msys2-mirror/msys/$arch/
3.安装编译库
打开 MSYS2 MINGW64
更新msys2
pacman -Syu
更新完成后按Y回车
输入Y后按下回车键,MSYS会自动关闭
重新打开MSYS2 MINGW64
pacman -S mingw-w64-x86_64-gcc
按Y回车
安装automake 并按Y回车
pacman -S automake
安装libtool 按Y 回车
安装完成
4.编译
1.打开libmodbus的解压文件,找到的autogen.sh文件。
我的目录D:\BaiduNetdiskWorkspace\libmodbus-master
cd D:/BaiduNetdiskWorkspace/libmodbus-master
2.运行脚本
./autogen.sh
报错了
要安装pacman -S autoconf-wrapper 库
pacman -S autoconf-wrapper
安装完成重新运行脚本
./autogen.sh
此时页面提示 运行configure了
./configure
完成
3.编译完成并复制库文件
进入编译所在目录
新建一个目录用于存放libmodbus的c文件和h文件
在根目录找到config.h
文件复制到新创建的文件夹
在src目录内复制所有的c文件和h文件到新创建的文件夹
三、创建QT工程并添加文件
1.打开QT工程文件pro文件所在目录 把刚刚新建的libmodbus文件夹复制到这里
2.搜索dll文件
打开搜索文件的软件输入ws2_32.dll
如果没有everythings的话就用自带的资源管理搜索
找到体积最大的然后复制到qt工程目录下
3.QT工程添加库文件和dll文件
1.先添加c文件和h文件
全选添加
添加后如图
2.修改pro文件添加dll库
LIBS += -Ldll -lws2_32
QT += core gui serialport
然后modbuspoll.h文件include一下
#include <libmodbus/config.h>//和你的modbus库文件名称有关
编译一下发现报错 说找不到config.h
把报错文件的#include <config.h>
全改成#include <libmodbus/config.h>
修改完成后再运行,就没有错误了
可以成功运行说明导入libmodbus成功
接下来可以开始编写modbus的主机的内容了
四、编写RTU Master端(主机端)
注:以下内容编程需要对modbus的内容有一些熟悉或者是做过无界面的modbus编程,否则会比较难以理解
1.编写UI
根据功能顺便把widget的名称修改好,方便后续编程
2、添加需要的内容的头文件
#include <QMainWindow>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QDebug>
#include <QTimer>
#include <QTime>
#include <QDate>
#include <QMessageBox>
#include <QElapsedTimer>
#include <libmodbus/config.h>
#include <libmodbus/modbus.h>
3.定时器、按键、串口实例化等等
定时器
定时器的主要作用是用于定期扫描串口
开一个100ms
的定时器和1000ms
定时器
注册结构体
QTimer *mstimer;
QTimer *mstimer1;
实例化并连接信号槽
mstimer = new QTimer(this);
mstimer->start(1000);//设置定时器1000ms
mstimer1 = new QTimer(this);
mstimer->start(100);//设置定时器100ms
connect(mstimer,SIGNAL(timeout()),this,
SLOT(timer1000ms()));
connect(mstimer,SIGNAL(timeout()),this,
SLOT(timer100ms()));
写槽函数
void ModbusPoll::timer1000ms()
{
if (!ui->btn_start->isChecked()) //按键未按下
{
choose_index = ui->cbx_dev_name->currentIndex();
reflashSerialPort();//刷新串口
}
//qDebug()<<QTime::currentTime()<<"按键是否被按下"<<ui->btn_start->isChecked();
}
void ModbusPoll::timer100ms()
{
}
串口
UI初始化后扫描串口一次
void ModbusPoll::scanSerialPort()
{
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
{
{
index++;
ui->cbx_dev_name->addItem(info.portName());
DEBUG<<QTime::currentTime()<<"已发现串口:"<<info.portName();
}
}
}
定期扫描串口并添加到 下拉组件
void ModbusPoll::reflashSerialPort()
{
ui->cbx_dev_name->clear();
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
{
{
ui->cbx_dev_name->addItem(info.portName());
}
}
ui->cbx_dev_name->setCurrentIndex(choose_index);//记录上次手动选择的串口
//qDebug()<<QTime::currentTime()<<"choose_index:"<<choose_index;
}
按键部分操作
按键主要有
1.初始化modbus主机并连接/断开从机
2.发送寄存器数据
3.获取从机寄存器数据
4.清除发送和接收
//启动停止
void ModbusPoll::on_btn_start_toggled(bool checked)
{
btn_state = ui->btn_start->isChecked();
if (checked == 1)
{
ui->btn_start->setText("停止");
DEBUG<<QTime::currentTime()<<"按下";
ui->cbx_dev_name->setEnabled(false);
modBusInit();
}
else
{
choose_index = ui->cbx_dev_name->currentIndex();//设置上次选择的串口
ui->btn_start->setText("启动");
DEBUG<<QTime::currentTime()<<"弹起";
ui->cbx_dev_name->setEnabled(true);
closeModBus();
}
}
//发送
void ModbusPoll::on_btn_tx_clicked()
{
if (ui->btn_start->isChecked()) //启动按键按下
{
modbusWriteRegister(ctx, write_addr, 10, &txdata[0]);
QString str = "成功发送从机数据 slave id:" + QString::number(slave_id1)
+" addr:" +QString::number(write_addr)
+" d_nub " +QString::number(sizeof (txdata))
+" data ";
for (int8_t a = 0;a<10;a++)
{
str+= " " +QString::number(txdata[a]);
}
ui->textEdit_tx->append(str);
}
}
//清除发送
void ModbusPoll::on_btn_tx_clear_clicked()
{
ui->textEdit_tx->clear();
}
//接收
void ModbusPoll::on_btn_rx_clicked()
{
if (ui->btn_start->isChecked()) //启动按键按下
{
modbusReadRegisters(ctx,read_addr,10,&rxdata[0]);
QString str = "成功获取从机回复 slave id:" + QString::number(slave_id1)
+" addr:" +QString::number(read_addr)
+" d_nub " +QString::number(sizeof (rxdata))
+" data ";
for (int8_t a = 0;a<10;a++)
{
str+= " " +QString::number(rxdata[a]);
}
ui->textEdit_rx->append(str);
}
}
//清除接收
void ModbusPoll::on_btn_rx_clear_clicked()
{
ui->textEdit_rx->clear();
}
4.初始化ModBus
RTU-Master的代码流程:
1.初始化并生成modbus_t结构体;
在modbuspoll.h 有个宏定义 #define DEBUG qDebug()
用DEBUG来代替qDebug()
private:
modbus_t *ctx;
typedef struct
{
uint32_t usec = 1000000;
uint8_t sec = 0;
} Modbus_t;
Modbus_t t_modbus ;
在modbuspoll.cpp
std::string str = ui->cbx_dev_name1->currentText().toStdString();
device_name = str.c_str();
//配置端口,""内写的是端口,Win系统下是COM*,Ubuntu是/dev/ttyusb*
ctx = modbus_new_rtu(device_name,115200,'N',8,2);
2.设置从机端的ID;
在modbuspoll.cpp
modbus_set_slave(ctx,slave_id1);
3.启动调试模式;
//设置响应时间1000ms
modbus_set_response_timeout(ctx,t_modbus.sec,t_modbus.usec);
//启动调试模式
modbus_set_debug( ctx, 1);
4.建立modbus连接;
if (modbus_connect(ctx) != -1)
{
DEBUG<<QTime::currentTime()<<(stderr,"*****************Connection Slave ")<<slave_id1 <<"success******************";
}
else if (modbus_connect(ctx) == -1)
{
DEBUG<<QTime::currentTime()<<(stderr,"*****************Connection failed ")<<slave_id1 <<modbus_strerror(errno);
modbus_free(ctx);
}
合并
void ModbusPoll::modBusInit()
{
qDebug()<<QTime::currentTime()<<"choose_index:"<<choose_index;
std::string str = ui->cbx_dev_name->currentText().toStdString();
device_name = str.c_str();
//配置端口,""内写的是端口,Win系统下是COM*,Ubuntu是/dev/ttyusb*
ctx = modbus_new_rtu(device_name,115200,'N',8,2);
//设置响应时间1000ms
modbus_set_response_timeout(ctx,t_modbus.sec,t_modbus.usec);
//启动调试模式
modbus_set_debug( ctx, 1);
//设置从站地址
modbus_set_slave(ctx,slave_id1);
//连接从机
if (modbus_connect(ctx) != -1)
{
DEBUG<<QTime::currentTime()<<(stderr,"*****************Connection Slave ")<<slave_id1 <<"success******************";
}
else if (modbus_connect(ctx) == -1)
{
DEBUG<<QTime::currentTime()<<(stderr,"*****************Connection failed ")<<slave_id1 <<modbus_strerror(errno);
modbus_free(ctx);
}
modbusWriteRegister(ctx, write_addr, sizeof (testdata), &testdata[0]);
}
5.申请动态内存(无);
6.生成随机数,其他地方是读取寄存器/线圈的输入;
自定义数组
7.线圈寄存器的单个读写/批量读写/保持寄存器的单个读写/批量读写/读取多个寄存器;
写寄存器值
槽函数:
/*************************************************************************
**函数名: modbusWriteRegister
**描 述: 读取寄存器值
**输 入: ctx : modbus contexts;
addr : 从机寄存器地址;
w_num : 写入字节数
w_data : 寄存器值
**输 出: void
**返 回: 成功:0, 失败:-1
**********************************************************************************/
int8_t ModbusPoll::modbusWriteRegister(modbus_t *ctx , uint16_t addr, uint16_t w_num, uint16_t w_data[])
{
int8_t rc = 0;
rc = modbus_write_registers(ctx, addr, w_num, w_data);
if (rc == -1)
{
DEBUG<<"Error writing registers: "<< modbus_strerror(errno);
}
else if (rc != -1)
{
DEBUG<<"registers addr: "<<addr;
DEBUG<<QTime::currentTime()<<"write reg";
for (uint16_t i=0; i < rc; i++)
{
DEBUG<< w_data[i];
}
DEBUG<<"*************write success*****************" ;
}
return rc;
}
读取寄存器值
槽函数:
/*************************************************************************
**函数名: modbusReadRegisters
**描 述: 读取寄存器值
**输 入: ctx : modbus contexts;
addr : 从机寄存器地址;
w_num : 读取字节数
w_data : 寄存器值
**输 出: void
**返 回: 成功:0, 失败:-1
**********************************************************************************/
int8_t ModbusPoll::modbusReadRegisters(modbus_t *ctx, uint16_t addr, uint16_t w_num, uint16_t w_data[])
{
int8_t rc = 0;
rc = modbus_read_registers(ctx, addr, w_num, w_data);
if (rc == -1)
{
DEBUG<<QTime::currentTime()<<" Error reading registers: "<< modbus_strerror(errno);
}
else if (rc != -1)
{
DEBUG<<QTime::currentTime()<<" registers addr:"<< addr;
DEBUG<<QTime::currentTime()<<"read reg : ";
for (int i=0; i < rc; i++)
{
DEBUG<<""<<w_data[i] ;
}
DEBUG<<"*************read success***************** " ;
}
return rc;
}
8.释放内存(无);
9.关闭modbus连接;
10.释放modbus结构体
槽函数:
void ModbusPoll::closeModBus()
{
DEBUG<<QTime::currentTime()<<"关闭主机" ;
modbus_close(ctx);
modbus_free(ctx);
}
五、测试结果
软件:Modbus_Slave
运行截图
打印输出:
2.测试结果
因为开了调试模式
当应用停止的时候还能看到一些数据
可以看到modbus主机已经可以和从机通讯,并且获取从机的寄存器的数据了
总结
1.用MYSY2编译modbus库
2.建立qt工程并添加modbus库
3.建立modbus的主机并和从机完成通讯