QT应用篇 四、window编译LibModbus库并用QT编写一个Modbus主机 手把手教学

QT应用篇 四、window编译LibModbus库并用QT编写一个Modbus主机

QT应用篇

一、QT上位机串口编程
二、QML用Image组件实现Progress Bar 的效果
三、QML自定义显示SpinBox的加减按键图片及显示值效果
四、window编译LibModbus库并用QT编写一个Modbus主机



前言

记录用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.读入数据在这里插入图片描述

找到这三个文件
在这里插入图片描述
第一个 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的主机并和从机完成通讯

猜你喜欢

转载自blog.csdn.net/qq_41930631/article/details/135269498