目录
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工程,在新建好的工程文件中添加压缩包中的文件
2)、在工程中添加刚刚libmodbus文件夹中文件
3)、修改.pro文件,在文件中加入串口及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从机:
安装了这个Modbus 从机工具,在其中开了3个modbus从机,设置他们hold寄存器的值,如下所示:
modbus主机界面:
该主机功能是:根据从机的个数,从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主机:
使用这个软件,设置读取的从机地址为1,读取的寄存器为0地址开始的hold寄存器的前5位。根据QT中前面设置的从机寄存器的值,可以观察到以下结果:
modbus从机:
自己写的,偷了点懒,在上一个主机软件中改了,就没改ui界面,设置好设备号和波特率直接点击开始就好了。
注:这里QT从机部分,使用的是定时器轮询扫描接收主机的消息,这里最好改成把扫描的函数放在另外一个线程中,因此下一篇我会把多线程在libmodbus中的使用说一下。