一.简介

最近粗略地学习了一下上位机的编程,大致了解了底层硬件与上位机之间的串口通信逻辑,TCP通信和UDP通信暂时还未学习。

本次把学习思路分享一下,主要学习视频是b站上北京迅为的QT教学视频,我的笔记也是在此基础上总结出来。很多细节在视频中已经介绍,篇幅有限,仅分享大致流程。

源码附在文章最后,希望和各位多多交流。

视频链接:【北京迅为】嵌入式学习之QT学习篇
同时分享一篇很好的相关博客:开源一款基于Qt的串口波形显示上位机 & 以“笔”会友

二.界面展示

界面制作较为简陋,代码也较为粗糙,多多包涵。

1.基础界面:

在这里插入图片描述

2.波形显示界面

在这里插入图片描述

三.QT软件下载

下载地址:清华镜像网

★按以下步骤依次点开:在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
选择需要下载的版本,以v5.12.1为例:
在这里插入图片描述

在这里插入图片描述
下载windows版本:
在这里插入图片描述
安装教程视频中有详细介绍。唯一需要改变的是
在选择安装组件时多勾选一个,用于后面的波形显示:
在这里插入图片描述

四.创建工程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五.工程文件结构

在这里插入图片描述

六.串口基本收发编写

注意:对于新手,请务必看完视频的基础教程再继续浏览,后续不包含基础教学,只提及一些学习过程中的难点和关键点。
串口的打开和普通的数据收发在迅飞的视频中均有介绍,在此不做过多讲解。

1.十六进制与字符之间的转换发送/接收

/************************发送数据按钮槽函数************************/
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);
}

2.以16进制/字符发送切换(接收暂未编程)

/************************字符串/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进制/字符发送时,发送框内的数据会自动转换:
在这里插入图片描述
在这里插入图片描述

3.定时发送

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——简单的动态波形图

1.波形数据接收并判断

在之前的就收槽函数中,加入了后面一部分波形数据的接收判断。

/************************接收数据槽函数,需要手动关联************************/
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);   //波形绘制
    }
}

2.使用波形测试数据时需要如下调整

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;
}

其余代码直接下载源码查看。

八.打包与部署(生成.exe文件)

1.把工程切换到release模式,然后编译。

在这里插入图片描述

2.找到release模式构建的文件夹

在这里插入图片描述

3.把图标文件添加到工程文件夹下,从而更换.exe文件的图标。(图标格式必须为.ico这个格式的)

在这里插入图片描述

然后在.pro文件中添加如下代码:
在这里插入图片描述
★USB.ico为图片文件名称。

4.使用QT的工作台进行封包的操作

(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的串口收发和波形绘制上位机程序