[作品提交] 【DigiKey“智造万物,快乐不停”创意大赛】项目总帖:基于STM32MP157D频谱分析仪

suntongxue   2024-1-9 19:12 楼主

基于STM32MP157D频谱分析仪
作者:suntongxue

 

前言:

    随着电子通信产业的不断发展,频谱分析仪广泛运用在通信、电子、雷达及电子产品研发等领域,是电子工程师重要的频域分析工具。随着对于信号分析的不断发展,应实现频谱分析仪与上位机的通信。频谱分析仪可以通过网口、USB接口及串口向上位机发送数据,在上位机中,MATLAB、GNU radio等多种工具软件可以被用于处理该数据,扩展了频谱分析仪的功能。而且传统的频谱分析采用昂贵的FPGA (Xilinx ZYNQ)个人和小型团体均无力承受,本设计采用STM32MP157为核心实现频谱分析仪的设计与实现,STM32MP157微处理器基于的Arm® Cortex®-A7双核(工作频率800 MHz)和Cortex®-M4内核(工作频率209 MHz)架构,并配有一个专用的3D图形处理单元 (GPU),Cortex-A7 就是为了运行 Linux 这样的富操作系统,Cortex-M4可以看做一个M4内核的单片机。还需要很多部件:锁相环频率合成芯片ADF4110,压控振荡芯片MAX2606;乘法器AD835,AD835。通过STM32MP157 QT 开发人家交互GUI,实现USB或者USART实现与桌面端上位机实现高速信号通信。

系统设计概要:

  一)频谱分析仪概述:

    频谱分析仪是用来测量电信号频谱特征的仪器。使用频谱分析仪,可以观测到信号在频域中的分布情况、信号能量及其他频谱信息。分析仪的基础功能是以图形方式显示信号,其中 Y 轴表示幅度或功率电平,X 轴表示频率。所检测信号的幅度在频域中进行表示。射频频谱分析仪涵盖无线电和微波频率。目前,仪器可用的最大频率范围为 2 Hz 至 85 GHz,使用外部混频器时可扩展到更高频率。一般而言,X 轴的频率使用线性刻度,而 Y 轴的幅度则使用对数或分贝刻度(也属于对数刻度),这样可以同时查看幅度变化较大的信号。频谱分析仪广泛用于射频测试,不仅可以显示有用信号的属性(例如信号是否占用指定带宽),还可以搜索无用信号。射频测试几乎不再使用频谱分析仪来显示频率范围内的频谱分量,进而检测有用和无用信号的电平。许多现代脉冲信号性质各异,且需要检测和分析瞬态信号,意味着和无线电接收机采用相同的超外差原理的传统频谱分析仪无法可靠地检测所有间歇性瞬态信号,或测量信号相位。由于感兴趣的频率范围(频率跨度)超出频谱分析仪同时处理数据的能力,因此分析仪从低频到高频扫描频率跨度(扫频)。如果扫频时不存在瞬态信号,则不会检测到信号。通过快速傅里叶变换 (FTT) 从时域到频域进行数字处理,显著扩展了超外差频谱分析仪的信号检测和分析能力。FFT 能够更快地在频率跨度内捕获和分析信号:并行使用 FFT 可以提供更宽的瞬时带宽,因此可以利用合适的滤波器检测脉冲和瞬态信号。许多频谱分析仪还提供零跨度模式来分析信号的相位和幅度,并在选定的频率解调信号。除了在屏幕上简单表示检测到的信号之外,频谱分析仪还可以测量噪声、增益、相位、占用信号带宽和邻道功率。数字信号可供导出以使用软件工具进行后处理,以便提供附加分析结果。

二)系统框架:

     该系统由基于锁相环的本振源、混频器、低通滤波器,频谱测量几部分组成。由PLL芯片ADF4110,VCO芯片MAX2606等组成的锁相环频率合成器产生本振信号,经过乘法器实现幅度可调后输出80~110Mhz ,幅度在10~100mV的信号。在本振信号输出后加一级10倍的固定增益放大电路,放大后的本振信号和信号源产生的被测信号经过乘法器混频后,再经过低通滤波器滤除高频分量,经过STM32MP157AD采样经过LPF的信号后根据幅值大小将信号频谱及中心频率显示在基于QT开发的GUI显示屏上。

 

1.png
二)系统实现:
    STM32MP157 ,ADC,全称为 Analog-to-Digital Converter,即模数转换器,是一种电子电路或芯片,用于将模拟信号转换成数字信号。在模拟电路中,信号通常是以连续的形式进行表示和处理的,例如声音、温度、光强等都是模拟信号。而数字电路则是以离散的形式进行表示和处理的,例如计算机中处理的数据和图像等都是数字信号。为了将模拟信号转换成数字信号以便进行数字处理,需要使用 ADC 芯片进行转换。ADC 芯片通常由采样保持电路、模数转换电路和数字输出接口三个部分组成。采样保持电路用于对模拟信号进行采样和保持,以便进行转换;模数转换电路用于将采样信号转换成数字信号;数字输出接口则用于输出转换后的数字信号。在linux系统中,adc属于IIO(工业输入/输出)子系统。IIO(Industrial I/O)子系统是 Linux 内核中的一个框架,用于支持各种工业输入和输出设备的驱动程序。它提供了一种标准化的接口和框架,用于处理来自传感器、ADC(模数转换器)、DAC(数模转换器)和其他工业设备的数据。IIO 子系统的主要目标是为开发者提供一种统一的方法来访问各种类型的工业设备,而不必担心底层硬件的特定细节。通过 IIO 子系统,开发者可以使用一套通用的 API 和接口来读取和控制传感器数据,简化了跨不同设备的应用程序开发。IIO 子系统在 /sys/bus/iio/devices 目录下提供了一组文件,用于访问已连接的工业设备。这些文件包括设备的输入和输出接口、触发器、采样频率等信息。下面重点介绍涉及STM32MP157开发过程
 1.1 ADC实现:

    STM32MP157系列有2个ADC(ADC1和ADC2),每个ADC都可以独立工作,每个ADC都可以单独分配给A7或者M4内核来使用。ADC1和ADC2除了可以工作在独立模式以外,还可以在双重模式下工作(提高采样率,ADC1为主机),STM32MP157的ADC主要特性我们可以总结为以下几条:

(1)可配置16位、14位、12位、10位或8位分辨率,可通过降低分辨率来缩短转换时间,因为转换时间缩短,我们可以做到的采样率就越高。

(2)每个ADC具有多达20个的采集模拟通道,其中有6路快速通道和14路慢速通道,慢速和快速的区别主要是支持的最高采样率不同,慢速通道要比快速通道低。这些通道的A/D转换可以单次、连续、扫描或间断模式执行。

(3)ADC的结果可以左对齐或右对齐方式存储在32位数据寄存器中。

(4)ADC具有6条专用的内部通道,用于内部参考电压 (VREFINT ) 、内部温度传感器 (VSENSE )、VBAT 监测通道 (VBAT /4)、连接到DAC内部通道、VDDCORE监视通道。

(5)支持过采样,最高可以到26位采样率。

(6)每个ADC支持3路模拟看门狗。

(7)支持单独输入和差分输入。

(8)ADC输入范围:VREF– ≤ VIN ≤ VREF+。由VREF- 、VREF+ 、VDDA和VSSA 这四个外部引脚决定。把VSSA 和VREF- 接地,把 VREF+ 和VDDA接到3.3V,所以得到ADC 的输入电压范围是:0~3.3V。注意不要接超出这个范围的电压进来,否则容易烧坏芯片。

2.png
ADC采样程序设计
ADC.C
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include "adc.h"

float adc_data_float_deal(char *str)
{
    float data = atof(str);
    return data;
}

int adc_data_int_deal(char *str)
{
    int data = atoi(str);
    return data;
}

int file_data_read(char *filename, char *str)
{
    int ret = 0;

    FILE *data_stream = fopen(filename, "r"); /* 只读打开 */
    if (data_stream == NULL)
    {
        printf("can't open file filename:%s\n",filename);
        return -1;
    }
    ret = fscanf(data_stream, "%s", str);
    if (!ret)
    {
        printf("file read error!\r\n");
    }
    else if (ret == EOF)
    {
        /* 读到文件末尾的话将文件指针重新调整到文件头 */
        fseek(data_stream, 0, SEEK_SET);
    }

    fclose(data_stream);
    return 0;
}

int adc_read(struct adc_dev *dev)
{
    int ret = 0;
    char str[50];
    ret = file_data_read(ADC_SCALE,str);
    dev->scale = adc_data_float_deal(str);

    ret = file_data_read(ADC_RAW, str);
    dev->raw = adc_data_int_deal(str);

    dev->act = (dev->scale * dev->raw) / 1000.f;

    return ret;
}

adc.h

#ifndef __ADC_H__
#define __ADC_H__

#include <stdio.h>
#define ADC_SCALE "/sys/bus/iio/devices/iio:device0/in_voltage_scale"
#define ADC_RAW   "/sys/bus/iio/devices/iio:device0/in_voltage1_raw"

struct adc_dev {
    int raw;
    float scale;
    float act;
};

/**************************************************************************
 * 函数名称: ADC_open
 * 函数说明:打开驱动设备文件
 * 参   数:
 * 返回值 : 无  
 *          
 *************************************************************************/
extern int adc_read(struct adc_dev *dev);
extern float adc_data_float_deal(char *str);
extern int   adc_data_int_deal(char *str);

#endif

main.c

#include <stdio.h>
#include <unistd.h>
#include "adc.h"
int main(int argc, char const *argv[])
{
    struct adc_dev adc;
    int ret = 0;
    while(1)
    {
        ret = adc_read(&adc);
        if(ret == 0) {
            printf("ADC 原始值:%d, 电压值:%.3fV\n", adc.raw,adc.act);
        }
        sleep(1);
    }

    return 0;
}

STM32CubeIDE设计:

   STM32CubeMX的规则是先生成Kernel的dts, 然后将生成的dts文件拷贝到u-boot目录下,也就是说u-boot的设备树stm32mp157是从kernel目录拷贝过来的,所以在U-Boot阶段修改设备树,添加User code时,请同步修改kernel的设备树或者将修改好的设备树拷贝到Kernel目录,防止下次使用CubeMX生成设备树的时候,u-boot部分的修改被kernel未修改的设备树覆盖。

 

3.png

 

4.png

 

5.png

 

6.png

 


 

1.2 STM32MP157 QT实现

QT交叉开发环境实现,详细帖子另附链接。 QCustomPlot是一个用于绘图和数据可视化的Qt C++小部件。它没有进一步的依赖关系,提供友好的文档帮助。这个绘图库专注于制作好看的,出版质量的2D绘图,图形和图表,以及为实时可视化应用程序提供高性能。QCustomPlot可以导出各种格式,如矢量化的PDF文件和光栅化的图像,如PNG, JPG和BMP。QCustomPlot是用于在应用程序中显示实时数据以及为其他媒体生成高质量图的解决方案。

 

7.png


QtCreator配置
        新建一个Qt Widgets Application工程。

8.png
 
9.png

 

10.png

 

11.png

 

111.gif 点击上图查看Gif动图

界面:

12.png

 

13.png
逻辑程序设计:
#ifndef FFT_H
#define FFT_H

#include <QObject>
#include <QDebug>
/*
*
*
*               计算傅里叶变换频谱
*
*
* */
#define      MAX_MATRIX_SIZE                4194304             // 2048 * 2048
#define      PI                             3.141592653
#define      MAX_VECTOR_LENGTH              10000


typedef struct Complex
{
    double rl;              //实部 用这个当做y轴画图像就可以
    double im;              //虚部
}Complex;
class fft : public QObject
{
    Q_OBJECT
public:
    explicit fft(QObject *parent = nullptr);
//   bool fft1(Complex  const inVec[], int  const len, Complex  outVec[]);
    //傅里叶转换 频域
    bool fft1(QVector<Complex> inVec, int  const len, QVector<Complex>&outVec);

    //逆转换
   bool ifft(Complex  const inVec[], int  const len, Complex  outVec[]);

   int get_computation_layers(int  num);

   bool is_power_of_two(int  num);

   void test();

signals:

public slots:
};

#endif // FFT_H

 

 

15.png

 

 

 

 

 

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QPalette>

#include <algorithm>
using namespace std;

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MainWindow),
      mscChanged(100)
{
    ui->setupUi(this);

    this->setWindowTitle("Fourier Assistant");

    QIcon icon = QIcon(":/back/images/001.ico");
    this->setWindowIcon(icon);
    this->resize(1400, 900);
//    this->setWindowState(Qt::WindowMaximized);

    //背景图片
//    QPalette pal;
//    QPixmap pixmap(":/back/images/stop.png");
//    pal.setBrush(this->backgroundRole(), QBrush(pixmap));
//    this->setPalette(pal);

    toolBarInit();
    chartInit();
    pushButtonInit();
    lineEditInit();

    ui->checkBoxLP->setEnabled(false);
    ui->checkBoxBP->setEnabled(false);
    ui->checkBoxHP->setEnabled(false);

    QIntValidator *_int = new QIntValidator;
    _int->setRange(0, 5000000);
    ui->lineEditStart->setValidator(_int);  //输入框指定输入范围
    ui->lineEditEnd->setValidator(_int);

    connect(&m_thread, &threadReadData::request, this, &MainWindow::showData);
    connect(&progressBarTimer, &QTimer::timeout, this, &MainWindow::on_progressBar_valueChanged);
    progressBarTimer.setInterval(80);

    connect(&chartDataTimer, &QTimer::timeout, this, &MainWindow::handleTimeout);
    chartDataTimer.setInterval(mscChanged);

}

MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow::toolBarInit()
{
    ui->toolBar->setMinimumSize(QSize(0,50));
    ui->toolBar->setIconSize(QSize(40,40));
    ui->actionOpen->setEnabled(true);
    ui->actionStart->setEnabled(false);
    ui->actionPause->setEnabled(false);
    ui->actionStop->setEnabled(false);
    ui->actionForward->setEnabled(false);
    ui->actionBackward->setEnabled(false);
    ui->actionThemes->setEnabled(true);
    ui->actionSet->setEnabled(true);
}
//#include <QtCharts/QSplineSeries>
void MainWindow::chartInit()
{
    chartSource = new Chart;    //可选择数据源显示图表
    chartSourceFFT = new Chart; //可选择数据源频谱
    chartData = new Chart;  //顺序读取显示图表
    chartFFT = new Chart;   //实时频谱

    chartSource->m_series = new QLineSeries;
    chartSourceFFT->m_series = new QLineSeries;
    chartData->m_series = new QLineSeries;
    chartFFT->m_series = new QLineSeries;

    QPen green(Qt::red);
    green.setWidth(2);

    chartSource->legend()->hide();
    chartSource->setCursor(QCursor(Qt::OpenHandCursor));
//    chartSource->setTheme(QChart::ChartThemeBlueCerulean);
    //设置坐标轴
//    chartSource->m_series->setPen(green);
    chartSource->m_series->setUseOpenGL(true);
    chartSource->addSeries(chartSource->m_series);
    chartSource->addAxis(chartSource->m_axisX, Qt::AlignBottom);
    chartSource->addAxis(chartSource->m_axisY, Qt::AlignLeft);
    chartSource->m_axisX->setTickCount(6);
    chartSource->m_axisX->setMinorTickCount(2);
    chartSource->m_axisX->setRange(0, 1000);
    chartSource->m_axisY->setTickCount(4);
    chartSource->m_axisY->setMinorTickCount(2);
    chartSource->m_axisY->setRange(-1, 2);
//    chartSource->m_axisX->setLabelsAngle(60);
    chartSource->m_series->attachAxis(chartSource->m_axisX);
    chartSource->m_series->attachAxis(chartSource->m_axisY);
    ui->widgetSource->setRenderHint(QPainter::Antialiasing);  //设置抗锯齿
    ui->widgetSource->setRubberBand(QChartView::RectangleRubberBand);    //设置橡皮筋(放大缩小)
    ui->widgetSource->setChart(chartSource);

    chartSourceFFT->legend()->hide();
    chartSourceFFT->setCursor(QCursor(Qt::OpenHandCursor));
//    chartSourceFFT->setTheme(QChart::ChartThemeBlueCerulean);
    //设置坐标轴
    chartSourceFFT->m_series->setPen(green);
    chartSourceFFT->m_series->setUseOpenGL(true);
    chartSourceFFT->addSeries(chartSourceFFT->m_series);
    chartSourceFFT->addAxis(chartSourceFFT->m_axisX, Qt::AlignBottom);
    chartSourceFFT->addAxis(chartSourceFFT->m_axisY, Qt::AlignLeft);
    chartSourceFFT->m_axisX->setTickCount(4);
    chartSourceFFT->m_axisX->setMinorTickCount(3);
    chartSourceFFT->m_axisX->setRange(0, 512);
    chartSourceFFT->m_axisY->setTickCount(5);
    chartSourceFFT->m_axisY->setMinorTickCount(3);
    chartSourceFFT->m_axisY->setRange(0, 0.001);
//    chartData->m_axisX->setLabelsAngle(60);
    chartSourceFFT->m_series->attachAxis(chartSourceFFT->m_axisX);
    chartSourceFFT->m_series->attachAxis(chartSourceFFT->m_axisY);
    ui->widgetSourceFFT->setRenderHint(QPainter::Antialiasing);  //设置抗锯齿
    ui->widgetSourceFFT->setRubberBand(QChartView::RectangleRubberBand);    //设置橡皮筋(放大缩小)
    ui->widgetSourceFFT->setChart(chartSourceFFT);

    chartData->legend()->hide();
    chartData->setCursor(QCursor(Qt::OpenHandCursor));
//    chartData->setTheme(QChart::ChartThemeBlueCerulean);
    //设置坐标轴
//    chartData->m_series->setPen(green);
    chartData->m_series->setUseOpenGL(true);
    chartData->addSeries(chartData->m_series);
    chartData->addAxis(chartData->m_axisX, Qt::AlignBottom);
    chartData->addAxis(chartData->m_axisY, Qt::AlignLeft);
    chartData->m_axisX->setTickCount(10);
    chartData->m_axisX->setMinorTickCount(2);
    chartData->m_axisX->setRange(0, 1000);
    chartData->m_axisY->setTickCount(5);
    chartData->m_axisY->setMinorTickCount(2);
    chartData->m_axisY->setRange(-1, 2);
//    chartData->m_axisX->setLabelsAngle(60);
    chartData->m_series->attachAxis(chartData->m_axisX);
    chartData->m_series->attachAxis(chartData->m_axisY);
    ui->widgetData->setRenderHint(QPainter::Antialiasing);  //设置抗锯齿
    ui->widgetData->setRubberBand(QChartView::VerticalRubberBand);    //设置橡皮筋(放大缩小)
    ui->widgetData->setChart(chartData);


    chartFFT->legend()->hide();
    chartFFT->setCursor(QCursor(Qt::OpenHandCursor));
//    chartFFT->setTheme(QChart::ChartThemeBlueCerulean);
    chartFFT->setToolTip("chartFFT");
    //设置坐标轴
    chartFFT->m_series->setPen(green);
    chartFFT->m_series->setUseOpenGL(true);
    chartFFT->addSeries(chartFFT->m_series);
    chartFFT->addAxis(chartFFT->m_axisX, Qt::AlignBottom);
    chartFFT->addAxis(chartFFT->m_axisY, Qt::AlignLeft);
    chartFFT->m_axisX->setTickCount(11);
//    chartFFT->m_axisX->setMinorTickCount(2);
    chartFFT->m_axisX->setRange(0, 512);
    chartFFT->m_axisY->setTickCount(4);
    chartFFT->m_axisY->setMinorTickCount(2);
    chartFFT->m_axisY->setRange(0, 0.001);
//    chartData->m_axisX->setLabelsAngle(60);
    chartFFT->m_series->attachAxis(chartFFT->m_axisX);
    chartFFT->m_series->attachAxis(chartFFT->m_axisY);
    ui->widgetFFT->setRenderHint(QPainter::Antialiasing);  //设置抗锯齿
    ui->widgetFFT->setRubberBand(QChartView::VerticalRubberBand);    //设置橡皮筋(放大缩小)
    ui->widgetFFT->setChart(chartFFT);
}

void MainWindow::pushButtonInit()
{
    ui->pushButtonSource->setEnabled(false);
    QIcon icon9;
    icon9.addFile(QString::fromUtf8(":/back/images/down_select.png"), QSize(), QIcon::Normal, QIcon::Off);
//    ui->pushButtonSource->setIcon(icon9);
    ui->pushButtonSource->setStyleSheet(
                //正常状态样式
                "QPushButton{"
                "background-color:rgba(100,225,100,30);"//背景色(也可以设置图片)
                "border-style:outset;"                  //边框样式(inset/outset)
                "border-width:4px;"                     //边框宽度像素
                "border-radius:10px;"                   //边框圆角半径像素
                "border-color:rgba(255,255,255,30);"    //边框颜色
                "font:bold 10px;"                       //字体,字体大小
                "color:rgba(0,0,0,100);"                //字体颜色
                "padding:6px;"                          //填衬
                "outline:none;"                         //去掉焦点框
                "}"
                //鼠标按下样式
                "QPushButton:pressed{"
                "background-color:rgba(100,255,100,200);"
                "border-color:rgba(255,255,255,30);"
                "border-width:5px;"                     //修改边框宽度像素,目的时使按钮看起来有动态变化
                "border-style:inset;"
                "color:rgba(0,0,0,100);"
                "}"
                //鼠标悬停样式
                "QPushButton:hover{"
                "background-color:rgba(100,255,100,100);"
                "border-color:rgba(255,255,255,200);"
                "color:rgba(0,0,0,200);"
                "}");
}

void MainWindow::lineEditInit()
{
    ui->lineEditStart->setStyleSheet(
                "QLineEdit{"
                "background-color:rgba(100,225,100,0);"//背景色(也可以设置图片)
                "border-style:outset;"                  //边框样式(inset/outset)
                "border-width:4px;"                     //边框宽度像素
                "border-radius:10px;"                   //边框圆角半径像素
                "border-color:rgba(255,255,255,120);"    //边框颜色
                "font:bold 12px;"                       //字体,字体大小
                "color:rgba(0,0,0,150);"                //字体颜色
                "padding:5px;"                          //填衬
                "outline:none;"                         //去掉焦点框
                "}");
    ui->lineEditEnd->setStyleSheet(
                "QLineEdit{"
                "background-color:rgba(100,225,100,0);"//背景色(也可以设置图片)
                "border-style:outset;"                  //边框样式(inset/outset)
                "border-width:4px;"                     //边框宽度像素
                "border-radius:10px;"                   //边框圆角半径像素
                "border-color:rgba(255,255,255,120);"    //边框颜色
                "font:bold 12px;"                       //字体,字体大小
                "color:rgba(0,0,0,150);"                //字体颜色
                "padding:5px;"                          //填衬
                "outline:none;"                         //去掉焦点框
                "}");
    ui->lineEditLP->setStyleSheet(
                "QLineEdit{"
                "background-color:rgba(100,225,100,0);"//背景色(也可以设置图片)
                "border-style:outset;"                  //边框样式(inset/outset)
                "border-width:4px;"                     //边框宽度像素
                "border-radius:10px;"                   //边框圆角半径像素
                "border-color:rgba(255,255,255,120);"    //边框颜色
                "font:bold 12px;"                       //字体,字体大小
                "color:rgba(0,0,0,150);"                //字体颜色
                "padding:5px;"                          //填衬
                "outline:none;"                         //去掉焦点框
                "}");
    ui->lineEditHP->setStyleSheet(
                "QLineEdit{"
                "background-color:rgba(100,225,100,0);"//背景色(也可以设置图片)
                "border-style:outset;"                  //边框样式(inset/outset)
                "border-width:4px;"                     //边框宽度像素
                "border-radius:10px;"                   //边框圆角半径像素
                "border-color:rgba(255,255,255,120);"    //边框颜色
                "font:bold 12px;"                       //字体,字体大小
                "color:rgba(0,0,0,150);"                //字体颜色
                "padding:5px;"                          //填衬
                "outline:none;"                         //去掉焦点框
                "}");
    ui->lineEditLP->setText("0");
    ui->lineEditHP->setText("50");
}

void MainWindow::on_actionThemes_triggered()
{
    dialog = new Dialog(this);  /*新建一个窗口对象,this指针指定新的窗口父对象
                                            是MainWindow,销毁MainWindow时也会销毁子对象*/

    dialog->setModal(false);    //modal属性决定show()应该将弹出的dialog设置为模态还是非模态
    dialog->show();
}

void MainWindow::on_actionOpen_triggered()
{
    QString FileName = QFileDialog::getOpenFileName(this,
                                                    tr("文件对话框"),
                                                    QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
                                                    tr("ALL files (*);;Excel Files(*.xlsx);;Text Files(*.txt)"));
//    qDebug() << "filename:" << FileName;
    ui->labelPath->setText(FileName);
    m_thread.filename = FileName;
    if(FileName.contains(".xlsx", Qt::CaseSensitive) != true)
    {
        qDebug()<<"地址错误!";
        QMessageBox::information(nullptr, "地址错误", "地址错误,\n请重新选择!",
                                 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
        return;
    }

    progressBarTimer.start();

    ui->actionOpen->setEnabled(false);
    ui->actionStart->setEnabled(false);
    ui->actionPause->setEnabled(false);
    ui->actionStop->setEnabled(false);
    ui->actionForward->setEnabled(false);
    ui->actionBackward->setEnabled(false);
    m_thread.handleFunc();
}

void MainWindow::on_actionStart_triggered()
{
    ui->actionOpen->setEnabled(false);
    ui->actionStart->setEnabled(false);
    ui->actionPause->setEnabled(true);
    ui->actionStop->setEnabled(false);
    ui->actionForward->setEnabled(true);
    ui->actionBackward->setEnabled(true);

    chartDataTimer.start();
}

void MainWindow::on_actionStop_triggered()
{
    ui->actionOpen->setEnabled(true);
    ui->actionStart->setEnabled(false);
    ui->actionPause->setEnabled(false);
    ui->actionStop->setEnabled(false);
    ui->actionForward->setEnabled(false);
    ui->actionBackward->setEnabled(false);

    chartData->m_series->clear();
    m_buffer.clear();
    chartData->zoomReset();
    chartData->m_axisX->setRange(0, 1000);
    chartDataCount = 0;

    chartSource->m_series->clear();
    m_buffer_Source.clear();
    chartSource->zoomReset();
    chartSource->m_axisX->setRange(0, 1000);

    pauseFlag = 0;

}

void MainWindow::on_actionPause_triggered()
{
    if(pauseFlag == 0)
    {
        chartDataTimer.stop();
        ui->actionStop->setEnabled(true);
        ui->actionForward->setEnabled(false);
        ui->actionBackward->setEnabled(false);
        pauseFlag = 1;
    }else{
        chartDataTimer.start();
        ui->actionStop->setEnabled(false);
        ui->actionForward->setEnabled(true);
        ui->actionBackward->setEnabled(true);
        pauseFlag = 0;
    }
}

void MainWindow::on_actionBackward_triggered()
{
    if(mscChanged <= 10)
        ui->actionForward->setEnabled(true);
    mscChanged += 10;
    qDebug()<<mscChanged;
    chartDataTimer.setInterval(mscChanged);
    chartDataTimer.start(mscChanged);
}

void MainWindow::on_actionForward_triggered()
{
    if(mscChanged >= 10)
    {
        mscChanged -= 5;
    }else{
        ui->actionForward->setEnabled(false);
    }
    qDebug()<<mscChanged;
    chartDataTimer.setInterval(mscChanged);
    chartDataTimer.start(mscChanged);

}

//void MainWindow::on_actionReset_triggered()
//{
//    ui->actionOpen->setEnabled(true);
//    ui->actionStart->setEnabled(false);
//    ui->actionPause->setEnabled(false);
//    ui->actionStop->setEnabled(false);
//    ui->actionForward->setEnabled(false);
//    ui->actionBackward->setEnabled(false);
//    ui->actionReset->setEnabled(false);

//    chartData->m_series->clear();
//    m_buffer.clear();
//    chartData->zoomReset();
//    chartData->m_axisX->setRange(0, 1000);
//    chartDataCount = 0;

//    chartSource->m_series->clear();
//    m_buffer_Source.clear();
//    chartSource->zoomReset();
//    chartSource->m_axisX->setRange(0, 1000);

//    pauseFlag = 0;
//}

void MainWindow::on_actionSet_triggered()
{
    chartFFT->zoomReset();

}

void MainWindow::showData(const QVariant var)
{
//    QList<QList<QVariant>> excel_list;//用于将QVariant转换为Qlist的二维数组
    QVariantList varRows=var.toList();
    if(varRows.isEmpty())
    {
        QMessageBox::information(nullptr, "错误", "数据表格为空,\n请重新选择!",
                                 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
        ui->actionOpen->setEnabled(true);
        progressBarValue = 0;
        ui->progressBar->setValue(progressBarValue);
        progressBarTimer.stop();
        return;
    }
    const int row_count = varRows.size();
    qDebug()<<row_count;
    ui->labelRows->setNum(row_count);

    QString str = QString::number(row_count, 10);
    ui->lineEditEnd->setText(str);
    ui->lineEditEnd->setEnabled(false);
    max_count = row_count;
    QVariantList rowData;

    excel_list.clear();
    for(int i=0;i<row_count;++i)
    {
        rowData = varRows[i].toList();
        excel_list.push_back(rowData);
    }//转换完毕
//    qDebug()<<excel_list.at(2).at(2).toDouble();


    progressBarValue = 100; //数据读取完成,进度条显示100%

    ui->checkBoxLP->setEnabled(true);
    ui->checkBoxBP->setEnabled(true);
    ui->checkBoxHP->setEnabled(true);

    m_buffer.reserve(1000);
    for (int i = 0; i < 1000; ++i)
        m_buffer.append(QPointF(i, 0));

    for(int i = 0; i < 1000; i++)
    {
        m_buffer[i].setY(excel_list.at(i).at(2).toDouble());
    }
    chartData->m_series->replace(m_buffer);
}

void MainWindow::on_progressBar_valueChanged()
{
    ui->progressBar->setValue(progressBarValue);

    if(progressBarValue >= 100)
    {
        progressBarTimer.stop();
        progressBarValue = 0;
        ui->actionStart->setEnabled(true);
        ui->pushButtonSource->setEnabled(true);
        return;
    }
    if(progressBarValue < 99)
    {
        progressBarValue += 1;
    }
}

void MainWindow::on_pushButtonSource_clicked()
{
    QString _valueS = ui->lineEditStart->text();
    QString _valueE = ui->lineEditEnd->text();
    int valueS = _valueS.toInt();
    int valueE = _valueE.toInt();

    if(valueE - valueS < 512 || valueE > max_count)
    {
        QMessageBox::information(nullptr, "输入错误", "输入范围错误,\n请重新输入!",
                                 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
        return;
    }

    chartSource->m_series->clear();
    chartSource->zoomReset();
    m_buffer_Source.clear();
    chartSource->m_axisX->setRange(valueS, valueE + 1);

    //坐标对应,前面补零
//    m_buffer_Source.reserve(valueE);
//    for (int i = 0; i < valueE; i++)
//        m_buffer_Source.append(QPointF(i, 0));

//    for(int i = valueS; i < valueE; i++)
//    {
//        m_buffer_Source[i].setY(excel_list.at(i).at(2).toDouble());
//    }
    //坐标不对应,无补零
    int count = valueE - valueS;
    m_buffer_Source.reserve(count);
    for (int i = valueS; i < valueE; i++)
        m_buffer_Source.append(QPointF(i - valueS, 0));

    for(int i = valueS; i < valueE; i++)
    {
        m_buffer_Source[i - valueS].setY(excel_list.at(i).at(2).toDouble());
    }

    chartSource->m_series->replace(m_buffer_Source);

    QVector<Complex> inVec(1024);
    QVector<Complex> outVec(1024);
    qreal temp[512], mymax, mymin;


    for(int i = valueS; i < valueS + 1024; i++)
    {
        inVec[i - valueS].rl = excel_list.at(i).at(2).toDouble();
    }
    myfft.fft1(inVec, 1024, outVec);

    m_buffer_SourceFFT.reserve(512);
    for (int i = 0; i < 512; i++)
        m_buffer_SourceFFT.append(QPointF(i, 0));

    for(int i = 0; i < 512; i++)
    {
        temp[i] = qAbs(outVec[i].rl);
    }
    mymax = *max_element(temp, temp + 512);
    mymin = *min_element(temp, temp + 512);

    for(int i = 0; i < 512; i++)
    {
        m_buffer_SourceFFT[i].setY((temp[i] - mymin) / (mymax - mymin));
    }

    m_buffer_SourceFFT[0].setY(0); //去除直流分量
    chartSourceFFT->m_series->replace(m_buffer_SourceFFT);
    m_buffer_SourceFFT.clear();
}

//void MainWindow::on_actionModel_triggered()
//{
//    static int flag = 0;

//    if(flag == 0)
//    {
////        setWindowOpacity(0.5);设置窗口透明度
////        chartData->setBackgroundVisible(false);
////        setAttribute(Qt::WA_TranslucentBackground);

////        ui->widgetData->setStyleSheet("background: transparent");
//        flag = 1;
//    }else{
////        chartData->setBackgroundVisible(true);
////        setWindowOpacity(1);
//        flag = 0;
//    }
//}

void MainWindow::handleTimeout()
{
    qreal x = chartData->plotArea().width() / chartData->m_axisX->tickCount();

    m_buffer.reserve(100);

    if(1000 + 100 * chartDataCount >= max_count - 100)  //防止数据溢出
    {
        chartDataTimer.stop();
        return;
    }
    for (int i = 1000+100*chartDataCount; i < 1000+100*(chartDataCount+1); ++i)
    {
        m_buffer.append(QPointF(i, 0));
    }

    for (int i = 1000+100*chartDataCount; i < 1000+100*(chartDataCount+1); ++i)
    {
        m_buffer[i].setY(excel_list.at(i).at(2).toDouble());
    }
    chartData->m_series->replace(m_buffer);
    chartData->scroll(x, 0);
//    chartDataCount++;

//    QVector<Complex> inVec(1024);
//    QVector<Complex> outVec(1024);


//    for (int i = 0+100*chartDataCount; i < 1024+100*chartDataCount; i++)
//            inVec[i-100*chartDataCount].rl = excel_list.at(i).at(2).toDouble();
//    myfft.fft1(inVec, 1024, outVec);
//    m_bufferFFT.reserve(512);
//    for (int i = 0; i < 512; i++)
//        m_bufferFFT.append(QPointF(i, 0));

//    for(int i = 0; i < 512; i++)
//    {
//        outVec[i].rl = qAbs(outVec[i].rl);
//        m_bufferFFT[i].setY(outVec[i].rl);
//    }
//    m_bufferFFT[0].setY(0); //去除直流分量
//    chartFFT->m_series->replace(m_bufferFFT);
//    m_bufferFFT.clear();

    QVector<Complex> inVec(1024);
    QVector<Complex> outVec(1024);
    qreal temp[512], mymax, mymin;


    for (int i = 0+100*chartDataCount; i < 1024+100*chartDataCount; i++)
            inVec[i-100*chartDataCount].rl = excel_list.at(i).at(2).toDouble();
    myfft.fft1(inVec, 1024, outVec);
    m_bufferFFT.reserve(512);
    for (int i = 0; i < 512; i++)
    {
        m_bufferFFT.append(QPointF(i, 0));
        temp[i] = qAbs(outVec[i].rl);
    }
    mymax = *max_element(temp, temp + 512);
    mymin = *min_element(temp, temp + 512);

    for(int i = 0; i < 512; i++)
    {
        m_bufferFFT[i].setY((temp[i] - mymin) / (mymax - mymin));
    }

    QString _valueLP = ui->lineEditLP->text();
    QString _valueHP = ui->lineEditHP->text();
    int valueLP = _valueLP.toInt();
    int valueHP = _valueHP.toInt();

    if(ui->checkBoxLP->isChecked() == true)
    {
        for(int i = valueLP; i < 512; i++)
        {
            m_bufferFFT[i].setY(0);
        }
    }
    if(ui->checkBoxBP->isChecked() == true)
    {
        for(int i = 0; i < valueLP; i++)
        {
            m_bufferFFT[i].setY(0);
        }
        for(int i = valueHP; i < 512; i++)
        {
            m_bufferFFT[i].setY(0);
        }
    }
    if(ui->checkBoxHP->isChecked() == true)
    {
        for(int i = 0; i < valueHP; i++)
        {
            m_bufferFFT[i].setY(0);
        }
    }
    m_bufferFFT[0].setY(0); //去除直流分量
    chartFFT->m_series->replace(m_bufferFFT);
    m_bufferFFT.clear();

    chartDataCount++;
}

 

16.png
QTMP157.7z (885.48 KB)
(下载次数: 5, 2024-1-9 19:12 上传)

 

回复评论 (3)

附件为代码
点赞  2024-1-9 19:13

大佬这个作品,不管是功能,还是界面,设计的都是相当相当的用心。

 

但是没有看到附件代码?

点赞  2024-1-11 08:34

期待看到大佬的视频介绍。从主程序看,采集到的ADC好象没有开放给QT,是不是QT单独给了采集数据的模块。

点赞  2024-3-29 06:48
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复