最近粗略地学习了一下上位机的编程,大致了解了底层硬件与上位机之间的串口通信逻辑,TCP通信和UDP通信暂时还未学习。
本次把学习思路分享一下,主要学习视频是b站上北京迅为的QT教学视频,我的笔记也是在此基础上总结出来。很多细节在视频中已经介绍,篇幅有限,仅分享大致流程。
源码附在文章最后,希望和各位多多交流。
视频链接:【北京迅为】嵌入式学习之QT学习篇
同时分享一篇很好的相关博客:开源一款基于Qt的串口波形显示上位机 & 以“笔”会友
界面制作较为简陋,代码也较为粗糙,多多包涵。
下载地址:清华镜像网
★按以下步骤依次点开:
选择需要下载的版本,以v5.12.1为例:
下载windows版本:
安装教程视频中有详细介绍。唯一需要改变的是:
在选择安装组件时多勾选一个,用于后面的波形显示:
注意:对于新手,请务必看完视频的基础教程再继续浏览,后续不包含基础教学,只提及一些学习过程中的难点和关键点。
串口的打开和普通的数据收发在迅飞的视频中均有介绍,在此不做过多讲解。
/************************发送数据按钮槽函数************************/
void Widget::on_sendBt_clicked()
{
//将发送栏的数据输出
long long a=0;
//字符串形式
if(ui->is16SendQch->checkState() == false)
{
a=serialPort->write(ui->lineEdit->text().toLocal8Bit().data());
}
else //16进制发送(将16进制转换成Ascll码对应的字符发送)
{
a=serialPort->write(QByteArray::fromHex(ui->lineEdit->text().toUtf8()).data()); //16进制数据解码后发送
}
//如果发送成功,a获取发送的字节长度,发送失败则返回-1;
if(a > 0)
{
sendNum += a;
set_num_on_label(ui->sendLbl,"S:", sendNum); //显示发送的数据量
}
}
connect(serialPort, SIGNAL(readyRead()), this, SLOT(serialPortReadyRead_Slot()));
//把接收数据信号与接收槽函数关联,加在构造函数中。
/************************接收数据槽函数,需要手动关联************************/
void Widget::serialPortReadyRead_Slot()
{
QString buf;
QByteArray recBuf;
int bufNum;
uint8_t checkNum=0; //计算校验码
//读取所有字节
recBuf=serialPort->readAll();
//接收字节计数
recvNum += recBuf.size();
if(ui->is16RecvQch->checkState() == false) //不使用16进制接收
{
buf = QString(recBuf);
}
else
{
buf = QString(recBuf).toUtf8().toHex(); //转成16进制
//每个16进制数据之间用空格分隔
bufNum = buf.length();
while(bufNum-2 > 0)
{
bufNum = bufNum - 2;
buf.insert(bufNum," ");
}
}
ui->plainTextEdit->insertPlainText(buf); //把接收到的数据显示到接收栏上
ui->plainTextEdit->moveCursor(QTextCursor::End); //光标设置,确保滚轮滚动
//状态栏显示接收数据量
set_num_on_label(ui->recvLbl, "R:", recvNum);
}
/************************字符串/16进制发送切换槽函数************************/
void Widget::on_is16SendQch_stateChanged()
{
QString str;
int strNum;
if(ui->is16SendQch->checkState() == false) //转成字符串
{
ui->lineEdit->setText(QByteArray::fromHex(ui->lineEdit->text().toUtf8()).data()); //解码16进制
}
else //转成16进制
{
str=ui->lineEdit->text().toUtf8().toHex().data();
//每个16进制数据之间用空格分隔
strNum = str.length();
while(strNum-2 > 0)
{
strNum = strNum - 2;
str.insert(strNum," ");
}
ui->lineEdit->setText(str);
}
}
该代码实现当切换16进制/字符发送时,发送框内的数据会自动转换:
connect(&timer,SIGNAL(timeout()),this,SLOT(on_sendBt_clicked()));
//把定时器溢出信号与串口发送槽函数关联,加在构造函数中。
/************************定时发送开始/关闭槽函数************************/
void Widget::on_timeSendQch_stateChanged()
{
long counter;
if(ui->timeSendQch->checkState() == false) //定时发送关闭
{
timer.stop(); //关闭定时器
}
else //定时发送打开
{
counter = (ui->timeSendQl->text()).toLong(); //转换成长整型
if(counter>0 && ui->lineEdit->text() != "" && isSerialConnect == true) //定时时间需大于0ms且发送栏须有字符,否则关闭
{
timer.start(counter); //打开定时器,单位ms
}
else
{
timer.stop(); //关闭定时器
}
}
}
上图是由测试数据直接显示,基本没有问题。但是根据接收数据进行实时绘制会出现问题。
1.示波器显示可以最多两个通道;
2.可以控制波形显示的更新与暂停,以及波形1/波形2是否显示。
3.与匿名上位机等类似,需要使用特定的帧格式。
4.显示主题可以切换。
5.仍存在一些未解决的BUG(文章最后会提到)。
6.代码中包含测试波形的程序。
★波形显示基础界面借鉴了下面这篇博客,博客中博主也分享了源码:
QtChart——简单的动态波形图
在之前的就收槽函数中,加入了后面一部分波形数据的接收判断。
/************************接收数据槽函数,需要手动关联************************/
void Widget::serialPortReadyRead_Slot()
{
QString buf;
QByteArray recBuf;
int bufNum;
uint8_t checkNum=0; //计算校验码
//读取所有字节
recBuf=serialPort->readAll();
//接收字节计数
recvNum += recBuf.size();
if(ui->is16RecvQch->checkState() == false) //不使用16进制接收
{
buf = QString(recBuf);
}
else
{
buf = QString(recBuf).toUtf8().toHex(); //转成16进制
//每个16进制数据之间用空格分隔
bufNum = buf.length();
while(bufNum-2 > 0)
{
bufNum = bufNum - 2;
buf.insert(bufNum," ");
}
}
ui->plainTextEdit->insertPlainText(buf); //把接收到的数据显示到接收栏上
ui->plainTextEdit->moveCursor(QTextCursor::End); //光标设置,确保滚轮滚动
//状态栏显示计数值
set_num_on_label(ui->recvLbl, "R:", recvNum);
if(wa->isHidden() == false && //界面打开后才进行波形显示
static_cast<uint8_t>(recBuf[0]) == 0xAA && //同时要保证帧头和地址正确
static_cast<uint8_t>(recBuf[1]) == 0xCC)
{
//计算校验位
for(uint8_t i=3;i<7;i++)
checkNum += recBuf[i];
if(checkNum == static_cast<uint8_t>(recBuf[7])) //校验位正确
{
//数据类型转换并转移到Ware的属性中
wa->head = static_cast<uint8_t>(recBuf[0]);
wa->address = static_cast<uint8_t>(recBuf[1]);
wa->length = static_cast<uint8_t>(recBuf[2]);
wa->ware1 = static_cast<uint8_t>(recBuf[3])*256+static_cast<uint8_t>(recBuf[4]);
wa->ware2 = static_cast<uint8_t>(recBuf[5])*256+static_cast<uint8_t>(recBuf[6]);
wa->check = static_cast<uint8_t>(recBuf[7]);
wa->isValid = true;
}
else
{
wa->isValid = false;
}
wa->serial_updata_data(wa->isValid); //波形绘制
}
}
1.去除下列注释符号:
Ware::Ware(QWidget *parent)
{
//timer->setInterval(50); //定时50ms
//timer->start();
}
void Ware::init_slot()
{
//connect(timer, SIGNAL(timeout()), this, SLOT(timerSlot())); //定时器溢出信号
}
2.把下述代码用注释代码替换
void Ware::update_data()
{
//double dataY1,dataY2;
int16_t dataY1,dataY2;
//获取波形数据
//dataY1 = 10 * sin(M_PI * count * 4 / 180); //获取新数据
//dataY2 = 10 * cos(M_PI * count * 4 / 180);
dataY1 = ware1;
dataY2 = ware2;
}
其余代码直接下载源码查看。
然后在.pro文件中添加如下代码:
★USB.ico为图片文件名称。
(1).直接通过windows的搜索功能找到QT工作台:
(2).然后在电脑桌面上建立新的文件夹(注意:文件夹名称不能出现中文)
再把工程文件中的.exe文件复制到新文件夹中。
(3).接着在QT工作台中输入:cd /d C:\Users\pc\Desktop\serial tools(后面为新建的文件夹路径)进入文件夹。
(4).然后输入:windeployqt serial.exe进行封包:
(5).封包结束后即可在文件夹中看到应用文件,双击即可使用。
1.接收数据的波形数据高8位/低8位不能为0,否则无法正常显示波形;
2.使用测试数据进行波形绘制时不会卡顿;但是,当使用接收的数据进行波形绘制时,接收的数据量变大后容易卡顿,只有清除串口接收界面的数据才可以缓解;
3.鼠标移动可能会引起波形绘制错误。
基于QT的串口收发和波形绘制上位机程序