(八) 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();