QT串口调试助手开发教程:上位机接收数据解析数据帧+多通道波形显示+数据保存

这篇具有很好参考价值的文章主要介绍了QT串口调试助手开发教程:上位机接收数据解析数据帧+多通道波形显示+数据保存。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在该设计中主要需要解决的问题就是接收单片机采集到的数据并在上位机将数字实时的通过波形显示出来,然后上位机要有保存下数据文件的功能,便于后续的软件读取数据做进一步的分析处理。有些人吃相难看,无底线,无道德,鉴于串口上位机会被广泛使用,撰写该教程,从头搭建一个好用的串口示波器。

完整的项目链接见文末

QT第一步:安装软件环境

安装qt5.14,可以在这个网站下载安装包。
下载版本: qt-opensource-windows-x86-5.14.2.exe

安装时需要勾选MinGW 相关选项

安装教程不在重复赘述,网上有很多的例子

第二步:初始QT

qt作为一种开源的UI程序设计框架可以便捷的通过qt提供的各种组件以低代码的方式组件自己需要的ui界面,这对于初步入门的设计人员十分的友好,同时qt官方对每个类、方法、变量的文档说明都非常详细并且提供了实例代码入门非常简单。
安装好qt后直接使用qt官方的Qt Creator程序进行开发,当然你可以使用MSVS进行开发,这还需要在MSVC中安装一下qt的官方插件。我使用的是VS2019+qt5.14.2,在UI设计界面上VS和qt还存在一定的兼容性问题,有好多次出现闪退的问题。所以工程不是特别大的时候还是建议老老实实就用Qt Creator进行开发。
qt串口数据解析,嵌入式,qt,c++
安装好后可以看到qt提供很多的模板程序,当然也可以都不使用,直接从空白模板开始我们的工程

第三步:了解信号与槽机制

Qt利用信号与槽(signals/slots)机制取代传统的callback来进行对象之间的沟通。当操作事件发生的时候,对象会发提交一个信号(signal);而槽(slot)则是一个函数接受特定信号并且执行槽本身设置的动作。信号与槽之间,则透过QObject的静态方法connect来链接。
信号在任何执行点上皆可发射,甚至可以在槽里再发射另一个信号,信号与槽的链接不限定为一对一的链接,一个信号可以链接到多个槽或多个信号链接到同一个槽,甚至信号也可连接到信号。
以往的callback缺乏类型安全,在调用处理函数时,无法确定是传递正确类型的参数。但信号和其接受的槽之间传递的资料类型必须要相符合,否则编译器会提出警告。信号和槽可接受任何数量、任何类型的参数,所以信号与槽机制是完全类型安全。

信号与槽机制也确保了低耦合性,发送信号的类别并不知道是哪个槽会接受,也就是说一个信号可以调用所有可用的槽。此机制会确保当在"连接"信号和槽时,槽会接受信号的参数并且正确执行。

上面的解释来自维基百科,说的简单点呢就是说:当你在qt界面中放置了一个按钮,当你运行程序并移动鼠标点击这个按钮的时候。点击按钮这个动作就是一个信号,当然有了信号我们就要执行命令,我们通过软件定义将这个信号连接上一个槽,这个槽函数执行点击动作所需要的对行功能。信号与槽可以是一一对应也可以是一对多、多对一。
在这里我们可以看到:

QObject::connect(ui->button, SIGNAL(clicked()), this, SLOT(senddata()));

上面这个函数就是一个槽函数,他把button按钮的点击信号链接到了发送数据的响应函数上,只要鼠标点击一次就会发送一次。

qt串口数据解析,嵌入式,qt,c++
在qt中每一个对象都有对应的属性,这些属性的值就对应了这个对象的大小,相对位置,名称等等。

在使用qt时也可以选择pyqt,其中也同样拥有界面设计的功能,但以下的程序默认针对c++版本的qt

开始串口助手

在本次的设计当中,我们的几个核心功能是完成串口数据的收发+波形数据绘制+文件操作。整体的设计思路如下所示:
qt串口数据解析,嵌入式,qt,c++
在程序设计上,首先我们需要一个串口类,用来发送和接收数据;
然后我们需要 两个文本框和几个按钮来实现数据的接收和发送,并且设置串口通信的参数;
实现之后我们就需要设置针对串口数据的解析了。
那么我们创建一个qt工程,使用widget作为默认控件。
qt串口数据解析,嵌入式,qt,c++
我们再widget.h中添加相关的函数和变量,指针申明,然后再widget.cpp中实现我们的具体的函数功能。

定时接收串口数据

由于对电脑端接收数据很难做到硬件级的收中断,收到1bit数据就中断处理一次所以我们设置一个定时器,让程序检测当有数据来时就打开定时器开始定时,定时一段时间后关闭中断并接收保存这段时间内的所有数据。
qt串口数据解析,嵌入式,qt,c++
我们如下定义串口对象:

// .h:
void find_port();           //查找可用串口
QSerialPort *serialport;
void on_open_port_clicked();
void on_close_port_clicked();

//.cpp:
//打开串口
void Widget::on_open_port_clicked()
{
   update();
   sleep(100);      //延时100ms
   find_port();     //重新查找com
    //初始化串口
        serialport->setPortName(ui->com->currentText());        //设置串口名
        if(serialport->open(QIODevice::ReadWrite))
        {
            serialport->setBaudRate(ui->baud->currentText().toInt());       //设置波特率
            switch(ui->bit->currentIndex())                   //设置数据位数
            {
                case 8:serialport->setDataBits(QSerialPort::Data8);break;
                default: break;
            }
            switch(ui->jiaoyan->currentIndex())                   //设置奇偶校验
            {
                case 0: serialport->setParity(QSerialPort::NoParity);break;
                default: break;
            }
            switch(ui->stopbit->currentIndex())                     //设置停止位
            {
                case 1: serialport->setStopBits(QSerialPort::OneStop);break;
                case 2: serialport->setStopBits(QSerialPort::TwoStop);break;
                default: break;
            }
            serialport->setFlowControl(QSerialPort::NoFlowControl);
            // 设置控件可否使用
            timerDrawLine->start(100);
            ui->send_button->setEnabled(true);
            ui->close_port->setEnabled(true);
            ui->save_data->setEnabled(true);
            ui->open_port->setEnabled(false);
        }
        else    //打开失败提示
        {
            sleep(100);

            QMessageBox::information(this,tr("Erro"),tr("Open the failure"),QMessageBox::Ok);
        }
}


//关闭串口
void Widget::on_close_port_clicked()
{
    serialport->clear();        //清空缓存区
    serialport->close();        //关闭串口
    timerDrawLine->stop();      //关闭波形刷新

    ui->send_button->setEnabled(false);
    ui->open_port->setEnabled(true);
    ui->close_port->setEnabled(false);
}

这一段时间根据串口发送一帧数据的时间做合理设置,一般来时这个时候收到的数据里包含着好几帧完整的数据,整段数据的头和尾可能并不是我们设置的帧头和帧尾,所以我们需要从中解析出需要的数据。
假设我们这里使用帧头*,帧尾#,然后中间使用多个数字连续,每个数字之间用逗号分开。例如有:*210,13,11,12,130#这样的一组数据。在查找帧数据时,这里我使用了关键字索引,从接收到的字符串中查找到第一个我设置的帧头数据(这里的*),然后从这个位置开始向后继续检索第一个我设置的帧尾(这里的#),然后计算帧头到帧尾的数据长度,长度符合要求的时则把这一段字符串从中提取出来做单独处理,源码如下:

//串口接收数据操作
//串口接收数据帧格式为:帧头'*' 帧尾'#' 数字间间隔符号',' 符号全为英文格式
void Widget::Read_Date()
{
    int bufferlens = 0;     //帧长
    QString str = ui->Receive_text_window->toPlainText();
    timerserial->stop();//停止定时器,

    qDebug()<<buffer;

    QByteArray bufferbegin = "*";   //帧头
    int index=0;
    QByteArray bufferend = "#";     //帧尾
    int indexend = 0;
    QByteArray buffercashe;

    index = buffer.indexOf(bufferbegin,index); //查找帧头
    indexend = buffer.indexOf(bufferend,indexend); //查找帧尾
    if((index<buffer.size())&&(indexend<buffer.size()))
    {
        bufferlens = indexend - index + 1; //计算帧长度
        buffercashe = buffer.mid(index,bufferlens); //截取出数据帧
    }

    char recvdata[buffercashe.size()];
    memset(recvdata,0,sizeof(recvdata));
    memcpy(recvdata,buffercashe.data(),bufferlens-1);
    recvdata[buffercashe.size()-1]=35;
    if(recvdata[0]=='*'&&recvdata[buffercashe.size()-1]=='#')   //二次帧检查
    {
        str_to_num(recvdata);       //更新数据并缓存到保存区
        str+="succeed:";    //在文本窗口给出提示
        str+=tr(buffercashe);
        str += "  ";
        ui->Receive_text_window->clear();
        ui->Receive_text_window->append(str);
    }
    else
    {
        str+="error! "; //错误处理
        str+=tr(buffercashe);
        str += "  ";
        ui->Receive_text_window->clear();
        ui->Receive_text_window->append(str);
    }
    buffer.clear();
}

void Widget::serial_timerstart()
{
    timerserial->start(4);
    buffer.append(serialport->readAll());
}

在上面的程序中,当串口发现有数据进来则引用Widget::serial_timerstart()开始定时接收。当定时到之后开始处理接收到的数据。如果数据正常后则把数据保存到缓冲区并更新当前的波形数据。有细心的同学可能会问了:我可以使用readline()函数或者其他read类的函数来读取数据吗?这当然是可以,这里之所以用上定时器,主要是考虑如果下位机的数据是连续不间断发送的,那为了提高数据解析的正确效率,避免上位机串口数据接收缓冲区溢出或者错误的\r\n影响数据接收,所以我们通过定时接收,这样可以从任何一个定时数据段内保证解析出至少一帧有效的数据。
qt串口数据解析,嵌入式,qt,c++
在上面的图片中很清楚的说明了这样操作的优势。假设最低通信波特率是9600,数据帧最长有35个字节,那么定时器最低应该设置到t=35/9600=0.0036也就是4ms左右时间。在实际的应用时可以根据项目需求自己自己调整定时时间。

值得强调的一点就是,在完成了相关串口的操作之后,我们还需要将当前解析好的数据存储到一个全局的缓存中,这个缓存可以是一个Vector容器,也可以是动态数组。如果考虑到大数据量的稳定性也可以使用SQL数据来管理数据。这部分可以自行发挥。

波形显示

这里显示模型数据使用了很简单的QChart()实例,定义QSplineSeries()对象,然后不断的更新QSplineSeries对象的数据列表,做好坐标轴的处理后就可以实现出动态曲线的效果了。

//曲线设置初始化
void Widget::Chart_Init()
{
    //初始化QChart的实例
    chart = new QChart();
    //初始化QSplineSeries的实例
    lineSeries = new QSplineSeries();
    //设置曲线的名称
    lineSeries->setName("曲线1");
    //把曲线添加到QChart的实例chart中
    chart->addSeries(lineSeries);

    //声明并初始化X轴、两个Y轴
    QValueAxis *axisX = new QValueAxis();
    QValueAxis *axisY = new QValueAxis();
    //设置坐标轴显示的范围
    axisX->setMin(0);
    axisX->setMax(MAX_X);
    axisY->setMin(0);
    axisY->setMax(MAX_Y);
    //设置坐标轴上的格点
    axisX->setTickCount(10);
    axisY->setTickCount(10);
    //设置坐标轴显示的名称
    QFont font("Microsoft YaHei",8,QFont::Normal);//微软雅黑。字体大小8
    axisX->setTitleFont(font);
    axisY->setTitleFont(font);
    axisX->setTitleText("X-时间");
    axisY->setTitleText("Y-角度");
    //设置网格不显示
    axisY->setGridLineVisible(false);
    //下方:Qt::AlignBottom,左边:Qt::AlignLeft
    //右边:Qt::AlignRight,上方:Qt::AlignTop
    chart->addAxis(axisX, Qt::AlignBottom);
    chart->addAxis(axisY, Qt::AlignLeft);
    //把曲线关联到坐标轴
    lineSeries->attachAxis(axisX);
    lineSeries->attachAxis(axisY);
    //把chart显示到窗口上
    ui->graphicsView->setChart(chart);
    ui->graphicsView->setRenderHint(QPainter::Antialiasing);      // 设置渲染:抗锯齿,如果不设置那么曲线就显得不平滑
}

//更新曲线函数
void Widget::DrawLine()
{
    if(count > MAX_X)
    {
        //当曲线上最早的点超出X轴的范围时,剔除最早的点,
        lineSeries->removePoints(0,lineSeries->count() - MAX_X);
        // 更新X轴的范围
        chart->axisX()->setMin(count - MAX_X);
        chart->axisX()->setMax(count);
    }
    else{
        chart->axisX()->setMin(0);
        chart->axisX()->setMax(MAX_X);
    }
    //增加新的点到曲线末端
    lineSeries->append(count, (int)Data.Sensor_1);
    count ++;
}

使用上面的程序我们每调用一次DrawLine()就会在画图区域新增一组数据。
qt串口数据解析,嵌入式,qt,c++
这样我们就有了一个可以画出曲线的界面了。值得一提的是,QT chart控件还提供了柱状图,饼图,折线图,等多种绘图样式,可以自行查看qt的相关文档类似上面的内容撰写绘图界面的数据。

文件保存

首先是说明下csv文件的写入格式:以逗号作为分隔符,\n作为换行符。
上位机的所有文件操作通过对上述的全局缓冲数据进行操作,在源码中定义为m_data。从串口接收数据时就把数据写入到m_data中,保存时就把数据从m_data中以此写入到txt文件里。
那么就可以先写入一个表头,然后根据格式从缓存好的数据容器中逐条加载后逐行写入并打上时间戳。
下面的代码举例我们要保存的数据是五通道的。

/*
    函   数:SaveRecvDataFile
    描   述:保存数据按钮点击槽函数
    输   入:无
    输   出:无
*/
void Widget::SaveRecvDataFile()
{
    if(m_data.size()<1)
    {
        QMessageBox::information(this, "提示","当前数据为空");
        return;
    }
    serialport->clear();        //清空缓存区
    serialport->close();        //关闭串口
    timerDrawLine->stop();      //关闭波形刷新
    ui->send_button->setEnabled(false);		//禁用部分按键
    ui->open_port->setEnabled(true);
    ui->close_port->setEnabled(false);
    ui->save_data->setEnabled(false);

    QString csvFile = QFileDialog::getExistingDirectory(this);      //获取文件保存路径
    if(csvFile.isEmpty())
       return;
    QDateTime current_date_time =QDateTime::currentDateTime();      //获取系统时间
    QString current_date =current_date_time.toString("MM_dd_hh_mm");    //获取时间字符串
    csvFile += tr("/%1.csv").arg(current_date);
    qDebug()<< csvFile;
    QFile file(csvFile);
    if ( file.exists())
    {
            //如果文件存在执行的操作,此处为空,因为文件不可能存在
    }
    file.open( QIODevice::ReadWrite | QIODevice::Text );    //以读写模式读取文件
    QTextStream out(&file);
    out<<tr("Time,")<<tr("sensor1,")<<tr("sensor2,")<<tr("sensor3,")<<tr("sensor4,")<<tr("sensor5,\n");     //写入表头
    // 创建 CSV 文件
    for (const auto &data : m_data) {           //测试格式: *111,222,333,444,555#
        out << data << "\n";        //顺序将缓冲区数据写入文件
    }
    file.close();
    QVector<QString>().swap(m_data);        //清空缓存区数据
    QMessageBox::information(this, "提示","数据保存成功");
}

保存数据效果如下图:
qt串口数据解析,嵌入式,qt,c++

好的,那我们基本实现了最初的三个功能,下面附上一张完整效果的演示截图:

qt串口数据解析,嵌入式,qt,c++

下位机单片机逻辑代码示例

最后,我们还需要下位机的单片机能够对应的将数据发送出来,这里按照上文的描述,给出一个单片机的逻辑结构代码示例:

typedef struct sonsor  //定义数据结构
{
    float sensor1;
    float sensor2;
    float sensor3;
    float sensor4;
    float sensor5;
}	sonsor;

sonsor cap;

int main(void)
{
    Sys_Init();
    sonsor_Init();	//假设初始化传感器
    while(1)
    {
        delay_ms(100);
        cap.sensor1 = sonsor_Read(CHANNEL_1);	//假设读取5通道的传感器数据
        cap.sensor2 = sonsor_Read(CHANNEL_2);
        cap.sensor3 = sonsor_Read(CHANNEL_3);
        cap.sensor4 = sonsor_Read(CHANNEL_4);
        cap.sensor5 = sonsor_Read(CHANNEL_5);
        
        //printf() 需要串口重定向到uart输出
        printf("*%0.1f,%0.1f,%0.1f,%0.1f,%0.1f#", cap.sensor1, cap.sensor2, cap.sensor3, cap.sensor4, cap.sensor5);
        
    }
}

核心程序已经给出,有需要的话自行修改就可以。

项目完整链接: github

为众人抱薪者,不可使其毙于风雪。我们生活在一个信息纷繁的世界,请保持独立思考,尊重他人付出,为自己的努力鼓掌。文章来源地址https://www.toymoban.com/news/detail-730188.html

到了这里,关于QT串口调试助手开发教程:上位机接收数据解析数据帧+多通道波形显示+数据保存的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Qt上位机与STM32进行串口通信】-2-Qt串口开发

    系列文章目标:Qt上位机与STM32进行串口通信,控制多个LED的状态。 本篇文章的主要目标: 1、设计两个界面,串口连接界面、控制界面。 2、只有在串口连接成功才能打开控制界面。 3、打开控制界面时,串口保持连接。 4、自定义控件,提升开发效率。 以下是我入门Qt的视频

    2024年02月06日
    浏览(50)
  • 正点原子Linux开发板——Qt串口上位机实验

    最近在学习嵌入式qt开发,然后跟着教程编写了一个简单的串口上位机程序,在编写的时候还算比较顺利,但在调试的时候花了点功夫,折腾了一下午。最后还是理清了思路,解决了问题,特写此博客进行记录和总结。 整个软件的界面我都是用ui来设计的,其实也可以用代码

    2024年02月09日
    浏览(43)
  • Rust UI开发(四):iced中如何添加菜单栏(串口调试助手)

    注:此文适合于对rust有一些了解的朋友 iced是一个跨平台的GUI库,用于为rust语言程序构建UI界面。 这是一个系列博文,本文是第四篇,前三篇链接: 1、Rust UI开发(一):使用iced构建UI时,如何在界面显示中文字符 2、Rust UI开发(二):iced中如何为窗口添加icon图标 3、Rust

    2024年02月03日
    浏览(93)
  • Qt开发简易蓝牙调试助手(低功耗蓝牙)

    Qt中是有蓝牙模块的,直接用此模块开发就行。但是注意使用的是低功耗蓝牙的类,连接方式和经典蓝牙会有区别 大致的连接步骤是: 搜索附近的蓝牙设备 连接指定的蓝牙设备 获取服务 指定服务进行连接(因为每一种下的特征对象的权限是不一样的,有的只有读取权限,没

    2024年02月16日
    浏览(43)
  • 蓝牙串口调试助手通过PC蓝牙发送数据给ESP32同时在串口上显示

    OK,好久没有更新Blog啦 今天把之前积累的代码放上,给需要学习的程序猿们使用 我还是不太喜欢写文字,倒是比较喜欢客套,哈哈 硬件图: ESP32和USB-micro-B数据线一根 蓝牙串口调试助手通过PC蓝牙发送数据给ESP32同时在串口上显示 具体代码如下:

    2024年02月09日
    浏览(46)
  • STM32数据可视化显示——纸飞机串口调试助手的使用

    本人在开发STM32的过程中,数据都是通过XCOM串口调试助手进行展示的,但这样的话,数据就做不到图像化、多样化处理的显示,使得一些连续的数据无法通过图像来直观表达 感兴趣的朋友可以点进链接进行下载哦 http://blog.comassistant.cn/ 上图所示,为纸飞机调试助手的界面,可

    2023年04月12日
    浏览(53)
  • Qt程序接收串口数据存在延迟解决办法

    在调试接收串口数据的Qt程序中发现,数据存在延迟和粘包现象。下位机发送数据包频率是100Hz,一包56字节,波特率115200,在打印 port-readAll() 的值的时候发现并不是每10ms读到一包数据,而是大概每50ms左右一次接收到5包数据,在其他电脑上调试,以及下载其他串口助手调试后

    2024年02月04日
    浏览(48)
  • 使用Microsoft Visual Studio编写C#上位机(串口助手)

    最近跟着刘工写了一套用于单片机与电脑通信的串口助手,此处将自己手敲的代码记录下来,供大家一起学习交流。 一、程序界面   程序界面 程序界面说明    二、代码(Form1.cs)  三、备注 此处在Form1的属性中,将AutoSize设为True,AutoScroll设为True,AutoScaleMode设为Font,以应

    2024年02月11日
    浏览(54)
  • QT网络编程TCP/UDP开发流程 制作网络调试助手

    1、QT的网络编程: TCP和UDP TCP编程需要用到俩个类: QTcpServer 和 QTcpSocket QTcpSocket类 提供了一个TCP套接字 QTcpSocket是QAbstractSocket的一个子类,它允许您建立TCP连接和传输数据流 注意:TCP套接字不能在QIODevice::Unbuffered模式下打开。 QTcpServer类 提供一个基于tcp的服务器 2. 这个类可以接

    2023年04月08日
    浏览(45)
  • QT串口接收数据并进行波形显示(含源码)

    ** 使用QT在串口调试助手基础上实现波形显示(含源码) 评论比较多留言需要源码的,逐个发邮箱比较麻烦也不能及时回复,现将源码上传至链接(无需积分下载)https://download.csdn.net/download/m0_51294753/87743394,下载不下来可以私信我留邮箱。 一、前言 背景:使用ADS1255对模拟信

    2024年02月01日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包