基于QT5的libmodbus主从机实现

目录

1、前言

2、主机实现

3、从机实现


1、前言

本文介绍libmodbus如何在windows+QT5中实现,本文所实现的是:一个电脑做主机、多个下位机做从机。在实现的过程中,发现电脑与从机通信的时候软件界面会出现卡顿,卡顿的频率为数据发送的频率(即数据发送的那个时间点,界面会卡),加了多线程也这样,估计是libmodbus底层函数的问题,目前暂未改进。望有懂的大佬一起交流交流。。。。

运行环境:window10专业版,Qt5.12版本

参考博客:https://blog.csdn.net/zgrjkflmkyc/article/details/44855543

上面这篇博客实现需要通过mingw自带的msys工具来完成libmodbus中.c和.h的生成,然后还需要添加window所支持的文件。这里为了方便我已经将libmodbus所需要的所有文件打包上传。

https://download.csdn.net/download/qq_41121877/19730765

2、主机实现

1)、新建一个Qt5工程,在新建好的工程文件中添加压缩包中的文件

基于QT5的libmodbus主从机实现

2)、在工程中添加刚刚libmodbus文件夹中文件

基于QT5的libmodbus主从机实现

3)、修改.pro文件,在文件中加入串口及libmodbus所支持的库

基于QT5的libmodbus主从机实现

4)、修改.h文件,如下所示。代码中都有注释,稍微看一下就能看懂。

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "libmodbus/modbus.h"
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QDebug>
#include <QTimer>
#include <QStringList>


extern modbus_t *my_bus;   //定义第一个modbus类
extern uint16_t modbus_hold_reg[100];      //缓存读取到的数据
extern uint16_t modbus_input_reg[100];
extern int modbus_num;

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:

    void on_pushButton_clicked(bool checked);

    void modbus_update_text();

    void Serial_search_function();

    void on_pushButton_2_clicked();


private:
    Ui::MainWindow *ui;

    QStringList oldPortStringList;

    struct timeval my_time; //modbus超时时间

    QTimer time_serial;    //定义一个串口使用的定时器

    QTimer Time_one;       //modbus数据更新时钟
};


#endif // MAINWINDOW_H

5)、修改.c文件如下所示。代码中都有注释,稍微看一下就能看懂。

#include "mainwindow.h"
#include "ui_mainwindow.h"

modbus_t *my_bus;   //定义第一个modbus类
uint16_t modbus_hold_reg[100];      //缓存读取到的数据
uint16_t modbus_input_reg[100];
int modbus_num;  //modbus从机的个数

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    #if _MSC_VER >= 1600                        //中文乱码
    #pragma execution_character_set("utf-8")
    #endif

    time_serial.start(1000);
    connect(&time_serial, &QTimer::timeout, this, &MainWindow::Serial_search_function);
    connect(&Time_one,&QTimer::timeout,this,&MainWindow::modbus_update_text);                //modbus数据扫描时钟
}

MainWindow::~MainWindow()
{
    Time_one.stop();
    modbus_close(my_bus);
    modbus_free(my_bus);
    delete ui;
}

void MainWindow::on_pushButton_clicked(bool checked)
{
    if(checked)
    {
        if(ui->comboBox_name->currentText().isEmpty()==true)
        {
            ui->textEdit->setText(ui->textEdit->toPlainText().append("未设置设备号\r\n"));
            return;
        }

        modbus_num = ui->lineEdit->text().toUInt();
        if(modbus_num < 1)
        {
            ui->textEdit->setText(ui->textEdit->toPlainText().append("请选择从机个数\r\n"));
           return;
        }
        QByteArray namestring = ui->comboBox_name->currentText().toLatin1();
        char *str_name = namestring.data();
        uint modbus_baud = ui->comboBox_baud->currentText().toUInt();
        my_bus = modbus_new_rtu(str_name,modbus_baud,'N',8,1);
        modbus_set_slave(my_bus,1);//设置需要连接的从机地址
        modbus_connect(my_bus);
        my_time.tv_sec=0;
        my_time.tv_usec=1000000;                    //设置modbus超时时间为1000ms
        modbus_set_response_timeout(my_bus,uint32_t(my_time.tv_sec),uint32_t(my_time.tv_usec));
        modbus_set_error_recovery(my_bus,MODBUS_ERROR_RECOVERY_LINK);//设置延时超时后错误响应为睡眠

        Time_one.start(300);

        ui->pushButton->setText("关闭");
    }else {
        Time_one.stop();
        modbus_close(my_bus);
        modbus_free(my_bus);
        ui->pushButton->setText("开启");
    }
}

void MainWindow::modbus_update_text()
{

        static int modbus_slave_addr = 1;

        modbus_set_slave(my_bus,modbus_slave_addr);//设置需要连接的从机地址

        modbus_read_registers(my_bus,0,5,modbus_hold_reg);         //读取保持寄存器的第0位开始的前5位
    //    modbus_read_input_registers(my_bus,0,50,modbus_input_reg);  //读取输入寄存器的第0位开始的前5位

        modbus_slave_addr++;

        if(modbus_slave_addr > modbus_num)
        {
            modbus_slave_addr = 1;
        }

        ui->textEdit->setText(ui->textEdit->toPlainText().append(QString("modbus read : %1  %2  %3  %4  %5 \r\n ").arg(modbus_hold_reg[0]).arg(modbus_hold_reg[1]).arg(modbus_hold_reg[2]).arg(modbus_hold_reg[3]).arg(modbus_hold_reg[4])));

        ui->textEdit->moveCursor(QTextCursor::End);//设置滑块在最底部

//    MyThread->modbus_thread_work();

}


/*
 *函数名:Serial_search_function
 *函数功能:扫描串口函数
 *备注:绑定Time_clock定时器,1s中断
**/
void MainWindow::Serial_search_function()
{
    QStringList newPortStringList;
    const auto infos = QSerialPortInfo::availablePorts();
    for (const QSerialPortInfo &info : infos)
    {
        newPortStringList += info.portName();
    }
    //更新串口号
    if(newPortStringList.size() != oldPortStringList.size())
    {
        oldPortStringList = newPortStringList;
        ui->comboBox_name->clear();
        ui->comboBox_name->addItems(oldPortStringList);
    }
}

void MainWindow::on_pushButton_2_clicked()
{
    ui->textEdit->clear();
}

6)、实验结果

modbus从机:

安装了基于QT5的libmodbus主从机实现这个Modbus 从机工具,在其中开了3个modbus从机,设置他们hold寄存器的值,如下所示:

基于QT5的libmodbus主从机实现

modbus主机界面:

基于QT5的libmodbus主从机实现

该主机功能是:根据从机的个数,从ID为1开始读取从机设备寄存器的值,最多读到最大从机的个数,选择连接到主机的串口号,点击开始,就可以了。可以看到主机依次读取到了从机设备中设置号的寄存器值。

3、从机实现

第1)、2)、3)步骤参考主机实现部分。

4)、修改.h文件,如下所示。代码中都有注释,稍微看一下就能看懂。

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "libmodbus/modbus.h"
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QDebug>
#include <QTimer>
#include <QStringList>
#include <QThread>


extern modbus_t *my_bus;   //定义第一个modbus类


namespace Ui {
class MainWindow;
}


class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:

    void on_pushButton_clicked(bool checked);

    void Serial_search_function();

    void on_pushButton_2_clicked();

    void modbus_slave_work();

private:
    Ui::MainWindow *ui;

    QStringList oldPortStringList;

    QTimer time_serial;    //定义一个串口使用的定时器

    QTimer Time_one;       //modbus数据更新时钟

    modbus_mapping_t *mb_mapping = NULL;  //modbus相关的寄存器

};


#endif // MAINWINDOW_H

5)、修改.c文件如下所示。代码中都有注释,稍微看一下就能看懂。

#include "mainwindow.h"
#include "ui_mainwindow.h"

modbus_t *my_bus;   //定义第一个modbus类


MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    #if _MSC_VER >= 1600                        //中文乱码
    #pragma execution_character_set("utf-8")
    #endif


    time_serial.start(1000);
    connect(&time_serial, &QTimer::timeout, this, &MainWindow::Serial_search_function);
    connect(&Time_one,&QTimer::timeout,this,&MainWindow::modbus_slave_work);                //modbus从机数据扫描时钟
}

MainWindow::~MainWindow()
{
    Time_one.stop();
    modbus_close(my_bus);
    modbus_free(my_bus);

    delete ui;
}

void MainWindow::on_pushButton_clicked(bool checked)
{
    if(checked)
    {
        if(ui->comboBox_name->currentText().isEmpty()==true)
        {
            ui->textEdit->setText(ui->textEdit->toPlainText().append("未设置设备号\r\n"));
            return;
        }

        QByteArray namestring = ui->comboBox_name->currentText().toLatin1();
        char *str_name = namestring.data();
        uint modbus_baud = ui->comboBox_baud->currentText().toUInt();
        my_bus = modbus_new_rtu(str_name,modbus_baud,'N',8,1);
        modbus_set_slave(my_bus,1);                    //设置从机地址为1
        modbus_connect(my_bus);

        //寄存器map初始化
       mb_mapping = modbus_mapping_new(MODBUS_MAX_READ_BITS, MODBUS_MAX_READ_BITS,
                                       MODBUS_MAX_READ_REGISTERS, MODBUS_MAX_READ_REGISTERS); //依次设置 bits、input_bits、registers、input_registers寄存器的大小,他们默认起始地址均为0
       if (mb_mapping == NULL) {
           fprintf(stderr, "Failed to allocate the mapping: %s\n",
                   modbus_strerror(errno));
           modbus_free(my_bus);
           return;
       }

       mb_mapping->tab_input_registers[1] = 1; //设置一下hold寄存器的值
       mb_mapping->tab_input_registers[2] = 2;
       mb_mapping->tab_input_registers[3] = 3;
       mb_mapping->tab_input_registers[4] = 4;
       mb_mapping->tab_input_registers[5] = 5;

        Time_one.start(300);
        ui->pushButton->setText("关闭");
    }else {
        Time_one.stop();
        modbus_close(my_bus);
        modbus_free(my_bus);
        ui->pushButton->setText("开启");
    }
}

void MainWindow::modbus_slave_work()
{
    int rc;

    uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
    //轮询接收数据,并做相应处理
    rc = modbus_receive(my_bus, query);
    if (rc > 0) {
        modbus_reply(my_bus, query, rc, mb_mapping);
    } 
}



/*
 *函数名:Serial_search_function
 *函数功能:扫描串口函数
 *备注:绑定Time_clock定时器,1s中断
**/
void MainWindow::Serial_search_function()
{
    QStringList newPortStringList;
    const auto infos = QSerialPortInfo::availablePorts();
    for (const QSerialPortInfo &info : infos)
    {
        newPortStringList += info.portName();
    }
    //更新串口号
    if(newPortStringList.size() != oldPortStringList.size())
    {
        oldPortStringList = newPortStringList;
        ui->comboBox_name->clear();
        ui->comboBox_name->addItems(oldPortStringList);
    }
}

void MainWindow::on_pushButton_2_clicked()
{
    ui->textEdit->clear();
}

6)、实验结果

modbus主机:

使用这个软件基于QT5的libmodbus主从机实现,设置读取的从机地址为1,读取的寄存器为0地址开始的hold寄存器的前5位。根据QT中前面设置的从机寄存器的值,可以观察到以下结果:

基于QT5的libmodbus主从机实现

modbus从机:

自己写的,偷了点懒,在上一个主机软件中改了,就没改ui界面,设置好设备号和波特率直接点击开始就好了。

基于QT5的libmodbus主从机实现

 

注:这里QT从机部分,使用的是定时器轮询扫描接收主机的消息,这里最好改成把扫描的函数放在另外一个线程中,因此下一篇我会把多线程在libmodbus中的使用说一下。

上一篇:ARM体系架构——MMU【转】


下一篇:2020年冬季PAT乙级题解(C语言)