基于Qt打造的串口示波器

今年在电赛的准备中,发现没有一个合适的波形显示器,显示电压电流的变化曲线。所有的调差,我都感觉我在瞎调,让我觉得着实不舒服。于是我花了两天的时间学了下Qt的基本使用,然后就开始撸我需要的这个工具—可进行PID调参的串口示波器。前前后后用了3天半的时间吧,前端的设计都很简单,没有什么说的。哦,这里吹一手QCustomPlot,这是一个绘图Widget,非常好用,支持风格设置,拖拽,放大缩小,很是适合做示波器。其次就是QSerialPort串口接收大量数据时,会造成数据分段。正是由于这种分段,导致了单次收到的数据是不完整的,因此需要定义解析数据的方式,增加帧头和帧尾。

先看效果

在这里插入图片描述

先看源码的同学,直接去我的github上面吧:https://github.com/Miller-em/UART_Oscillscope,要是觉得不错的话,欢迎fork和star😃。

QCustomPlot的使用

Qt的串口的收发,怎么用的,网上一大推,我这里就不重复说一个内容了。这一节,主要聊聊,我是怎么实现customPlot的背景设置,拖拽,缩放等功能的。QCustomPlot这个是继承自QWidget,所以在Qt设计师中,直接拖一个Widget就好了,然后选择提升为QCustomPlot。然后就可以去编辑区写自己的业务需要了。

下面就进行程序开始时的配置,设置背景色,拖拽,缩放,可选和坐标range。

void MainWindow::buildChart()
{
    /*--------------------------------
     *  构造示波器
     *-------------------------------*/
    ui->customPlot->setBackground(QBrush(QColor("#474848"))); //
    ui->customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);      //可拖拽+可滚轮缩放
    ui->customPlot->yAxis->setRange(0, 3.3);
}

接收串口数据并绘图

这个地方,卡了我差不多一天,因为串口的ReadAll()那个API,读数据会断节,不是一条完整的数据,就不能完成绘图。然后我就找了很多解决方案,有人说用延时,我个人感觉不太靠谱(在我这种开发场景下)。另外一个就是加数据帧头和帧尾。然后我又觉得,网上定义那种复杂的数据解析格式,实在不好写,好吧,是我太菜了。思前想后,于是我想到了利用Json数据进行数据传输,而帧头和帧尾恰好是{},然后我更加偷懒,只检查{的位置,然后对断节的信息,进行拼接。

下面是实现代码:

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,'{');
            }
         }
    }
}

这是绘图函数:

void MainWindow::plotCustom(QByteArray info)
{
        //避免中文乱码
        QTextCodec *tc = QTextCodec::codecForName("GBK");
        QString tmpQStr = tc->toUnicode(info);

        // 若示波器打开,开始解析数据
        if (oscill_flag){
            QStringList datakeys;

            QJsonParseError jsonError;
            QJsonDocument jsonDoc = QJsonDocument::fromJson(info, &jsonError);

            if (jsonError.error != QJsonParseError::NoError)
            {
                qDebug() << "parse json object failed, " << jsonError.errorString();
                ui->textBrowser->append("parse json object failed!");
                return;
            }
            QJsonObject jsonObj = jsonDoc.object();

            qDebug() << "parse json object Successfully";
            datakeys = jsonObj.keys();      // 获取通道名称
            qDebug() << datakeys;

            if (Received_Keys != datakeys){
                // 只能够设置一次标签
                qDebug() << "update keys";
                Received_Keys = datakeys;

                this->index = 0;
                this->Ptext.clear();
                this->XData.clear();
                this->YData.clear();
                // 清除画布
                ui->customPlot->clearGraphs();
                ui->customPlot->legend->setVisible(false);
                ui->customPlot->replot();

                ui->customPlot->legend->setVisible(true);  //右上角指示曲线的缩略框
                ui->customPlot->legend->setBrush(QColor(100, 100, 100, 0));//设置图例背景颜色,可设置透明
                ui->customPlot->legend->setTextColor(Qt::white);
                for (int i=0; i < datakeys.size(); i++){
                    ui->customPlot->addGraph();
                    ui->customPlot->graph(i)->setPen(QPen(color[i]));
                    ui->customPlot->graph(i)->setName(datakeys[i]);
                    Ptext.push_back(datakeys[i]);
                }
            }

            // 添加XData , YData 数据
            XData.push_back(index);
            QVector<double> Data;
            YData.resize(datakeys.size());

            for (int i = 0;  i < Ptext.size(); ++i) {
                Data.push_back(jsonObj.value(datakeys[i]).toDouble());
            }
            for (int i = 0;  i < Ptext.size(); ++i) {
                YData[i].push_back(Data[i]);
            }

            //向坐标值赋值
            for (int i=0; i < datakeys.size(); ++i){
                ui->customPlot->graph(i)->addData(XData[index], YData[i][index]);
            }
            this->index++;
            // 更新坐标
            ui->customPlot->xAxis->setRange((ui->customPlot->graph(0)->dataCount()>1000)?
                                                (ui->customPlot->graph(0)->dataCount()-1000):
                                                0,
                                            ui->customPlot->graph(0)->dataCount());
            ui->customPlot->replot(QCustomPlot::rpQueuedReplot);  //实现重绘
        }
        // 向接收区打印
        ui->textBrowser->append(tmpQStr);
        qDebug() << "Received Data: " << tmpQStr;
}

不过,我这样解决问题有些取巧,对于那种很长的字符串,解析效果也是不好,会出现丢帧的情况。大概在50个字符以内基本还是不会出现丢数据的情况。后面如果想到更好的数据解析方式,我会做更改。

STM32 测试程序

由于我们的整个系统都是依靠的Json进行数据的传输,所以单片机那边发的数据就要是Json格式的数据,我这里使用的cJson的库,具体如何使用我也不准备说了,网上很多例子。

  1. 导入头文件

image-20210812205721944

  1. 然后在main函数里面向上位机串口发送两个正选数据:

    int main(void) {
        /* USER CODE BEGIN 1 */
        cJSON *cJson_test = NULL;     //创建头指针
        char* str = NULL;
    
        cJson_test = cJSON_CreateObject();    //创建头结点,并将头指针指向头结点
        /* USER CODE END 1 */
    
        /* MCU Configuration--------------------------------------------------------*/
    
        /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
        HAL_Init();
    
        /* USER CODE BEGIN Init */
    
        /* USER CODE END Init */
    
        /* Configure the system clock */
        SystemClock_Config();
    
        /* USER CODE BEGIN SysInit */
    
        /* USER CODE END SysInit */
    
        /* Initialize all configured peripherals */
        MX_GPIO_Init();
        MX_USART1_UART_Init();
        /* USER CODE BEGIN 2 */
        cJSON_AddNumberToObject(cJson_test, "ADC1", 0);
        cJSON_AddNumberToObject(cJson_test, "ADC2", 0);
        /* USER CODE END 2 */
    
        /* Infinite loop */
        /* USER CODE BEGIN WHILE */
        while (1) {
            double target_val;
            HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    
    //      printf("{\"ADC1\":1.333,\"ADC2\":2.555}");
    //      printf("{\"ADC1\":1.333}");
            for (int i = 0; i < N; ++i){
                target_val = (int)((sin(2*PI*i/N))*1000+0.5)/1000.0;
                cJSON_ReplaceItemInObject(cJson_test, "ADC1", cJSON_CreateNumber(target_val));
                target_val = (int)((cos(2*PI*i/N))*1000+0.5)/1000.0;
                cJSON_ReplaceItemInObject(cJson_test, "ADC2", cJSON_CreateNumber(target_val));
                str = cJSON_PrintUnformatted(cJson_test);
                printf("%s", str);
            }
            /* USER CODE END WHILE */
    
            /* USER CODE BEGIN 3 */
        }
        /* USER CODE END 3 */
    }
    
  • 23
    点赞
  • 163
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值