说明:用单片机采集并发送波形数据给QT,在QT上显示波形
通过 51 单片机与 Qt 应用程序进行串口通信,实现了单片机发送数据到 Qt 应用程序,Qt 应用程序则可以根据接收到的数据生成波形。
1.STC89C51单片机程序
1.数据采集:
使用ADC(模数转换器)或其他方法从硬件中读取电压值。
使用uart_init()
函数配置串口通信参数,包括波特率、数据位、停止位等,确保与Qt应用程序设置相匹配。
void uart_init()//选择需要的配置
{
SM0=0; //设置串口工作模式为 8 位数据位,无校验位,1 位停止位
SM1=1;
PCON=0; //SMOD=0
TMOD=0X20;//定义定时器T1为方式2
TH1=TL1=256-3;//设定波特率
TR1=1;//启动T1
REN=1; //串口接收功能
}
2.串口发送数据:
使用string()
函数发送数据。在本例中,通过串口发送JSON格式的数据,用于向Qt应用程序传递实时数据。
void string(unsigned char *p)//函数用于发送以空字符结尾的字符串
{
while(*p!=0)
{
SBUF=*p;//将字符串中的每个字符发送到串口
while(TI!=1);//等待发送完成
TI=0;//清除标志位
p++;
}
}
3.串口接收和中断处理:
通过设置串口接收中断函数uart()
,实现对从Qt应用程序发送回来的控制指令的接收和处理。
void uart()interrupt 4//uart() 是串口中断处理函数,处理串口接收的数据
{
if(RI==1)
{
temp=SBUF;
ta_temp[i]=temp;//当接收到数据时,将数据存储在 ta_temp 数组中
RI=0;
i++;
if(temp=='\n')//当接收到换行符 \n 时,将 flag_rx 置为 1,表示接收完成
{
flag_rx=1;
i=0;
temp=0;
}
RI=0;//清除接收中断标志位 RI
}
}
主要功能
周期性数据发送:在主循环中,单片机周期性地向串口发送数据。这些数据可以是传感器采集到的实时数据,如温度、湿度等。
串口中断处理:当单片机接收到来自Qt应用程序的指令时,通过中断处理函数处理这些指令,以实现远程控制和反馈。
2.QT示波器程序
示波器简介:在电子工程领域中,示波器是一种不可或缺的工具,它利用电子束在荧光屏上的位置变化来显示波形,帮助我们理解和分析电路的工作状态。而随着计算机技术的快速发展,基于QT的示波器显示技术正逐渐崭露头角,为我们带来了更为便捷和高效的解决方案。
在基于QT的示波器显示中,波形的生成和显示是关键环节。示波器通过接收电路中的电压信号,将其转换为电子束的位置变化,从而在荧光屏上形成波形。QT框架提供了强大的绘图功能,使得示波器能够实时地将波形数据绘制在界面上。
1.qcustomplot库
我用的是qcustomplot库来生成ui界面的波形图
官方下载链接Qt Plotting Widget QCustomPlot - 简介https://www.qcustomplot.com/index.php/introduction
我们需要在pro文件中加上这串代码,确保能够运行qcustomplot库
QT += core gui serialport charts
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport
CONFIG += c++11
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
main.cpp \
mainwindow.cpp \
qcustomplot.cpp
HEADERS += \
mainwindow.h \
qcustomplot.h
下一步我们需要在头文件.h中添加跟串口相关的声明
private:
Ui::MainWindow *ui;
/*-------------function----------------*/
void system_init();
QStringList getPortNameList();
/*-------------variable----------------*/
// 串口声明
QSerialPort global_port;
// 状态栏声明
QLabel *statusLabel;
QLabel *msgLabel;
// 串口相关
QString receivedDdata = "";
bool receivedFlag = false;
bool OpenorClose_Flag = false;
bool stop_flag = true;
// 示波器相关
bool oscill_flag = false;
QVector <QString> Ptext; // 每条曲线上的显示的文字
QVector <QVector<double>> YData; // Y轴数据,可具有多个数据曲线
QVector <double> XData; // x轴数据
double index = 0; // 索引
QString frontData;
QString backData;
QString oneData;
QStringList Received_Keys; // 标签值
};
2.与单片机进行串口通信
在程序中,我们可以使用Qt的串口通信类来实现与单片机的通信,使用Qt的信号与槽机制来处理用户事件。
1.初始化串口配置 :
目录
通过 51 单片机与 Qt 应用程序进行串口通信,实现了单片机发送数据到 Qt 应用程序,Qt 应用程序则可以根据接收到的数据生成波形。
在 system_init
函数中,初始化了串口的配置参数,如无奇偶校验位、8位数据位、1位停止位和无流控制。并连接了串口的 readyRead()
信号到 receiveInfo()
槽,这样当串口接收到数据时,就会触发 receiveInfo()
函数来处理数据。
void MainWindow::system_init()
{
// 这是statusbar的相关初始化
QLabel *permanent = new QLabel(this);
permanent->setFrameStyle(QFrame::Box | QFrame::Sunken);
permanent->setTextFormat(Qt::RichText);
permanent->setOpenExternalLinks(true);
ui->statusbar->addPermanentWidget(permanent);
statusLabel = new QLabel;
statusLabel->setMinimumSize(150, 20); // 设置标签最小大小
statusLabel->setFrameShape(QFrame::WinPanel); // 设置标签形状
statusLabel->setFrameShadow(QFrame::Sunken); // 设置标签阴影
ui->statusbar->addWidget(statusLabel);
statusLabel->setStyleSheet("color: white");
statusBar()->setStyleSheet("background-color : rgb(30,30,30)");
//PID 控制初始值控制
ui->lineEdit->setText(tr("0"));
ui->lineEdit_2->setText(tr("0"));
ui->lineEdit_3->setText(tr("0"));
ui->lineEdit_4->setText(tr("0"));
// 串口初始化配置
global_port.setParity(QSerialPort::NoParity); //无奇偶校验位
global_port.setDataBits(QSerialPort::Data8); //8位校验位
global_port.setStopBits(QSerialPort::OneStop); //停止位为1
global_port.setFlowControl(QSerialPort::NoFlowControl); //无流控制
connect(&global_port, SIGNAL(readyRead()), this, SLOT(receiveInfo()));
// 示波器初始化配置
ui->customPlot->setBackground(QBrush(QColor("#474848")));
}
2.打开或关闭串口 :
on_OpenorClose_clicked
槽函数用于打开或关闭串口。它首先检查 OpenorClose_Flag
变量来决定是打开还是关闭串口。如果打开串口,它会配置端口号和波特率,然后调用 open()
函数打开串口。
void MainWindow::on_OpenorClose_clicked()
{
/*--------------------------------
* 打开或者关闭串口
*-------------------------------*/
if (!this->OpenorClose_Flag)
{
// 打开串口
ui->OpenorClose->setText("关闭串口");
this->OpenorClose_Flag = true;
QString portId;
portId = ui->portId->currentText();
qDebug() << "portIds: " << portId;
global_port.setPortName(portId); //配置端口号
QString baudRate;
baudRate = ui->baudRate->currentText();
qDebug() << "baudRate: " << baudRate;
global_port.setBaudRate(baudRate.toInt(), QSerialPort::AllDirections); //配置波特率
// 配置完成,开启串口
global_port.open(QIODevice::ReadWrite);
qDebug() << "Open Serial Successfully";
}else{
// 关闭串口
global_port.close();
qDebug() << "Close Serial Successfully";
ui->OpenorClose->setText("打开串口");
this->OpenorClose_Flag = false;
}
}
3.发送PID数据 :
on_sendPIDData_clicked
槽函数用于发送PID相关数据到串口。在发送前,它会检查串口是否打开,如果打开,则将PID数据(包括P、I、D和目标值)格式化为JSON对象,并转换为字节数组,然后通过 write()
函数发送到串口。
void MainWindow::on_sendPIDData_clicked()
{
/*--------------------------------
* 发送PID相关数据
*-------------------------------*/
if (global_port.isOpen()){
QString P, I, D, targetVal;
QJsonObject sendPIDData;
P = ui->lineEdit->text();
I = ui->lineEdit_2->text();
D = ui->lineEdit_3->text();
targetVal = ui->lineEdit_4->text();
sendPIDData.insert("P", P.toFloat());
sendPIDData.insert("I", I.toFloat());
sendPIDData.insert("D", D.toFloat());
sendPIDData.insert("Target", targetVal.toFloat());
QJsonDocument document;
document.setObject(sendPIDData);
QByteArray pidbyte_array = document.toJson(QJsonDocument::Compact);
QString pidjson_str(pidbyte_array);
pidjson_str += '\n';
qDebug() << QString::fromLocal8Bit("PID Data:") << pidjson_str;
global_port.write(pidjson_str.toLocal8Bit());
}else{
QMessageBox box;
box.setWindowTitle(tr("警告"));
box.setWindowIcon(QIcon(":/Images/Icons/Alien 2.png"));
box.setIcon(QMessageBox::Warning);
box.setText(tr(" 未检测到串口,请确认串口是否打开?"));
box.exec();
}
}
4.发送任意串口数据 :
on_sendData_clicked
槽函数用于发送任意文本数据到串口。它读取 textEdit
控件中的文本,并发送到串口
void MainWindow::on_sendData_clicked()
{
/*--------------------------------
* 发送串口数据
*-------------------------------*/
if (global_port.isOpen()){
QString sendData;
sendData = ui->textEdit->toPlainText() + '\n';
qDebug() << sendData;
ui->textEdit->clear();
global_port.write(sendData.toLocal8Bit());
}else{
QMessageBox box;
box.setWindowTitle(tr("警告"));
box.setWindowIcon(QIcon(":/Images/Icons/Alien 2.png"));
box.setIcon(QMessageBox::Warning);
box.setText(tr(" 未检测到串口,请确认串口是否打开?"));
box.exec();
}
}
5.接收串口数据 :
receiveInfo
函数用于处理从串口接收到的数据。它读取所有可用的数据,并检查是否包含完整的JSON对象。如果包含,它会调用 plotCustom
函数来处理这些数据(例如,更新示波器)。
void MainWindow::receiveInfo()
{
/*--------------------------------
* 接受串口信息
*-------------------------------*/
QString data;
QByteArray info = global_port.readAll();
QStringList list;
if(!info.isEmpty())
{
if (info.contains('{'))
{
data = info;
list= data.split("{");
if (list.length() == 2){
if (frontData.isEmpty()){
frontData = list.at(1);
frontData.insert(0,'{');
return;
}
oneData = frontData + list.at(0);
frontData = list.at(1);
frontData.insert(0,'{');
plotCustom(oneData.toLocal8Bit()); //绘图
qDebug() << "One data: " << oneData;
}
if (list.length() == 3){
oneData = frontData + list.at(0);
plotCustom(oneData.toLocal8Bit()); //绘图
qDebug() << "One data: " << oneData;
oneData = list.at(1);
oneData.insert(0,'{');
plotCustom(oneData.toLocal8Bit()); //绘图
qDebug() << "One data: " << oneData;
frontData = list.at(2);
frontData.insert(0,'{');
}
}
}
}
在以上代码中,global_port
是一个 QSerialPort
对象,它提供了与串口通信所需的所有功能。通过配置这个对象的参数,连接信号和槽,以及调用它的方法(如 open()
, write()
, readAll()
),MainWindow
类实现了与单片机的串口通信。
需要注意的是,处理接收到的串口数据时,代码采用了字符串分割的方式来处理JSON数据的拼接,这可能不是最优的方式,尤其是当数据量较大或数据格式复杂时。在实际应用中,可能需要使用更健壮的JSON解析库来处理接收到的数据。此外,代码中的 frontData
和 oneData
变量用于处理跨多个 readyRead()
信号调用的数据拼接,这也是处理串口数据流时常见的做法。
3.显示波形
最后我们单片机连接QT上位机显示波形
由于我这边单片机没有给按钮,所以单片机程序写的是固定的值
51单片机连接上位机,把程序下载到单片机,打开qt代码运行,通过电压直接就生成波形了
总结:一个完整的Qt应用程序,包含了串口通信、数据处理、示波器绘图以及用户界面交互等功能。每个函数或槽都负责一个特定的任务,并通过Qt的信号和槽机制进行通信。通过这种方式,应用程序能够响应用户的操作,读取串口数据,并在示波器上实时显示这些数据。