Bootstrap

手把手教你制作一个温湿度上位机(串口通信)

制作一个串口温湿度上位机

  1. 串口接收数据
  2. 文本框实时显示温湿度数据
  3. 绘制实时变化曲线

1、新建一个工程

1.1、打开QT软件(我的QT版本是QT5.9.0),点击“+New Project”新建一个新工程,项目选择“Application”,具体模板选择“Qt Widget Application”,然后点击“Choose…”;
在这里插入图片描述
1.2、设置项目名称和项目创建路径;
切记:名称和创建路径均不能出现中文字符。
在这里插入图片描述
1.3、Kit Selection选择“Desktop Qt 5.9.0 MinGW 32bit”;在这里插入图片描述
1.4、类名自定义一个,由于用不到菜单栏,因此基类选择“QWidget”,当然“QDialog”也可以,具体看需要什么。下面来解释一下这三个基类的区别是啥:
(1)QMainWindow:主窗口类,主窗口具有主菜单栏、工具栏和状态栏,类似于一般的应用程序的主窗口;
(2)QWidget:所有具有可视界面类的基类,选择QWidget创建的界面对各种界面组件都可以支持;
(3)QDialog:对话框类,可建立一个基于对话框的界面。
在这里插入图片描述
1.5、最后点击完成即可生成一个新的工程;
在这里插入图片描述

2、具体设计

2.1、建立完新工程以后,先编译一遍,编译可以点击左下方的编译按键,也可以“CTRL+R”编译。编译完成后,便会出现一个空白界面(Widget);在这里插入图片描述
编译完成后,双击Forms下的widget.ui进入ui界面设置界面。
在这里插入图片描述
在这里插入图片描述
2.2、进行界面设计:
(1)、放置几个文本框用来显示温湿度数据;在这里使用到了QT的Line Edit控件;
在这里插入图片描述
然后对这两个文本框进行命名,方便编写代码的时候不会弄混淆;
首先双击需要更改的文本框(LineEdit),然后在属性那里将该文本框的值改为自己的;例如,我第一个需要显示温度数据,则将其改为line_Temp;第二个需要显示湿度数据,则将其改为line_Humi;
在这里插入图片描述
(2)、在两个文本框前面添加各自的标题:在这里需要用到QT的label控件;
在这里插入图片描述
双击添加的Label更改里面的文字信息;
同样为了方便编程将这两个label的属性名称依次改成lab_Temp、lab_Humi;
在这里插入图片描述
但是这样有些人又会觉得字太小了,字体也不是自己喜欢的类型(比如我就喜欢楷体),怎么办呢?
在这个地方可以设置文本框的长宽大小;
在这里插入图片描述
在这里可以设置字体的大小和字体类型:其中字体族设置字体类型,点大小设置字体大小。
在这里插入图片描述
(3)、为了整齐,咱们将这四个放一块。
首先先添加一个Group Box,并调节一下大小范围。
在这里插入图片描述
然后全选需要放进去的四个玩意;
在这里插入图片描述
将这四个全部放进Group Box里面,然后点击上面的栅格布局,然后自己调节一下范围。最后改一下GroupBox的名称:实时数据。具体的字体大小和字体类型同上面一样,自行设置即可;
在这里插入图片描述
在这里插入图片描述
(4)、设置串口相关的东西
首先添加一个Combo Box,这个效果是用来下拉选项,用来显示搜索到的端口号;
在这里插入图片描述
咱再添加两个按钮用来搜索串口和打开串口,这里用到了QT的Push Button控件,然后继续更改相关属性,改成咱们自己方便编程的;
在这里插入图片描述
咱们再添加一个label用来显示"端口号:";再和上面一样添加一个Group Box,将这四个玩意也放进去进行栅格布局一下,然后调节一下位置就行了。
在这里插入图片描述
最后添加一个退出按钮,用来关闭这个上位机(具体设置见我另一篇博客:Qt学习笔记一:按键关闭窗口

最后编译一遍,显示效果如图。
在这里插入图片描述
(5)、添加坐标系用以绘制实时动态曲线

第一步:复制两个文件到工程目录下,也可以自己新建两个TXT文件,将代码复制进去然后改后缀;具体代码后面会一并粘贴;
在这里插入图片描述
第二步:点击“Headers”添加现有文件,点击“Sources”添加现有文件;
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这时候如果直接编译会出现很多错误,这是因为.pro工程文件没有把这两个文件添加进去;
在这里插入图片描述
怎么弄呢?
先双击tempandhumi.pro打开工程文件代码,然后在QT += widgets 后面加上printsupport,这样再编译就不会出错了。
在这里插入图片描述
在这里插入图片描述
然后咱们再回到ui窗口设计界面。
首先添加一个Widget并调整一下大小。
在这里插入图片描述
然后右键将其提升为QCustomplot。
在这里插入图片描述
为了方便分别,咱们这里也同样将这个坐标系放进一个GroupBox里面;具体设置就不再赘述
在这里插入图片描述
设计完ui界面以后,编译一遍,结果如图所示;
在这里插入图片描述
2.3、代码设计编写
在开始前,咱们需要在.pro文件里面加上这样一句话

QT       += serialport

(1)、双击widget.h。由于是串口通信,因此要用到QT自带的串口控件。
首先咱们先添加几个头文件;

/*------------------------用户代码头文件---------------------------*/
#include <QtSerialPort/QSerialPort>//串口
#include <QtSerialPort/QSerialPortInfo>//串口
#include <QDebug>//用于在控制台输出调试信息
#include <QTime>//定时器
#include <QPainter>//坐标系绘图

然后回到ui设计界面对两个按钮进行操作:右键点击搜索串口将其转到槽,选择信号为clicked();
在这里插入图片描述
在这里插入图片描述
然后在widget.h和widget.cpp里面就会出现这两个按键函数;
在这里插入图片描述
在这里插入图片描述
在widget.h的private slots下面添加两个新函数:

    void AnalyzeData();//数据读取
    void setupPlot();//初始化

在这里插入图片描述
同样在widget.cpp里面添加这两个函数;
在这里插入图片描述
在widget.h里面的private下添加这几句代码;

 	QSerialPort *myserial;//声明串口类,myserial是QSerialPort的实例
    bool serial_flag,start_flag;//定义两个标志位
    QByteArray alldata;//接收串口数据
    //绘图函数
    QDateTime mycurrenttime;//系统当前时间
    QDateTime mystarttime;//系统开始时间

至此widget.h里面的代码已经写完了,接下来便是具体函数的编写。
点击打开widget.cpp;

(2)、在

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
}

里面添加这几句代码

	myserial = new QSerialPort();
    serial_flag = true;
    start_flag = true;
    setupPlot();//图形界面初始化函数

(3)、搜索串口代码

void Widget::on_btn_search_port_clicked()
{
    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())//读取串口信息
    {
        myserial->setPort(info);//这里相当于自动识别串口号之后添加到了cmb,如果要手动选择可以用下面列表的方式添加进去
        if(myserial->open(QIODevice::ReadWrite))
         {
          ui->comboBox->addItem(myserial->portName());//将串口号添加到cmb
          myserial->close();//关闭串口等待人为(打开串口按钮)打开
         }
     }
}

(4)、打开串口代码

void Widget::on_btn_open_port_clicked()
{
    if(serial_flag)
    {
        ui->comboBox->setDisabled(true); //禁止修改串口
        myserial->setPortName(ui->comboBox->currentText()); //设置串口号
        myserial->setBaudRate(QSerialPort::Baud115200); //设置波特
        myserial->setDataBits(QSerialPort::Data8); //设置数据位数
        myserial->setParity(QSerialPort::NoParity);//设置奇偶校验
        myserial->setStopBits(QSerialPort::OneStop);//设置停止位
        myserial->setFlowControl(QSerialPort::NoFlowControl);//非流控制
        if(myserial->open(QIODevice::ReadWrite))
        {
            connect(myserial,&QSerialPort::readyRead,this,&Widget::AnalyzeData);
            mystarttime = QDateTime::currentDateTime();//图像横坐标初始值参考点,读取初始时间
            qDebug()<<"串口打开成功";
        }
        else
        {
            qDebug()<<"串口打开失败";
            //QMessageBox::warning(this,tr("waring"),tr("串口打开失败"),QMessageBox::Close);
        }
        ui->btn_open_port->setText("关闭串口");
        serial_flag = false;//串口标志位置失效
    }
    else
    {
        ui->comboBox->setEnabled(true);//串口号下拉按钮使能工作
        myserial->close();
        ui->btn_open_port->setText("打开串口");//按钮显示“打开串口”
        serial_flag = true;//串口标志位置工作
    }
}

(5)、数据分析代码
首先咱们先对串口发送过来的数据进行定义;

	QByteArray mytemp = myserial->readAll();//定义mytemp为串口读取的所有数据

为了方便在控制台进行观测,我加了一句(这一句可有可无)

 qDebug()<<"mytemp:"<<mytemp;

当有数据传输过来时,咱们先对数据进行拆分,因为有两个数据:温度数据和湿度数据,因此咱们先对数据进行一个简单的协议设定:数据中T与P中间的是温度数据,H与I之间的是湿度数据;当然这个数据协议也得在下位机程序里面同样设定;

QString StrI1=tr(mytemp.mid(mytemp.indexOf("T")+1,mytemp.indexOf("P")-mytemp.indexOf("T")-1));//自定义了简单协议,通过前面字母读取需要的数据
QString StrI2=tr(mytemp.mid(mytemp.indexOf("H")+1,mytemp.indexOf("I")-mytemp.indexOf("H")-1));

这个是stm32程序里面对串口发送的数据的协议设定;
在这里插入图片描述
然后两个文本框显示相对应的实时数据;

ui->line_Temp->setText(StrI1);//显示读取温度值
ui->line_Humi->setText(StrI2);//显示读取湿度值

由于串口发送过来的是字符串,因此咱们需要将字符串穿换成浮点数的数据格式;

float dataI1=StrI1.toFloat();//将字符串转换成float类型进行数据处理
float dataI2=StrI2.toFloat();//将字符串转换成float类型进行数据处理

获取系统时间;

 mycurrenttime = QDateTime::currentDateTime();//获取系统时间
double xzb = mystarttime.msecsTo(mycurrenttime)/1000.0;//获取横坐标,相对时间就是从0开始

将转换好的数据发给坐标系显示;

ui->widget_plot->graph(0)->addData(xzb,dataI1);//添加数据1到曲线1
ui->widget_plot->graph(1)->addData(xzb,dataI2);//添加数据1到曲线1

最后设定横坐标显示范围;

 if(xzb>30)
        {
            ui->widget_plot->xAxis->setRange((double)qRound(xzb-30),xzb);//设定x轴的范围
        }
        else ui->widget_plot->xAxis->setRange(0,30);//设定x轴的范围
        ui->widget_plot->replot();//每次画完曲线一定要更新显示

(6)、坐标系的曲线显示代码
具体的就不分析了,里面都有注释;

void Widget::setupPlot()
{
    //设置曲线一
    ui->widget_plot->addGraph();//添加一条曲线
    QPen pen;
    pen.setWidth(1);//设置画笔线条宽度
    pen.setColor(Qt::blue);
    ui->widget_plot->graph(0)->setPen(pen);//设置画笔颜色
    ui->widget_plot->graph(0)->setBrush(QBrush(QColor(0, 0, 255, 20))); //设置曲线画刷背景
    ui->widget_plot->graph(0)->setName("0-100");
    ui->widget_plot->graph(0)->setAntialiasedFill(false);
    ui->widget_plot->graph(0)->setLineStyle((QCPGraph::LineStyle)1);//曲线画笔
    ui->widget_plot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssNone,5));//曲线形状


    ui->widget_plot->addGraph();//添加一条曲线
    pen.setColor(Qt::red);
    ui->widget_plot->graph(1)->setPen(pen);//设置画笔颜色
    ui->widget_plot->graph(1)->setBrush(QBrush(QColor(0, 0, 255, 20))); //设置曲线画刷背景
    ui->widget_plot->graph(1)->setName("0-100");
    ui->widget_plot->graph(1)->setAntialiasedFill(false);
    ui->widget_plot->graph(1)->setLineStyle((QCPGraph::LineStyle)1);//曲线画笔
    ui->widget_plot->graph(1)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssNone,5));//曲线形状

    //设置图表
    ui->widget_plot->xAxis->setLabel(QStringLiteral("时间/s"));//设置x坐标轴名称
    ui->widget_plot->xAxis->setLabelColor(QColor(20,20,20));//设置x坐标轴名称颜色
    ui->widget_plot->xAxis->setAutoTickStep(false);//设置是否自动分配刻度间距
    ui->widget_plot->xAxis->setTickStep(2);//设置刻度间距5
    ui->widget_plot->xAxis->setRange(0,30);//设定x轴的范围

    ui->widget_plot->yAxis->setLabel(QStringLiteral("PH & TDS"));//设置y坐标轴名称
    ui->widget_plot->yAxis->setLabelColor(QColor(20,20,20));//设置y坐标轴名称颜色
    ui->widget_plot->yAxis->setAutoTickStep(false);//设置是否自动分配刻度间距
    ui->widget_plot->yAxis->setTickStep(10);//设置刻度间距1
    ui->widget_plot->yAxis->setRange(0,100);//设定y轴范围

    ui->widget_plot->axisRect()->setupFullAxesBox(true);//设置缩放,拖拽,设置图表的分类图标显示位置
    ui->widget_plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom| QCP::iSelectAxes);
    ui->widget_plot->axisRect()->insetLayout()->setInsetAlignment(0,Qt::AlignTop | Qt::AlignRight);//图例显示位置右上
    ui->widget_plot->legend->setVisible(true);//显示图例

    ui->widget_plot->replot();
}

到此,所有代码就编写完成。运行一下:
在这里插入图片描述
如果咱们需要给它换个名称,便在ui->setupUi(this);下面添加这么一句话即可;这样的话就可以更改标题了,至于更换软件图标(左上角那个东东),也很简单,自己去搜一下如何添加ico文件,我这里就不多说了;

this->setWindowTitle(QString("温湿度监测系统"));  //设置标题

在这里插入图片描述

3、测试结果

将制作的基于STM32的温湿度系统通过串口与电脑连接,打开上位机,点击搜索串口,然后点击打开串口,这样数据就会慢慢传递过来,同时也会绘制出两个参数的变化曲线;
效果如下
在这里插入图片描述

在这里插入图片描述

4、相关代码

4.1、tempandhumi.pro

#-------------------------------------------------
#
# Project created by QtCreator 2020-08-29T10:38:04
#
#-------------------------------------------------

QT       += core gui
QT       += serialport
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport

TARGET = tempandhumi
TEMPLATE = app

# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as 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 you use 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 += \
        main.cpp \
        widget.cpp \
    qcustomplot.cpp

HEADERS += \
        widget.h \
    qcustomplot.h

FORMS += \
        widget.ui

4.2、widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

/*------------------------用户代码头文件---------------------------*/
#include <QtSerialPort/QSerialPort>//串口
#include <QtSerialPort/QSerialPortInfo>//串口
#include <QDebug>//用于在控制台输出调试信息
#include <QTime>//定时器
#include <QPainter>//坐标系绘图


namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private slots:
    void on_btn_search_port_clicked();

    void on_btn_open_port_clicked();

    void AnalyzeData();//数据读取

    void setupPlot();//初始化
private:
    Ui::Widget *ui;
    QSerialPort *myserial;//声明串口类,myserial是QSerialPort的实例
    bool serial_flag,start_flag;//定义两个标志位
    QByteArray alldata;//接收串口数据
    //绘图函数
    QDateTime mycurrenttime;//系统当前时间
    QDateTime mystarttime;//系统开始时间
};

#endif // WIDGET_H

4.3、main.cpp

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}

4.4、widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QVector>
#include <QMessageBox>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setWindowTitle(QString("温湿度监测系统"));  //设置标题
    myserial = new QSerialPort();
    serial_flag = true;
    start_flag = true;
    setupPlot();//图形界面初始化函数
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_btn_search_port_clicked()
{
    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())//读取串口信息
    {
        myserial->setPort(info);//这里相当于自动识别串口号之后添加到了cmb,如果要手动选择可以用下面列表的方式添加进去
        if(myserial->open(QIODevice::ReadWrite))
         {
          ui->comboBox->addItem(myserial->portName());//将串口号添加到cmb
          myserial->close();//关闭串口等待人为(打开串口按钮)打开
         }
     }
}

void Widget::on_btn_open_port_clicked()
{
    if(serial_flag)
    {
        ui->comboBox->setDisabled(true); //禁止修改串口
        myserial->setPortName(ui->comboBox->currentText()); //设置串口号
        myserial->setBaudRate(QSerialPort::Baud115200); //设置波特
        myserial->setDataBits(QSerialPort::Data8); //设置数据位数
        myserial->setParity(QSerialPort::NoParity);//设置奇偶校验
        myserial->setStopBits(QSerialPort::OneStop);//设置停止位
        myserial->setFlowControl(QSerialPort::NoFlowControl);//非流控制
        if(myserial->open(QIODevice::ReadWrite))
        {
            connect(myserial,&QSerialPort::readyRead,this,&Widget::AnalyzeData);
            mystarttime = QDateTime::currentDateTime();//图像横坐标初始值参考点,读取初始时间
            qDebug()<<"串口打开成功";
        }
        else
        {
            qDebug()<<"串口打开失败";
            //QMessageBox::warning(this,tr("waring"),tr("串口打开失败"),QMessageBox::Close);
        }
        ui->btn_open_port->setText("关闭串口");
        serial_flag = false;//串口标志位置失效
    }
    else
    {
        ui->comboBox->setEnabled(true);//串口号下拉按钮使能工作
        myserial->close();
        ui->btn_open_port->setText("打开串口");//按钮显示“打开串口”
        serial_flag = true;//串口标志位置工作
    }
}

void Widget::AnalyzeData()
{
    QByteArray mytemp = myserial->readAll();//定义mytemp为串口读取的所有数据
    qDebug()<<"mytemp:"<<mytemp;
    if(!mytemp.isEmpty())
    {
        QString StrI1=tr(mytemp.mid(mytemp.indexOf("T")+1,mytemp.indexOf("P")-mytemp.indexOf("T")-1));//自定义了简单协议,通过前面字母读取需要的数据
        QString StrI2=tr(mytemp.mid(mytemp.indexOf("H")+1,mytemp.indexOf("I")-mytemp.indexOf("H")-1));
        ui->line_Temp->setText(StrI1);//显示读取温度值
        ui->line_Humi->setText(StrI2);//显示读取湿度值
        float dataI1=StrI1.toFloat();//将字符串转换成float类型进行数据处理
        float dataI2=StrI2.toFloat();//将字符串转换成float类型进行数据处理

        mycurrenttime = QDateTime::currentDateTime();//获取系统时间
        double xzb = mystarttime.msecsTo(mycurrenttime)/1000.0;//获取横坐标,相对时间就是从0开始
        qDebug()<<"xzb:"<<xzb;
        //注:下位机每隔10ms发送一次数据
        ui->widget_plot->graph(0)->addData(xzb,dataI1);//添加数据1到曲线1
        ui->widget_plot->graph(1)->addData(xzb,dataI2);//添加数据1到曲线1
        if(xzb>30)
        {
            ui->widget_plot->xAxis->setRange((double)qRound(xzb-30),xzb);//设定x轴的范围
        }
        else ui->widget_plot->xAxis->setRange(0,30);//设定x轴的范围
        ui->widget_plot->replot();//每次画完曲线一定要更新显示
    }
}

void Widget::setupPlot()
{
    //设置曲线一
    ui->widget_plot->addGraph();//添加一条曲线
    QPen pen;
    pen.setWidth(1);//设置画笔线条宽度
    pen.setColor(Qt::blue);
    ui->widget_plot->graph(0)->setPen(pen);//设置画笔颜色
    ui->widget_plot->graph(0)->setBrush(QBrush(QColor(0, 0, 255, 20))); //设置曲线画刷背景
    ui->widget_plot->graph(0)->setName("0-100");
    ui->widget_plot->graph(0)->setAntialiasedFill(false);
    ui->widget_plot->graph(0)->setLineStyle((QCPGraph::LineStyle)1);//曲线画笔
    ui->widget_plot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssNone,5));//曲线形状


    ui->widget_plot->addGraph();//添加一条曲线
    pen.setColor(Qt::red);
    ui->widget_plot->graph(1)->setPen(pen);//设置画笔颜色
    ui->widget_plot->graph(1)->setBrush(QBrush(QColor(0, 0, 255, 20))); //设置曲线画刷背景
    ui->widget_plot->graph(1)->setName("0-100");
    ui->widget_plot->graph(1)->setAntialiasedFill(false);
    ui->widget_plot->graph(1)->setLineStyle((QCPGraph::LineStyle)1);//曲线画笔
    ui->widget_plot->graph(1)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssNone,5));//曲线形状

    //设置图表
    ui->widget_plot->xAxis->setLabel(QStringLiteral("时间/s"));//设置x坐标轴名称
    ui->widget_plot->xAxis->setLabelColor(QColor(20,20,20));//设置x坐标轴名称颜色
    ui->widget_plot->xAxis->setAutoTickStep(false);//设置是否自动分配刻度间距
    ui->widget_plot->xAxis->setTickStep(2);//设置刻度间距5
    ui->widget_plot->xAxis->setRange(0,30);//设定x轴的范围

    ui->widget_plot->yAxis->setLabel(QStringLiteral("PH & TDS"));//设置y坐标轴名称
    ui->widget_plot->yAxis->setLabelColor(QColor(20,20,20));//设置y坐标轴名称颜色
    ui->widget_plot->yAxis->setAutoTickStep(false);//设置是否自动分配刻度间距
    ui->widget_plot->yAxis->setTickStep(10);//设置刻度间距1
    ui->widget_plot->yAxis->setRange(0,100);//设定y轴范围

    ui->widget_plot->axisRect()->setupFullAxesBox(true);//设置缩放,拖拽,设置图表的分类图标显示位置
    ui->widget_plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom| QCP::iSelectAxes);
    ui->widget_plot->axisRect()->insetLayout()->setInsetAlignment(0,Qt::AlignTop | Qt::AlignRight);//图例显示位置右上
    ui->widget_plot->legend->setVisible(true);//显示图例

    ui->widget_plot->replot();
}

鉴于qcustomplot.h和qcustomplot.cpp代码太长因此我将它俩放在我的百度网盘里面,请自行下载。
链接: https://pan.baidu.com/s/1NfzuVXYvGBMGKE9dZ-0Mfw?pwd=4avi 提取码: 4avi

项目源代码:温湿度上位机

我也是个初学者,有什么问题可以互相讨论互相学习,如果觉得这篇文章对你有用请点赞评论一波谢谢。

;