QT上位机串口实时温湿度显示

STM32与上位机通信协议——UART协议:
串行通讯需要有通信协议
通信协议:规定发送与接收方,通信的方式与要求,数据的格式
由RXD和TXD两条线,由于没有时钟线,所以需要规定波特率
数据传输速率就是波特率
UART(串行异步全双工)
采用的是串行通信,也就是一条传输线,一位一位的顺序发送(可以远距离传输,传输较慢)
异步通信是以一个字符为传输单位,每个字符为10位(1个起始位,7个数据位,1个校验位,1个停止位)
通信中两个字符之间的时间间隔不固定,但是同一个字符相邻位之间时间间隔是固定的:
数据通信格式
1、起始位
2、数据位
3、校验位
4、停止位
5、空闲位
STM32需要通过串口向上位机完成数据上传,然后完成实时显示。
所以需要先完成上位机的一个开发,上位机开发工具选择QT
创建QT工程:
在这里插入图片描述

选择QT Widgets 修改名称为:serialdisplay,路径自己选择
在这里插入图片描述

直接点击下一步,到这个界面:
在这里插入图片描述
修改一下类名为:SerialDisplay
在这里插入图片描述

直接点击下一步,直到点击完成 创建工程就成功了,创建完成可以运行一下;
接下来就是添加空间,找到设计界面,点击ui
在这里插入图片描述
就转到这个界面,可以开始添加控件,左边就是控件栏:
在这里插入图片描述
添加完控件如下:
在这里插入图片描述
修改右边对应控件的名字,便于代码里使用;
在这里插入图片描述
然后就是代码部分:
右击你要实现功能的控件,比如说打开串口按钮如下:
在这里插入图片描述
点击转到槽函数:
在这里插入图片描述
选择点击触发,点击ok,就会跳转到以下界面:
在这里插入图片描述
对应实现该函数即可。其他我就不一一介绍了:
直接上源码:
serialdisplay.cpp文件

#include "serialdisplay.h"
#include "ui_serialdisplay.h"
#include <QDebug>
#include <QMessageBox>
#include <QFontDialog>

#include <QJsonObject>
#include <QJsonDocument>

SerialDisplay::SerialDisplay(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::SerialDisplay)
{
    ui->setupUi(this); 
    serialflag = 0;//表示串口关闭
    setWindowFlags(windowFlags()&~Qt::WindowMaximizeButtonHint);// 禁止最大化按钮
    serialport = new QSerialPort();//分配内存
    ui->dateTimeEdit->setDisabled(true);//
    timerid = startTimer(1000);//开启定时器,每隔1秒
}

SerialDisplay::~SerialDisplay()
{
    killTimer(timerid);//关闭
    delete ui;
    delete serialport;
}

//串口获取
void SerialGetData::Serial_read()
{
    int temp = 0;//
    int humi = 0;
    QByteArray recvData;
    try {
        recvData = SerialGetData::serial_recv_Data();//从串口接收数据
    } catch (MyExcption &err) {
        QMessageBox::warning(NULL , "提示", err.what());
        return;
    }
    QString receive = QString::fromLocal8Bit(recvData.constData());
    //json格式数据解析 比如:{"温度":"10℃","湿度":"20%"}
    QJsonParseError err;
    QByteArray arr ;
    arr.append(receive);
    QJsonDocument doc = QJsonDocument::fromJson(arr,&err);
    if(err.error != QJsonParseError::NoError){
        qDebug() << "转换失败";
        return;
    }
    QJsonObject obj = doc.object();
    temp = SerialGetData::getNumsFromStr(obj.value("温度").toString());
    humi = SerialGetData::getNumsFromStr(obj.value("湿度").toString());
   // qDebug() << receive;

    /*分割字符串的方法*/
    /*//自定义数据格式(温度:10-湿度:20或者10℃-20%)
    QStringList list = receive.split("-");//以:分割字符串
    if(list[0].contains("温度") || list[0].contains("℃")){
        temp = SerialGetData::getNumsFromStr(list[0]);
        humi = SerialGetData::getNumsFromStr(list[1]);
    }
    else if(list[0].contains("湿度") || list[0].contains("%")){
        temp = SerialGetData::getNumsFromStr(list[1]);
        humi = SerialGetData::getNumsFromStr(list[0]);
    }
    else{
        QMessageBox::information(NULL,"提示","数据格式错误",QMessageBox::Ok);
        return;
    }
    */
    ui->lcdTemp->display(temp);
    ui->lcdHuim->display(humi);
}
//串口接收
QByteArray SerialGetData::serial_recv_Data() throw(MyExcption)
{
    //读取串口收到的数据
    QByteArray buffer = serialport->readAll();
    //为空的话
    if(buffer.isEmpty()){
        throw("收到数据为空");
        return NULL;
    }
    return buffer;
}
//获取字符串中数字部分
int SerialGetData::getNumsFromStr(QString data)
{
    QString num;
    int j = 0;
    for(int i = 0 ; i < data.length();i++){
        if(data[i] >= '0' && data[i] <= '9'){
            num[j] = data[i];
            j++;
        }
    }
    return num.toInt();
}
//扫描串口
void SerialGetData::on_scanSerialBtn_clicked()
{
    // 清除当前显示的端口号
     ui->serialPortBox->clear();
     //检索端口号
     foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
     {
         ui->serialPortBox->addItem(info.portName());
     }
}
//打开串口
void SerialGetData::on_openSerialBtn_clicked()
{
    if(ui->openSerialBtn->text() == QString("打开串口"))  //串口未打开
    {
        //设置端口号
        //qDebug() << ui->serialPortBox->currentText();
        serialport->setPortName(ui->serialPortBox->currentText());
        //设置波特率
        serialport->setBaudRate(ui->baudRateBox->currentText().toInt());
        //设置数据位
        switch (ui->dataBitBox->currentText().toInt())
        {
            case 8: serialport->setDataBits(QSerialPort::Data8); break;
            case 7: serialport->setDataBits(QSerialPort::Data7); break;
            case 6: serialport->setDataBits(QSerialPort::Data6); break;
            case 5: serialport->setDataBits(QSerialPort::Data5); break;
            default: break;
        }
        //设置停止位
        switch (ui->stopBitBox->currentText().toInt())
        {
            case 1: serialport->setStopBits(QSerialPort::OneStop);break;
            case 2: serialport->setStopBits(QSerialPort::TwoStop);break;
            default:break;
        }
        //设置校验方式
        switch (ui->chekBitBox->currentIndex())
        {
            case 0: serialport->setParity(QSerialPort::NoParity);break;
            default:break;
        }
        //设置流控制模式
        serialport->setFlowControl(QSerialPort::NoFlowControl);
        //打开串口
        if(serialport->open(QIODevice::ReadWrite)==false)
        {
            QMessageBox::warning(NULL , "提示", "串口打开失败!");
            return;
        }
        // 失能串口设置控件
        ui->serialPortBox->setEnabled(false);
        ui->chekBitBox->setEnabled(false);
        ui->baudRateBox->setEnabled(false);
        ui->dataBitBox->setEnabled(false);
        ui->stopBitBox->setEnabled(false);
        ui->scanSerialBtn->setEnabled(false);
        //调整串口控制按钮的文字提示
        ui->openSerialBtn->setText(QString("关闭串口"));
        ui->openSerialBtn->setStyleSheet("background-color: rgb(255, 255, 255);\
                                       color: rgb(255,0,0);\
                                       border-color: rgb(255, 0, 0);");
        serialflag = 1;//串口打开
    }
    else       //串口已经打开
    {
        serialport->close();
        // 使能串口设置控件
        ui->serialPortBox->setEnabled(true);
        ui->chekBitBox->setEnabled(true);
        ui->baudRateBox->setEnabled(true);
        ui->dataBitBox->setEnabled(true);
        ui->stopBitBox->setEnabled(true);
        ui->scanSerialBtn->setEnabled(true);
        //调整串口控制按钮的文字提示
        ui->openSerialBtn->setText(QString("打开串口"));
        ui->openSerialBtn->setStyleSheet("background-color: rgb(0, 255, 255);\
                                       color: rgb(0,0,255);\
                                       border-color: rgb(255, 0, 0);");
        serialflag = 0;//串口关闭
    }
}
//建立连接
void SerialGetData::on_eConnectBtn_clicked()
{
   if(serialflag == 0){
       QMessageBox::information(NULL,"提示","串口未打开",QMessageBox::Ok);
       return;
   }
   ui->eConnectBtn->setEnabled(false);
   ui->eDisconnectBtn->setEnabled(true);
   ui->eConnectBtn->setStyleSheet("background-color: rgb(255, 255, 255);\
                                  color: rgb(255, 0, 0);\
                                  border-color: rgb(255, 0, 0);");
   ui->eDisconnectBtn->setStyleSheet("background-color: rgb(0, 255, 255);\
                                  color: rgb(0, 0, 255);\
                                  border-color: rgb(255, 0, 0);");
   /*
   if(connectflag == 1){
       QMessageBox::information(NULL,"提示","已建立连接",QMessageBox::Ok);
       return;
   }*/
   connect(serialport,&QSerialPort::readyRead,this,&SerialGetData::Serial_read);
   //connectflag = 1;//已为信号绑定槽函数
}
//关闭连接
void SerialGetData::on_eDisconnectBtn_clicked()
{
    if(serialflag == 0){
        QMessageBox::information(NULL,"提示","串口未打开",QMessageBox::Ok);
        return;
    }
    ui->eDisconnectBtn->setEnabled(false);
    ui->eConnectBtn->setEnabled(true);
    ui->eConnectBtn->setStyleSheet("background-color: rgb(0, 255, 255);\
                                   color: rgb(0, 0, 255);\
                                   border-color: rgb(255, 0, 0);");
    ui->eDisconnectBtn->setStyleSheet("background-color: rgb(255, 255, 255);\
                                   color: rgb(255, 0, 0);\
                                   border-color: rgb(255, 0, 0);");
    /*
    if(connectflag == 0){
        QMessageBox::information(NULL,"提示","未建立连接",QMessageBox::Ok);
        return;
    }*/
    disconnect(serialport,&QSerialPort::readyRead,this,&SerialGetData::Serial_read);
    //connectflag = 0;//已解除绑定
    ui->lcdTemp->display(0);
    ui->lcdHuim->display(0);
}
//定时器,定时获取时间展示
void SerialGetData::timerEvent(QTimerEvent *event)
{
    //获取当前时间
    QDate currentdate = dateTime.currentDateTime().date();
    QTime currenttime = dateTime.currentDateTime().time();
    if(event->timerId() == timerid){
        ui->dateTimeEdit->setDate(currentdate);
        ui->dateTimeEdit->setTime(currenttime);
    }
}

serialdisplay.h文件:

#ifndef SERIALDISPLAY_H
#define SERIALDISPLAY_H

#include <QMainWindow>
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
#include <QDateTime>
#include "TOOL/myexcption.h"

QT_BEGIN_NAMESPACE
namespace Ui { class SerialDisplay; }
QT_END_NAMESPACE

class SerialDisplay : public QMainWindow
{
    Q_OBJECT

public:
    SerialGetData(QWidget *parent = nullptr);
    ~SerialGetData();

    QByteArray serial_recv_Data() throw(MyExcption);

    bool serial_sendData(QString data);

    int getNumsFromStr(QString data);//获取字符串中数字部分

private slots:
    void on_scanSerialBtn_clicked();

    void Serial_read();

    void on_openSerialBtn_clicked();

    void on_eConnectBtn_clicked();

    void on_eDisconnectBtn_clicked();

    void timerEvent(QTimerEvent *event)override;

private:
    Ui::SerialGetData *ui;

    QSerialPort *serialport;

    int timerid;

    int serialflag;

    QDateTime dateTime;
};
#endif // SERIALDISPLAY_H

上面两个文件用到了,自定义异常处理:
myexcption.cpp文件:

#include "myexcption.h"

MyExcption::MyExcption(const char *err)
{
    this->error = err;//
}

myexcption.h文件:

#ifndef MYEXCPTION_H
#define MYEXCPTION_H


class MyExcption
{
public:
    MyExcption(const char *err);
    const char* what() throw(){
        return this->error;
    }
private:
    const char *error;
};

#endif // MYEXCPTION_H

代码中完成了实时时间的显示,和温湿度显示,温湿度部分用到了json格式数据的处理,要求下位机传上来的数据必须是json格式的数据,接下来说说json格式数据:
json格式大概分为三种:
第一种,也是最简单的(JSON对象):

{
	"name":"smith",
	"age":30,
	"sex":}

第二种 (对象的属性也可以是JSON对象):

{
	"name":"smith",
	"age":18
	"sex":"school":{
		"sname":"南京大学",
		"address":"南京市鼓楼区汉口路22号"
	}
}

第三种(对象数组):

[
	{
		"title":"Java实战经典开发",
		"edition":3,
		"author":["smith","尼古拉斯","斯巴达"]
	},
	{
		"title":"Oracle实战经典开发",
		"edition":3,
		"author":["smith","尼古拉斯","斯巴达"]
	},
	{
		"title":"Vue实战经典开发",
		"edition":5,
		"author":["smith","尼古拉斯","斯巴达"]
	}
]

其实JSON数据就是一段字符串而已,只不过有不同意义的分隔符将其分割开来而已,我们看上面的符号,里面有[] ,{}等符号,其中

1 []中括号代表的是一个数组;
2 {}大括号代表的是一个对象
3 双引号“”表示的是属性值
4 冒号:代表的是前后之间的关系,冒号前面是属性的名称,后面是属性的值,这个值可以是基本数据类型,也可以是引用数据类型。
而我们只是简单的上传温湿度,所以只需要用到第一种:
可以规定数据格式为:

{
	"温度":"18℃",
	"湿度":"70%"
}

而下位机只需要按照这种格式发数据到串口即可。

实现效果图:

在这里插入图片描述
对之前的控件进行了颜色修改和美化。

问题

2024-4-8修改:
针对大家评论区或者私信提出的一直转换失败的原因:由于串口是按流接收,会在长数据发送时出现一帧数据不完整的情况,比如发送:1dabnxaiuhnsxius这个字符串,会在第一帧数据发送:1dabnxaiu第二帧发送:hnsxius,而我那里没有做完整性判断,所以会出问题,只需要将串口读取部分做一个约定,以回车换行结尾即可解决该问题,在接收到回车换行之前的数据保存,每一帧数据拼接起来,直到获取到回车换行为止,修改部分函数如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/ddd58d335ec4496da73286a5f860772a.png

完整工程链接:
请根据资源链接下载

<think>嗯,用户想开发一个基于Qt温湿度传感器上位机程序,询问如何设计界面和通信方法。首先,我需要整理用户的需求,可能包括实时显示温湿度、历史数据存储、串口通信等等。然后根据提供的引用资料,看看有没有可参考的信息。 引用里提到使用Qt开发上位机,通过串口与传感器(比如DHT11)通信,用SQLite存储数据。所以界面设计可能需要包括数据显示区域、控制按钮、图表等。通信方面,可能需要使用Qt串口模块,比如QSerialPort。 接下来,我需要分步骤回答。首先是界面设计,可能需要用Qt Designer或者手写代码布局。然后是通信部分,配置串口参数,比如波特率、数据位、停止位等,这需要参考传感器和STM32的通信协议。数据解析部分,可能需要处理接收到的字节流,提取温湿度数值。存储方面,使用SQLite数据库,Qt有相关的模块支持。 还要考虑实时显示,可能需要定时器或者信号槽机制来更新UI。历史数据回放可能需要图表控件,比如Qt Charts。此外,引用中提到STM32通过UART协议上传数据,所以通信协议部分需要详细说明数据格式,比如起始位、数据位、校验位等。 用户可能还需要知道如何处理错误,比如通信中断或数据校验失败。代码示例可能会有帮助,比如展示如何打开串口、读取数据、更新界面和存储数据库。需要确保代码符合Qt的最佳实践,比如使用信号槽进行线程间通信,避免阻塞UI线程。 最后,生成相关问题,可能涉及优化通信、扩展功能、数据可视化方法等。要确保回答结构清晰,步骤明确,引用相关参考资料,比如引用中的示例项目。</think>### 基于Qt温湿度传感器上位机开发指南 #### 一、界面设计步骤 1. **布局规划** - 使用`QWidget`或`QMainWindow`作为主窗口 - 划分显示区域:实时数值区(QLCDNumber控件)、历史曲线图(Qt Charts)、控制按钮区 - 示例布局代码: ```cpp QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(createDisplayGroup()); mainLayout->addWidget(createChartGroup()); mainLayout->addWidget(createControlGroup()); ``` 2. **控件选择** - 实时显示:`QLCDNumber`显示温湿度数值 - 数据可视化:`QChartView`绘制动态曲线 - 串口配置:`QComboBox`选择端口,`QPushButton`控制连接 - 状态指示:`QLabel`配合颜色变化表示连接状态 #### 二、通信实现方法 1. **串口配置** - 使用QtSerialPort模块 - 关键参数配置(参考UART协议[^4]): ```cpp QSerialPort serial; serial.setPortName("COM3"); serial.setBaudRate(9600); serial.setDataBits(QSerialPort::Data8); serial.setParity(QSerialPort::NoParity); serial.setStopBits(QSerialPort::OneStop); ``` 2. **数据解析** - 定义通信协议(示例格式): $$ \text{起始符}(1B) + \text{温度}(2B) + \text{湿度}(2B) + \text{校验和}(1B) $$ - 字节处理示例: ```cpp void parseData(QByteArray &data) { if(data[0] == 0xAA && checkSumValid(data)) { int temp = (data[1] << 8) | data[2]; int humi = (data[3] << 8) | data[4]; emit newDataReceived(temp/10.0, humi/10.0); } } ``` 3. **数据库存储** - 使用SQLite存储历史数据 - 建表语句: ```sql CREATE TABLE SensorData ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, temperature REAL, humidity REAL ); ``` #### 三、完整实现流程 1. 创建Qt Widgets Application项目 2. 在.pro文件中添加模块: ```qmake QT += serialport charts sql ``` 3. 通信线程实现(伪代码): ```cpp class SerialThread : public QThread { Q_OBJECT public slots: void readData() { while(serial.bytesAvailable() >= 6) { QByteArray frame = serial.read(6); parseFrame(frame); } } signals: void newData(double temp, double humi); }; ``` 4. 界面与数据绑定: ```cpp connect(serialThread, &SerialThread::newData, this, [=](double t, double h){ lcdTemp->display(t); chartSeries->append(QDateTime::currentDateTime().toMSecsSinceEpoch(), t); db.insertData(t, h); }); ``` #### 四、关键技术要点 1. 多线程处理:通过`QThread`分离UI线程与数据采集线程[^2] 2. 数据校验:采用CRC或校验和保证传输可靠性[^4] 3. 实时曲线:使用`QDateTimeAxis`和`QValueAxis`实现时间序列显示 4. 异常处理:添加串口断开重连、数据超时检测等容错机制
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值