QT子线程读取串口数据并传到主线程实时显示心电波形图

最近在做测试心电的项目,我用QT开发了一个简单的界面,实时显示底层采集到的心电数据波形图,由于完全靠自己开发,之前也没有类似经验,所以很苦恼,在网上也查了不少资料,也问了一些老师,最后基本完成了,由于网上查资料看到类似案例资料并不多,也不齐全,所以将自己的心得分享出来,大家一起讨论。由于是初学者,所以有不对或者需要改进的地方请各位大佬指出,共同学习,共同进步。以下贴上示例代码及解析。
 

读取串口部分借鉴于Quartz010的文章《如何在QT中读取串口数据》

http://blog.csdn.net/zz709196484/article/details/66474917  这是博客网址
 
大致思路就是子线程去读取串口数据并传送到主线程,主线程在用widget对象画图实时显示波形图

这里推荐一下我的个人技术博客网站,蹭点点击量,目前正在搭建中,慢慢优化。http://1414805054.xyz/

一、在main.cpp定义一个自己封装的类myapp的对象w,在myapp界面中new出两个button按钮用于开关控制和

QComboBox复选框选择串口端口号,并定义出自己封装好的画图的类的对象,并设置布局。项目结构如下图:

 

二、直接贴上代码

myapp.h

#ifndef MYAPP_H
#define MYAPP_H

 
#include <QWidget>
#include "widget.h"
#include <QTimer>

 
#include <QComboBox>
#include <QPushButton>
#include <QLineEdit>
#include <QLabel>
#include <QDebug>
#include <QPalette>
#include <QTextEdit>
#include "mythread.h"

 
class myapp : public QWidget
{
    Q_OBJECT
public:
    explicit myapp(QWidget *parent = 0);

 
signals:
    void close_serial(char*);//向子线程传递信号关闭串口

 
public slots:
      void setplay(float data1);//传数据

 
      void on_btn_clicked();//打开串口按钮槽函数
      void closeit();//关闭按钮槽函数
    void get_value2()
    {
        
        w2->setvote(data, setval,statu);//设置画图状态及数据

 
  
    }

 
private:
    int  setval;
    float data;
    Widget *w2;
    QPushButton *btn,*closebtn;
    QComboBox *box;
    //QTextEdit *TB;
    QString port1;//串口端口号
    int statu,j;
    MyThread *mthread;//开辟子线程,以第三方库封装的模式
};

 
#endif // MYAPP_H

  myapp.cpp主要做了一些界面的布局,和连接与断开串口的操作

#include "myapp.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include "widget.h"

myapp::myapp(QWidget *parent) : QWidget(parent)
{
    setWindowTitle("心电图");
    //setMinimumSize(800,600);
    setFixedSize(1200,800);
    data = 0;
    setval = 1;
    //w1 = new Widget;
    w2 = new Widget;
    //w3 = new Widget;
    QPalette palette;
    palette.setBrush(QPalette::Background,QBrush(QPixmap("./Tim").scaled(this->size())));
    setPalette(palette);

 
    btn = new QPushButton("显示心电图");
    btn->setFixedSize(100,30);
    closebtn = new QPushButton("关闭");
    closebtn->setFixedSize(100,30);
    //TB= new QTextEdit();

 
    box = new QComboBox();
    box->setFixedSize(100,30);
    box->addItem("COM1");
    box->addItem("COM2");
    box->addItem("COM3");
    box->addItem("COM4");
    box->addItem("COM5");
    box->addItem("COM6");
    box->addItem("COM7");
    box->addItem("COM8");
    box->addItem("COM9");
    box->addItem("COM10");
    box->addItem("COM11");
    box->addItem("COM12");
    box->addItem("COM13");
    box->addItem("COM14");
//当然这里可以做成自己识别端口号,我这里是随便做个demo调试
 
    QHBoxLayout *hbox1 = new QHBoxLayout;
    hbox1->addWidget(btn);
    hbox1->addWidget(closebtn);
    hbox1->addWidget(box);

 
    QVBoxLayout *vbox1 = new QVBoxLayout;
    vbox1->addLayout(hbox1);
    //vbox1->addWidget(w1);
    vbox1->addWidget(w2);
    //vbox1->addWidget(TB);

 
    setLayout(vbox1);
    closebtn->setEnabled(false);

 
    connect(btn, SIGNAL(clicked()), this, SLOT(on_btn_clicked()));
    connect(closebtn,SIGNAL(clicked()),this,SLOT(closeit()));//监听两个按钮信号
}
void myapp::setplay(float data1)
{
    data = data1;
    setval = 10;
    get_value2();//调画图函数

 
}

 
void myapp::on_btn_clicked()
{
        statu = 1;
        btn->setEnabled(false);//设置按钮使能
        closebtn->setEnabled(true);
        qDebug()<<"open ok"<< endl;
        port1 = box->currentText();//获取端口号
        qDebug()<<port1<< endl;
        mthread =new MyThread(port1);
        connect(mthread,SIGNAL(setsenddata(float)),this,SLOT(setplay(float)),Qt::QueuedConnection);
	//接收子线程传输数据的信号
        mthread->start();//开启线程
 
}

 

 
void myapp::closeit()
{
    statu = 0; 
    closebtn->setEnabled(false);
    btn->setEnabled(true);
    connect(this,SIGNAL(close_serial(char*)),mthread,SLOT(close_mthread_serial(char*)));
    char *a="2";
    emit this->close_serial(a);//发送关闭的信号
    delete mthread;//销毁子线程

 
}

 

mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

 
#include <QObject>
#include <QDebug>
#include <QThread>
#include "qextserialbase.h"
#include "win_qextserialport.h"
#include <QPushButton>
class MyThread : public QThread
{
        Q_OBJECT
public:

 
    MyThread(QString port1);
    void run();

 
signals:
    void setsenddata(float data);//向主线程发送接收到的串口数据

 
public slots:
    void read_serial_data();//读取串口数据
    void close_mthread_serial(char *st);//关闭
private:
    Win_QextSerialPort *mycom;
    QString port;
    float data;
};

 
#endif // MYTHREAD_H

 

mythread.cpp

      在这里会做一些串口初始化的工作,以及和下位机定义的数据解析方式,这里有点值得注意的是,读取要同步,下位机发多少,我们读多少,因为我之前出现过一个现象就是,到后面界面会越来越卡顿,我初步排查是由于串口buffer空间的问题导致。当然也许是其他原因,但是我判断mycom->bytesAvailable()>= 9后再每次从串口读取9个字节:mycom->read(9)后,就解决了这个问题。

#include "mythread.h"

 
MyThread::MyThread(QString port1)


 
{
    port = port1;
}
void MyThread::run()
{
    //重写run()函数初始化串口
    mycom = new Win_QextSerialPort(port,QextSerialBase::EventDriven);//读取串口采用事件驱动模式
    mycom->open(QIODevice::ReadWrite);//读写方式打开
    mycom->setBaudRate(BAUD115200);//波特率
    mycom->setDataBits(DATA_8);//数据位
    mycom->setParity(PAR_NONE);//奇偶校验
    mycom->setStopBits(STOP_1);//停止位
    mycom->setFlowControl(FLOW_OFF);//控制位
    mycom->write("1");//向下位机发送1告诉它开始发送数据
    connect(mycom,SIGNAL(readyRead()),this,SLOT(read_serial_data()));//有数据就读
}
void MyThread::read_serial_data()
{
    if(mycom->bytesAvailable()>= 9)
    {
        qDebug()<<mycom->bytesAvailable() << endl;
        QByteArray temp;
        temp = mycom->read(9);//每串数据为9个字节
          union dat{
            char a[4];
            int x;
        };
        dat data_serial;
        data_serial.a[0] = 0;
        data_serial.a[1] = temp[8];
        data_serial.a[2] = temp[7];
        data_serial.a[3] = temp[6];
        data = data_serial.x*2.4/256/8288607*10000;//提取有效数据位,根据需要的数据设计的算法
        emit setsenddata(data);//发送数据给主线程
        qDebug() <<data ;

 
    //TB->insertPlainText(temp.toHex()+"  ");
      }
}
void MyThread::close_mthread_serial(char *st)
{
    mycom->write(st);//告知下位机读端已关闭
    qDebug() <<"close ok"<<st<<endl;
    mycom->close();//关闭子线程

 
}

 

widget.h

波形的绘制在这里完成,先申明一个画家,然后再给他一只调好的画笔,先画好一个坐标轴,draw()方法里面则是动态更新心电波形的逻辑。

#ifndef WIDGET_H
#define WIDGET_H

 
#include <QWidget>
#include <QPainter>
#include <QPen>
#include <QDebug>

class Widget : public QWidget
{
    Q_OBJECT

 
public:
    Widget(QWidget *parent = 0,float pwidth = 0.5);
    ~Widget();
    void paintEvent(QPaintEvent *event)
    {

 
        QPainter p(this);
        QPen pen;
 
        pen.setColor(QColor("green"));笔的颜色
        pen.setWidth(3);//笔的粗细
        p.setPen(pen);

 
        QFont font;//画text的大小
        font.setPixelSize(30);//请画家并给画家一支调好的笔

 
        p.drawLine(0, height()/2, width(), height()/2);//X轴
        p.drawLine(0, height()-5, 0, 5);//Y轴

 
        pen.setColor(QColor("red"));
        p.setPen(pen);
        p.setFont(font);
        p.drawText(QPoint(width()-15, height()/2-5), "S");//横坐标
        p.drawText(QPoint(10, 15), "mv");//纵坐标
#if 1
        pen.setColor(QColor("gray"));//网格
        pen.setWidth(1);
        p.setPen(pen);
        for(int i=10; i<width(); i+=10)
            p.drawLine(i, 0, i, height()-5);
        for(int j=10;j<height(); j+=10)
            p.drawLine(0, j, width(), j);
        pen.setColor(QColor("red"));
        pen.setWidth(10);
        p.setPen(pen);
        font.setPixelSize(16);
        p.setFont(font);
        p.drawText(QPoint(5, height()/2+15), "0");
        float a= 0.2;

 
        for(int k= 50;k<width();k+=50)
         {
            pen.setColor(QColor("white"));
            pen.setWidth(3);
            p.setPen(pen);
            p.drawLine(k, 0, k, height()-5);
            QString data = QString("%1").arg(a);
            pen.setColor(QColor("red"));
            pen.setWidth(10);
            p.setPen(pen);
            p.drawText(QPoint(k+5, height()/2+15), data);
            a=a+0.2;//横轴代表时间,每小格0.04s,每大格0.2s

 
        }

 
#endif

 
        p.translate(-fresh, 0);
        pen.setColor(QColor("red"));
        pen.setWidth(penwidth);
        p.setPen(pen);
        p.drawPath(path);//曲线图(波形)

 
        QPainter set_p(this);
        //QPen set_pen;
        set_p.translate(-set_fresh, 0);
//        set_pen.setColor(QColor("green"));
//        set_pen.setWidth(3);
//        set_p.setPen(set_pen);
//        set_p.drawPath(set_path);//参考线
        //set_p.drawLine(QPoint(0, height()-setval), QPoint(width(), height()-setval));

 
    }
public slots:
    void draw()
    {
        if(statu == 1)
        {
            path.lineTo(QPoint(x+=penwidth, height()/2-vote));//每个数据的纵坐标路径
            if(x > width())//画满
            fresh+=penwidth;
//        set_path.lineTo(QPoint(set_x+=set_penwidth, height()-vote-10));
//        if(set_x > width())//画满
//            set_fresh+=set_penwidth;
            update();自动刷新
        }
    }

 
    void setvote(float value, int set,int status)//最关键的一个方法!!!要向显示电压信号直接调
这个方法就可以了
    {
        vote = value;//传入的数据值
        setval = set;
        statu = status;
        draw();
    }

 
private:

 
    QPainterPath path, set_path;
    float fresh, set_fresh;//滚屏
    float penwidth, set_penwidth;//滚屏速度
    float x, set_x;
    int  setval,statu;
    float vote;

 
};


#endif // WIDGET_H
 
widget.cpp

 思路是通过描点连线的方式,画出心电波形图

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

 
Widget::Widget(QWidget *parent, float pwidth)
    : QWidget(parent),penwidth(pwidth)
{
    QPalette pal(palette());
    pal.setColor(QPalette::Background, Qt::black);
    setAutoFillBackground(true);
    setPalette(pal);

 
    path.moveTo(QPoint(10, height()-5));
    set_path.moveTo(QPoint(10, height()-5));
    vote = 0;
    setval = height()-10;
    x = 0;
    set_x = 10;
    fresh = 0;
    set_fresh = 0;

 
    setFixedHeight(300);

 
}

 
Widget::~Widget()
{

 
}
三、串口代码就省略了,大家有需要看的可以去看我前面借鉴的大佬的博客
附上效果图:

心电波形图

测试的正弦波形图

四、总结

     之所以用线程读取数据,是因为一开始主线程读取数据的时候因为程序执行,也或许是画图影响反应事件
驱动信号的原因,导致接收数据与下位机发送数据不匹配,会丢失一部分数据,也做过算法改进,仍然不行,
后面采取开辟子线程读取串口,主线程显示,就没有这个问题了。总之在绘图上需要计算频率以及幅值,使图
形清楚明了。

  • 15
    点赞
  • 138
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
Qt中实现串口接收数据显示的功能可以按照以下步骤进行操作: 1. 配置串口参数:首先,根据需要配置串口的参数,包括波特率、数据位、校验位、停止位等。可以参考中提供的《QT串口助手(二):参数配置》。 2. 连接信号槽:将QSerialPort类的readyRead()信号连接到一个槽函数,用于接收串口数据。可以参考中提供的信号槽连接代码。 3. 实现数据读取与处理:在槽函数中,使用QSerialPort类的readAll()函数读取串口收到的数据,并进行相应的处理。可以参考中提供的数据读取代码。 4. 显示接收数据:根据需求,可以选择以ASCII字符形式或者Hex字符形式来显示接收的数据。可以将读取到的数据转换为对应形式的字符串,并将其显示在界面上。 5. 添加时间戳显示:如果需要显示接收数据的时间戳,可以在每次接收到数据时获取当前时间,并将其与接收到的数据一起显示。 6. 实现接收数据的统计与显示:根据中提到的功能,可以实现接收数据的统计与显示。可以定义一个计数器变量,在每次接收到数据时进行累加,并将累加结果显示在界面上。 7. 提供接收数据的清零功能:根据中提到的功能,可以实现接收数据的清零。可以将计数器变量重置为0,并更新界面上的显示。 通过以上步骤,就可以在Qt中实现串口接收数据显示的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值