添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

(八) QtCharts和QSerialPort类的使用

在上一篇文章中,我们讲述了如何使用Lucid语言对FPGA进行编程,使其能处理PMT的计数并且能够每隔21ms将平均后的数据通过串口发送到主机上。而本文则将介绍如何通过Qt处理这些串口数据并实现可视化。

由于我们需要实时观察PMT计数的变化情况,因此就要求程序能够动态地显示图像。经过简单的搜索可以找到Qt提供的一个完全符合我们要求的示例程序:

这个程序能够每隔1s产生一个随机数,然后不断地将数据点加到曲线图中并向左移动曲线。以下是示例程序中实现核心功能的源代码:

#include "chart.h"
#include <QtCharts/QAbstractAxis>
#include <QtCharts/QSplineSeries>
#include <QtCharts/QValueAxis>
#include <QtCore/QRandomGenerator>
#include <QtCore/QDebug>
Chart::Chart(QGraphicsItem *parent, Qt::WindowFlags wFlags):
    QChart(QChart::ChartTypeCartesian, parent, wFlags),
    m_series(0),
    m_axisX(new QValueAxis()),
    m_axisY(new QValueAxis()),
    m_step(0),
    m_x(5),
    m_y(1)
    QObject::connect(&m_timer, &QTimer::timeout, this, &Chart::handleTimeout);
    m_timer.setInterval(1000);
    m_series = new QSplineSeries(this);
    QPen green(Qt::red);
    green.setWidth(3);
    m_series->setPen(green);
    m_series->append(m_x, m_y);
    addSeries(m_series);
    addAxis(m_axisX,Qt::AlignBottom);
    addAxis(m_axisY,Qt::AlignLeft);
    m_series->attachAxis(m_axisX);
    m_series->attachAxis(m_axisY);
    m_axisX->setTickCount(5);
    m_axisX->setRange(0, 10);
    m_axisY->setRange(-5, 10);
    m_timer.start();
Chart::~Chart()
void Chart::handleTimeout()
    qreal x = plotArea().width() / m_axisX->tickCount();
    qreal y = (m_axisX->max() - m_axisX->min()) / m_axisX->tickCount();
    m_x += y;
    m_y = QRandomGenerator::global()->bounded(5) - 2.5;
    m_series->append(m_x, m_y);
    scroll(x, 0);
    if (m_x == 100)
        m_timer.stop();

通过分析代码我们不难发现,通过改变 m_series m_axisX m_axisY 就可以把 Chart 中的图形变化完全定下来。具体到这个程序而言,它每隔1s产生一个坐标点,横坐标有个固定的增加值,纵坐标则为随机数。为了移动曲线图,程序在产生新点的同时会移动X轴。而实现每隔1s做一件事情的方法是设置一个Timer,并通过我们在之前文章提到的信号-槽机制将timeout信号和一个处理这个信号的函数关联起来。

对这个程序稍加修改就可以用于我们显示PMT计数的程序了:

void Chart::handleTimeout()
    qreal x = y * plotArea().width() / 30;
    qreal y = (m_axisX->max() - m_axisX->min()) * interval / 1000 / 30;
    m_x += y;
    m_series->append(m_x, m_y);
    if (m_x > 29) {
        m_series->remove(0




    
);
        scroll(x, 0);
void Chart::start()
    m_timer.start();
void Chart::stop()
    m_timer.stop();
void Chart::setY(qreal y)
    m_y = y;

首先我们添加了 start stop setY 三个public函数方便我们从外部开关曲线图的移动,以及设置数据点的值。对于 handleTimeout 函数我们对其略加做了修改,使其一张图中显示30个点。由于我们可能要一直开着这个程序查看计数,为了避免内存溢出,程序只保留最新的30个点,之前的点我们从 m_series 中删除并释放内存。

现在我们需要一个窗口。它能够从串口接收数据,通过 setY 函数把数据传到 Chart 上,然后留个位置展示这个 Chart 。这样我们的任务就完成了!我们按照“ (四) Qt窗口、布局与控件的使用 ”的流程创建新的项目。之后我们将 window.cpp 文件修改如下:

#include "window.h"
#include <QGridLayout>
#include <QLabel>
#include <QComboBox>
#include <QSerialPortInfo>
#include <QLineEdit>
#include <QPushButton>
#include <QMessageBox>
Window::Window(QWidget *parent) :
    QWidget(parent),
    m_serialPortLabel(new QLabel(tr("Serial port:"))),
    m_serialPortComboBox(new QComboBox),
    m_requestButton(new QPushButton(tr("Request"))),
    m_requestLineEdit(new QLineEdit(tr(""))),
    m_responseLabel(new QLabel(tr("Response:"))),
    m_responseLineEdit(new QLineEdit(tr(""))),
    m_statusLabel(new QLabel(tr("Status: Not running."))),
    m_runButton(new QPushButton(tr("Connect"))),
    m_stopButton(new QPushButton(tr("Disconnect"))),
    m_serial(new QSerialPort(this))
    const auto infos = QSerialPortInfo::availablePorts();
    for (const QSerialPortInfo &info : infos)
        m_serialPortComboBox->addItem(info.portName());
    chart = new Chart;
    chartView = new QChartView(chart);
    chartView->setRenderHint(QPainter::Antialiasing);
    auto mainLayout = new QGridLayout;
    mainLayout->addWidget(m_serialPortLabel, 0, 0);
    mainLayout->addWidget(m_serialPortComboBox, 0, 1);
    mainLayout->addWidget(m_runButton, 0, 2);
    mainLayout->addWidget(m_stopButton, 0, 3);
    mainLayout->addWidget(m_requestButton, 1, 0);
    mainLayout->addWidget(m_requestLineEdit, 1, 1, 1, 3);
    mainLayout->addWidget(m_responseLabel, 2, 0);
    mainLayout->addWidget(m_responseLineEdit, 2, 1, 1, 3);
    mainLayout->addWidget(m_statusLabel, 3, 0, 1, 5);
    mainLayout->addWidget(chartView, 4, 0, 1, 5);
    setLayout(mainLayout);
    setWindowTitle(tr("PMT Counter"));
    m_serialPortComboBox->setFocus();
    connect(m_runButton, &QPushButton::clicked, this, &Window::openSerialPort);
    connect(m_stopButton, &QPushButton::clicked, this, &Window::closeSerialPort);
    connect(m_requestButton, &QPushButton::clicked, this, &Window::writeData);
    connect(m_serial, &QSerialPort::errorOccurred, this, &Window::handleError);
    connect(m_serial, &QSerialPort::readyRead, this, &Window::readData);
    m_requestButton->setEnabled(false);
    m_stopButton->setEnabled(false);
Window::~Window()
void Window::openSerialPort()
    m_runButton->setEnabled(false);
    m_serial->setPortName(m_serialPortComboBox->currentText());
    m_serial->setBaudRate(QSerialPort::Baud115200);
    m_serial->setDataBits(QSerialPort::Data8);
    m_serial->setParity(QSerialPort::NoParity);
    m_serial->setStopBits(QSerialPort::OneStop);
    m_serial->setFlowControl(QSerialPort::NoFlowControl);
    if (m_serial->open(QIODevice::ReadWrite)) {
        m_statusLabel->setText(tr("Connected to %1 : %2, %3, %4, %5, %6")
                          .arg(m_serialPortComboBox->currentText()).arg(QSerialPort::Baud115200).arg(QSerialPort::Data8)
                          .arg(QSerialPort::NoParity).arg(QSerialPort::OneStop).arg(QSerialPort::NoFlowControl));
        m_requestButton->setEnabled(true);
        m_stopButton->setEnabled(true);
        m_serial->write("s");
        chart->start();
    } else {
        m_statusLabel->setText(tr("Open error"));
void Window::closeSerialPort()
    m_stopButton->setEnabled(false);
    m_serial->write("e");
    chart->stop();
    if (m_serial->isOpen())
        m_serial->close();
    m_requestButton->setEnabled(false);
    m_runButton->setEnabled(true);
    m_statusLabel->setText(tr("Disconnected"));
void Window::writeData()
    QByteArray data = m_requestLineEdit->text().toUtf8();
    m_serial->write(data);
void Window::readData()
    const QByteArray data = m_serial->readAll();
    static int i = 0;
    static unsigned int counts = 0;
    if (data.size() == 2) {
        i = i + 1;
        unsigned char char1 = data.data()[1];
        unsigned char char0 = data.data()[0];
        counts = counts + (char1 * 256 + char0);
    if (i == 15) {
        showData(counts);
        i = 0;
        counts = 0;
void Window::handleError(QSerialPort::SerialPortError error)
    if (error == QSerialPort::ResourceError) {
        QMessageBox::critical(this, tr("Critical Error"), m_serial->errorString());
        closeSerialPort();