最近在做测试心电的项目,我用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()
{
}
三、串口代码就省略了,大家有需要看的可以去看我前面借鉴的大佬的博客
附上效果图:
心电波形图
测试的正弦波形图
四、总结
之所以用线程读取数据,是因为一开始主线程读取数据的时候因为程序执行,也或许是画图影响反应事件
驱动信号的原因,导致接收数据与下位机发送数据不匹配,会丢失一部分数据,也做过算法改进,仍然不行,
后面采取开辟子线程读取串口,主线程显示,就没有这个问题了。总之在绘图上需要计算频率以及幅值,使图
形清楚明了。