基于STM32的便携式数字示波器设计

举报
DS小龙哥 发表于 2025/12/25 11:47:50 2025/12/25
【摘要】 项目开发背景数字示波器在电子测量领域扮演着关键角色,用于实时捕获和分析电信号,支持电路设计、调试和故障排查等应用。然而,传统台式示波器通常体积较大、成本较高,且依赖于交流电源,难以满足现场测试、移动工作或教育场景中对便携性和经济性的需求。这导致许多小型实验室、学生项目或户外工程任务中缺乏便捷的测量工具。随着嵌入式技术和微控制器的进步,便携式数字示波器逐渐成为解决方案,通过集成高性能处理器和...

项目开发背景

数字示波器在电子测量领域扮演着关键角色,用于实时捕获和分析电信号,支持电路设计、调试和故障排查等应用。然而,传统台式示波器通常体积较大、成本较高,且依赖于交流电源,难以满足现场测试、移动工作或教育场景中对便携性和经济性的需求。这导致许多小型实验室、学生项目或户外工程任务中缺乏便捷的测量工具。

随着嵌入式技术和微控制器的进步,便携式数字示波器逐渐成为解决方案,通过集成高性能处理器和低功耗组件,实现小型化与功能多样化的平衡。STM32系列单片机以其强大的处理能力、丰富的外设接口和低成本优势,为开发此类设备提供了可靠平台,能够有效驱动实时数据采集和显示系统。

本项目聚焦于设计一款基于STM32的便携式数字示波器,旨在整合信号调理、高速模数转换和用户交互模块,实现对0-200kHz带宽信号的实时采集与分析。通过提供自动触发、电压频率测量、波形缩放平移以及FFT频域分析等功能,该设备力求以紧凑形态满足专业测量需求,为工程师、爱好者和教育机构提供一种灵活实用的工具,以填补市场对高性能便携示波器的空白。

设计实现的功能

(1)实现对0-200kHz带宽内输入信号的实时采集与波形显示。
(2)提供自动、常规、单次三种触发模式,并支持触发电平与边沿设置。
(3)具备电压幅值(Vpp,Vrms)与信号频率/周期的手动与自动测量功能。
(4)支持波形显示画面的缩放与平移操作。
(5)具备FFT频域分析功能,可将时域波形转换为频谱显示。

项目硬件模块组成

(1)主控与显示模块:采用STM32F103ZET6单片机驱动3.5寸TFT-LCD电阻触摸屏。
(2)信号调理模块:由OPA2350运放构成的衰减/放大电路,将输入信号调理至0-3.3V的ADC量程内。
(3)模数转换模块:直接使用STM32内置的12位ADC,配合DMA实现最高1Msps的采样率。
(4)用户输入模块:采用旋转编码器与独立按键组合,进行参数调节与功能切换。
(5)电源模块:采用MP1584EN DC-DC降压芯片将外部5V输入转换为系统所需的3.3V。

设计意义

该便携式数字示波器设计基于STM32平台,实现了对0-200kHz带宽内信号的实时采集与显示,结合触发、测量和频域分析功能,具有显著的实际应用价值。其小型化硬件构成,包括STM32F103ZET6主控、3.5寸TFT-LCD触摸屏和紧凑的电源模块,使得设备便于携带和现场使用,适合电子工程师、学生或爱好者在移动环境中进行信号调试和测试,克服了传统示波器体积庞大、不便移动的局限性。

该设计体现了嵌入式系统在仪器仪表领域的有效应用,通过利用STM32内置ADC和DMA技术实现最高1Msps采样率,展示了低成本硬件实现高速数据采集的可行性。这为嵌入式学习和项目开发提供了实践案例,有助于深入理解数字信号处理、实时系统设计和用户界面编程,推动技术教育和创新。

设计采用通用组件如OPA2350运放进行信号调理和旋转编码器进行交互,在保证功能完整性的同时控制了成本。这使得设备适用于预算有限的场景,如实验室教学、初创企业或个人项目,同时通过自动测量和FFT分析功能,扩展了其应用范围至频域分析,增强了实用性和灵活性。

整体而言,该设计通过集成实时波形显示、触发模式和测量功能,满足了基本示波器的需求,并为便携式电子测试工具的发展提供了参考。它强调实际应用,以STM32为核心实现了功能与便携性的平衡,有助于促进小型化、智能化测试仪器的普及和进步。

设计思路

该便携式数字示波器的设计核心在于利用STM32F103ZET6作为主控,通过高效的硬件模块协同工作,实现对输入信号的实时采集、处理与显示。系统首先通过信号调理模块将外部输入信号进行衰减或放大,确保其幅度适配STM32内置ADC的0-3.3V量程,从而安全且精确地处理0-200kHz带宽内的信号。这一调理过程由OPA2350运放构成的电路完成,它能有效保持信号完整性,为后续模数转换奠定基础。

模数转换模块直接使用STM32的12位ADC,配合DMA技术实现最高1Msps的采样率,确保对200kHz信号的充分采样,满足奈奎斯特定理要求。ADC采集的数据通过DMA直接传输到内存,减少了CPU负担,实现了高效的实时数据流处理。这为波形显示和后续分析提供了原始数据来源,支持自动、常规和单次三种触发模式,触发电平与边沿设置通过软件算法实现,基于采集数据与用户设定阈值的比较来稳定波形显示。

主控与显示模块由STM32驱动3.5寸TFT-LCD电阻触摸屏,负责波形渲染和用户界面管理。通过嵌入式图形库,系统能够实时绘制采集到的波形,并支持触摸和物理输入进行缩放与平移操作,方便用户观察信号细节。用户输入模块结合旋转编码器和独立按键,允许灵活调节参数如时间基、电压档位和触发设置,增强了仪器的交互性和便携性。

测量功能通过软件算法实现,包括电压幅值(如Vpp和Vrms)与信号频率/周期的计算,支持手动与自动模式,基于采集数据进行数字处理,确保测量精度。同时,系统集成FFT频域分析功能,利用STM32的运算能力将时域波形转换为频谱显示,帮助用户分析信号的频率成分,扩展了示波器的应用范围。

电源模块采用MP1584EN DC-DC降压芯片,将外部5V输入稳定转换为系统所需的3.3V,为整个硬件提供可靠供电,确保了便携式设计的稳定运行。整个系统通过软件固件整合各模块,优化资源分配,以实现功能需求的同时,保持低功耗和紧凑结构,适合现场使用。

框架图

系统框架图:

          +-----------------------------+
          |        输入信号             |
          |       (0-200kHz)           |
          +-----------------------------+
                     |
                     v
          +-----------------------------+
          |      信号调理模块           |
          |     (OPA2350运放电路)       |
          +-----------------------------+
                     |
                     v
          +-----------------------------+
          |      ADC模块               |
          | (内置12ADC, DMA, 1Msps)  |
          +-----------------------------+
                     |
                     v
+-----------------------------+     +-----------------------------+
|     用户输入模块            |     |      主控单元              |
| (旋转编码器, 独立按键)      |---->| (STM32F103ZET6单片机)      |
+-----------------------------+     +-----------------------------+
                                               |
                                               v
                                    +-----------------------------+
                                    |      显示单元              |
                                    | (3.5TFT-LCD触摸屏)       |
                                    +-----------------------------+
                                               ^
                                               |
                                    +-----------------------------+
                                    |      电源模块              |
                                    | (MP1584EN DC-DC, 5V转3.3V)|
                                    +-----------------------------+

系统总体设计

该系统总体设计基于STM32F103ZET6单片机为核心,实现便携式数字示波器的功能。系统从外部信号输入开始,通过信号调理模块将输入信号进行衰减或放大,确保其电压范围适配到0-3.3V以内,以匹配STM32内置ADC的量程要求。这一调理过程由OPA2350运放构成的电路完成,能够处理0-200kHz带宽内的信号,为后续采集提供稳定基础。

模数转换模块利用STM32的内置12位ADC,配合DMA技术实现高速数据采集,最高采样率可达1Msps,以满足实时采集与波形显示的需求。采集到的数据直接存储到内存中,由主控单元进行实时处理,确保波形能够及时更新并显示在3.5寸TFT-LCD电阻触摸屏上。LCD屏幕不仅用于波形可视化,还通过触摸功能辅助用户交互,但主要控制依赖于硬件输入模块。

用户输入模块采用旋转编码器与独立按键的组合,允许用户灵活调节参数,如触发电平、边沿设置以及显示缩放与平移。这些输入直接与主控交互,实现触发模式的切换,包括自动、常规和单次模式,确保波形捕获的精确性。同时,系统支持电压幅值测量,如Vpp和Vrms,以及信号频率与周期的手动与自动计算,测量结果实时显示在屏幕上,增强实用性。

系统还集成了FFT频域分析功能,主控单元对采集的时域波形数据进行快速傅里叶变换,将结果转换为频谱显示在LCD上,提供频域视角以辅助信号分析。整个系统由电源模块供电,MP1584EN DC-DC降压芯片将外部5V输入转换为稳定的3.3V电源,为各个硬件模块提供所需电压,确保系统在便携环境下可靠运行。

系统功能总结

系统功能 描述
信号采集与调理 实现对0-200kHz带宽内输入信号的实时采集,通过OPA2350运放构成的衰减/放大电路将信号调理至0-3.3V ADC量程,使用STM32内置12位ADC配合DMA实现最高1Msps采样率。
波形显示与操作 在3.5寸TFT-LCD电阻触摸屏上显示波形,支持缩放与平移操作。
触发功能 提供自动、常规、单次三种触发模式,并支持触发电平与边沿设置。
测量功能 具备电压幅值(Vpp, Vrms)与信号频率/周期的手动与自动测量功能。
频域分析功能 具备FFT频域分析功能,可将时域波形转换为频谱显示。
用户输入 采用旋转编码器与独立按键组合,进行参数调节与功能切换。
电源供应 采用MP1584EN DC-DC降压芯片将外部5V输入转换为系统所需的3.3V。

设计的各个功能模块描述

主控与显示模块采用STM32F103ZET6单片机作为核心控制器,驱动3.5寸TFT-LCD电阻触摸屏实现波形实时显示、界面交互以及参数设置,支持波形缩放、平移操作,并整合触发模式、测量功能和FFT频谱显示等控制逻辑。

信号调理模块由OPA2350运放构成的衰减与放大电路组成,负责将输入信号在0-200kHz带宽内进行调理,确保信号电压范围适配到0-3.3V以内,以供后续模数转换模块进行准确采集。

模数转换模块直接利用STM32内置的12位ADC,配合DMA技术实现最高1Msps的采样率,实现对输入信号的高速实时采集,满足波形显示和FFT频域分析的数据需求。

用户输入模块采用旋转编码器与独立按键的组合,允许用户进行触发电平、边沿设置、电压幅值与频率测量等参数调节,以及功能切换和操作控制。

电源模块采用MP1584EN DC-DC降压芯片,将外部输入的5V电压转换为系统所需的稳定3.3V电源,为各个硬件模块提供可靠的电能供应。

上位机代码设计

由于篇幅限制,我将提供一个精简但完整的基于Qt C++的示波器上位机软件框架代码。这个代码包含串口通信、波形显示、FFT分析、数据测量等核心功能。

// main.cpp
#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtCharts>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QTimer>

QT_CHARTS_USE_NAMESPACE

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void onConnectClicked();
    void onDisconnectClicked();
    void onStartStopClicked();
    void onSerialReadyRead();
    void updateWaveform();
    void processData(const QByteArray &data);
    void calculateFFT();
    void updateMeasurements();
    void onTriggerChanged();
    void onTimebaseChanged();
    void onVoltageScaleChanged();
    void onSaveDataClicked();
    void onLoadDataClicked();
    void onAutoScaleClicked();

private:
    Ui::MainWindow *ui;
    QSerialPort *serialPort;
    QTimer *dataTimer;
    QChart *timeDomainChart;
    QChart *freqDomainChart;
    QLineSeries *waveSeries;
    QLineSeries *fftSeries;
    QValueAxis *timeAxis;
    QValueAxis *voltageAxis;
    QValueAxis *freqAxis;
    QValueAxis *magnitudeAxis;
    
    // 数据缓冲区
    QVector<double> timeData;
    QVector<double> voltageData;
    QVector<double> fftData;
    QVector<double> freqBins;
    
    // 测量参数
    double vpp, vrms, frequency, period;
    double sampleRate;
    double timebase;
    double voltageScale;
    int triggerMode;
    double triggerLevel;
    bool triggerEdge;
    
    // 通信参数
    QByteArray buffer;
    bool isStreaming;
    
    void setupUI();
    void setupCharts();
    void setupSerialPort();
    void initializeVariables();
    void parseDataPacket(const QByteArray &packet);
    void applyTrigger();
    void performFFT();
    void calculateMeasurements();
};

#endif // MAINWINDOW_H
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QFileDialog>
#include <QFile>
#include <QTextStream>
#include <QDebug>
#include <complex>
#include <vector>
#include <algorithm>
#include <cmath>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    serialPort(new QSerialPort(this)),
    dataTimer(new QTimer(this)),
    timeDomainChart(new QChart()),
    freqDomainChart(new QChart()),
    waveSeries(new QLineSeries()),
    fftSeries(new QLineSeries())
{
    ui->setupUi(this);
    
    initializeVariables();
    setupUI();
    setupCharts();
    setupSerialPort();
    
    // 连接信号槽
    connect(dataTimer, &QTimer::timeout, this, &MainWindow::updateWaveform);
    connect(serialPort, &QSerialPort::readyRead, this, &MainWindow::onSerialReadyRead);
    
    // 界面控件信号连接
    connect(ui->connectButton, &QPushButton::clicked, this, &MainWindow::onConnectClicked);
    connect(ui->disconnectButton, &QPushButton::clicked, this, &MainWindow::onDisconnectClicked);
    connect(ui->startStopButton, &QPushButton::clicked, this, &MainWindow::onStartStopClicked);
    connect(ui->timebaseCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
            this, &MainWindow::onTimebaseChanged);
    connect(ui->voltageScaleCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
            this, &MainWindow::onVoltageScaleChanged);
    connect(ui->triggerCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
            this, &MainWindow::onTriggerChanged);
    connect(ui->saveButton, &QPushButton::clicked, this, &MainWindow::onSaveDataClicked);
    connect(ui->loadButton, &QPushButton::clicked, this, &MainWindow::onLoadDataClicked);
    connect(ui->autoScaleButton, &QPushButton::clicked, this, &MainWindow::onAutoScaleClicked);
}

MainWindow::~MainWindow()
{
    if(serialPort->isOpen())
        serialPort->close();
    delete ui;
}

void MainWindow::initializeVariables()
{
    vpp = vrms = frequency = period = 0;
    sampleRate = 1000000; // 1Msps
    timebase = 0.001; // 1ms/div
    voltageScale = 1.0; // 1V/div
    triggerMode = 0; // 自动触发
    triggerLevel = 1.65; // 中间电平
    triggerEdge = true; // 上升沿
    isStreaming = false;
    
    // 初始化数据缓冲区
    timeData.resize(1024);
    voltageData.resize(1024);
    fftData.resize(512);
    freqBins.resize(512);
    
    // 初始化时间轴
    for(int i = 0; i < 1024; i++) {
        timeData[i] = i * (1.0/sampleRate);
    }
}

void MainWindow::setupUI()
{
    // 设置串口列表
    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
        ui->portCombo->addItem(info.portName());
    }
    
    // 设置波特率
    ui->baudCombo->addItems({"9600", "19200", "38400", "57600", "115200", "230400", "460800", "921600"});
    ui->baudCombo->setCurrentText("115200");
    
    // 设置时基选项
    QStringList timebases;
    timebases << "1us/div" << "2us/div" << "5us/div" << "10us/div" << "20us/div" 
              << "50us/div" << "100us/div" << "200us/div" << "500us/div" << "1ms/div"
              << "2ms/div" << "5ms/div" << "10ms/div" << "20ms/div" << "50ms/div"
              << "100ms/div" << "200ms/div" << "500ms/div" << "1s/div";
    ui->timebaseCombo->addItems(timebases);
    ui->timebaseCombo->setCurrentIndex(9); // 1ms/div
    
    // 设置电压档位
    QStringList voltageScales;
    voltageScales << "10mV/div" << "20mV/div" << "50mV/div" << "100mV/div" << "200mV/div"
                  << "500mV/div" << "1V/div" << "2V/div" << "5V/div";
    ui->voltageScaleCombo->addItems(voltageScales);
    ui->voltageScaleCombo->setCurrentIndex(6); // 1V/div
    
    // 设置触发模式
    ui->triggerCombo->addItems({"自动", "常规", "单次"});
    
    // 初始化显示
    ui->vppLabel->setText("Vpp: --");
    ui->vrmsLabel->setText("Vrms: --");
    ui->freqLabel->setText("频率: --");
    ui->periodLabel->setText("周期: --");
}

void MainWindow::setupCharts()
{
    // 时域图设置
    waveSeries->setName("波形");
    waveSeries->setColor(Qt::blue);
    waveSeries->setUseOpenGL(true);
    
    timeDomainChart->addSeries(waveSeries);
    timeDomainChart->setTitle("时域波形");
    timeDomainChart->setAnimationOptions(QChart::NoAnimation);
    
    timeAxis = new QValueAxis();
    timeAxis->setTitleText("时间 (s)");
    timeAxis->setRange(0, timebase * 10); // 10格
    timeAxis->setLabelFormat("%.6f");
    
    voltageAxis = new QValueAxis();
    voltageAxis->setTitleText("电压 (V)");
    voltageAxis->setRange(-5, 5); // ±5V范围
    voltageAxis->setLabelFormat("%.3f");
    
    timeDomainChart->addAxis(timeAxis, Qt::AlignBottom);
    timeDomainChart->addAxis(voltageAxis, Qt::AlignLeft);
    waveSeries->attachAxis(timeAxis);
    waveSeries->attachAxis(voltageAxis);
    
    // 频域图设置
    fftSeries->setName("频谱");
    fftSeries->setColor(Qt::red);
    
    freqDomainChart->addSeries(fftSeries);
    freqDomainChart->setTitle("频域分析");
    freqDomainChart->setAnimationOptions(QChart::NoAnimation);
    
    freqAxis = new QValueAxis();
    freqAxis->setTitleText("频率 (Hz)");
    freqAxis->setRange(0, 50000); // 0-50kHz
    freqAxis->setLabelFormat("%.0f");
    
    magnitudeAxis = new QValueAxis();
    magnitudeAxis->setTitleText("幅度 (dB)");
    magnitudeAxis->setRange(-100, 0);
    
    freqDomainChart->addAxis(freqAxis, Qt::AlignBottom);
    freqDomainChart->addAxis(magnitudeAxis, Qt::AlignLeft);
    fftSeries->attachAxis(freqAxis);
    fftSeries->attachAxis(magnitudeAxis);
    
    // 设置到GraphicsView
    ui->timeChartView->setChart(timeDomainChart);
    ui->timeChartView->setRenderHint(QPainter::Antialiasing);
    
    ui->freqChartView->setChart(freqDomainChart);
    ui->freqChartView->setRenderHint(QPainter::Antialiasing);
}

void MainWindow::setupSerialPort()
{
    serialPort->setDataBits(QSerialPort::Data8);
    serialPort->setParity(QSerialPort::NoParity);
    serialPort->setStopBits(QSerialPort::OneStop);
    serialPort->setFlowControl(QSerialPort::NoFlowControl);
}

void MainWindow::onConnectClicked()
{
    if(ui->portCombo->currentText().isEmpty()) {
        QMessageBox::warning(this, "错误", "没有可用的串口!");
        return;
    }
    
    serialPort->setPortName(ui->portCombo->currentText());
    serialPort->setBaudRate(ui->baudCombo->currentText().toInt());
    
    if(serialPort->open(QIODevice::ReadWrite)) {
        ui->statusLabel->setText("已连接: " + ui->portCombo->currentText());
        ui->connectButton->setEnabled(false);
        ui->disconnectButton->setEnabled(true);
    } else {
        QMessageBox::critical(this, "错误", "无法打开串口!");
    }
}

void MainWindow::onDisconnectClicked()
{
    if(serialPort->isOpen()) {
        serialPort->close();
        ui->statusLabel->setText("已断开连接");
        ui->connectButton->setEnabled(true);
        ui->disconnectButton->setEnabled(false);
        if(isStreaming) {
            onStartStopClicked();
        }
    }
}

void MainWindow::onStartStopClicked()
{
    if(!serialPort->isOpen()) {
        QMessageBox::warning(this, "警告", "请先连接串口!");
        return;
    }
    
    if(!isStreaming) {
        // 开始采集
        QByteArray cmd = "START\r\n";
        serialPort->write(cmd);
        dataTimer->start(50); // 20Hz更新
        ui->startStopButton->setText("停止");
        ui->statusLabel->setText("正在采集数据...");
        isStreaming = true;
    } else {
        // 停止采集
        QByteArray cmd = "STOP\r\n";
        serialPort->write(cmd);
        dataTimer->stop();
        ui->startStopButton->setText("开始");
        ui->statusLabel->setText("已停止采集");
        isStreaming = false;
    }
}

void MainWindow::onSerialReadyRead()
{
    buffer.append(serialPort->readAll());
    
    // 查找完整的数据包(以换行符结束)
    while(buffer.contains('\n')) {
        int endIndex = buffer.indexOf('\n');
        QByteArray packet = buffer.left(endIndex);
        buffer = buffer.mid(endIndex + 1);
        
        if(packet.startsWith("DATA:")) {
            parseDataPacket(packet.mid(5)); // 去掉"DATA:"
        }
    }
}

void MainWindow::parseDataPacket(const QByteArray &packet)
{
    QList<QByteArray> values = packet.split(',');
    if(values.size() >= 1024) {
        voltageData.clear();
        for(int i = 0; i < 1024; i++) {
            // 转换为电压值(假设ADC参考电压3.3V,12位)
            double voltage = values[i].toDouble() * (3.3 / 4096.0);
            voltageData.append(voltage);
        }
        
        applyTrigger();
        updateMeasurements();
        calculateFFT();
    }
}

void MainWindow::applyTrigger()
{
    // 简单的触发处理
    if(triggerMode == 2) { // 单次触发
        static bool triggered = false;
        if(!triggered) {
            for(int i = 1; i < voltageData.size(); i++) {
                if(triggerEdge) { // 上升沿
                    if(voltageData[i-1] < triggerLevel && voltageData[i] >= triggerLevel) {
                        triggered = true;
                        break;
                    }
                } else { // 下降沿
                    if(voltageData[i-1] > triggerLevel && voltageData[i] <= triggerLevel) {
                        triggered = true;
                        break;
                    }
                }
            }
        }
        if(!triggered) {
            voltageData.fill(0);
        }
    }
}

void MainWindow::updateWaveform()
{
    waveSeries->clear();
    
    // 更新波形显示
    double maxVoltage = -9999, minVoltage = 9999;
    for(int i = 0; i < voltageData.size(); i++) {
        waveSeries->append(timeData[i], voltageData[i]);
        
        if(voltageData[i] > maxVoltage) maxVoltage = voltageData[i];
        if(voltageData[i] < minVoltage) minVoltage = voltageData[i];
    }
    
    // 自动调整Y轴范围
    if(ui->autoScaleCheck->isChecked()) {
        double range = maxVoltage - minVoltage;
        double center = (maxVoltage + minVoltage) / 2;
        voltageAxis->setRange(center - range * 0.7, center + range * 0.7);
    }
    
    // 更新FFT显示
    fftSeries->clear();
    for(int i = 0; i < fftData.size(); i++) {
        fftSeries->append(freqBins[i], fftData[i]);
    }
}

void MainWindow::calculateFFT()
{
    // 简单FFT实现(实际应使用FFTW或KissFFT等库)
    int N = voltageData.size();
    std::vector<std::complex<double>> x(N);
    
    // 转换为复数并应用窗函数
    for(int i = 0; i < N; i++) {
        // 汉宁窗
        double window = 0.5 * (1 - cos(2 * M_PI * i / (N - 1)));
        x[i] = std::complex<double>(voltageData[i] * window, 0);
    }
    
    // 简单DFT实现(性能较低,实际应用中应使用FFT算法)
    std::vector<std::complex<double>> X(N/2);
    fftData.clear();
    freqBins.clear();
    
    for(int k = 0; k < N/2; k++) {
        std::complex<double> sum(0, 0);
        for(int n = 0; n < N; n++) {
            double angle = -2 * M_PI * k * n / N;
            sum += x[n] * std::complex<double>(cos(angle), sin(angle));
        }
        X[k] = sum;
        
        // 转换为dB
        double magnitude = 20 * log10(abs(sum) / (N/2) + 1e-10);
        fftData.append(magnitude);
        freqBins.append(k * sampleRate / N);
    }
}

void MainWindow::updateMeasurements()
{
    // 计算Vpp
    double max = *std::max_element(voltageData.begin(), voltageData.end());
    double min = *std::min_element(voltageData.begin(), voltageData.end());
    vpp = max - min;
    
    // 计算Vrms
    double sumSquares = 0;
    for(double v : voltageData) {
        sumSquares += v * v;
    }
    vrms = sqrt(sumSquares / voltageData.size());
    
    // 简单频率测量(过零检测)
    int zeroCrossings = 0;
    for(int i = 1; i < voltageData.size(); i++) {
        if((voltageData[i-1] < 0 && voltageData[i] >= 0) ||
           (voltageData[i-1] > 0 && voltageData[i] <= 0)) {
            zeroCrossings++;
        }
    }
    
    frequency = zeroCrossings * sampleRate / (2.0 * voltageData.size());
    period = 1.0 / frequency;
    
    // 更新显示
    ui->vppLabel->setText(QString("Vpp: %1 V").arg(vpp, 0, 'f', 3));
    ui->vrmsLabel->setText(QString("Vrms: %1 V").arg(vrms, 0, 'f', 3));
    ui->freqLabel->setText(QString("频率: %1 Hz").arg(frequency, 0, 'f', 1));
    ui->periodLabel->setText(QString("周期: %1 ms").arg(period * 1000, 0, 'f', 3));
}

void MainWindow::onTimebaseChanged()
{
    QString timebaseStr = ui->timebaseCombo->currentText();
    timebaseStr.remove("s/div");
    
    if(timebaseStr.contains('u')) {
        timebaseStr.remove('u');
        timebase = timebaseStr.toDouble() * 1e-6;
    } else if(timebaseStr.contains('m')) {
        timebaseStr.remove('m');
        timebase = timebaseStr.toDouble() * 1e-3;
    } else {
        timebase = timebaseStr.toDouble();
    }
    
    // 计算采样点数
    int points = 10 * timebase * sampleRate; // 10格
    points = qMin(points, 10000); // 限制最大点数
    points = qMax(points, 100);   // 限制最小点数
    
    // 更新X轴范围
    timeAxis->setRange(0, timebase * 10);
    
    // 发送设置到下位机
    if(serialPort->isOpen() && isStreaming) {
        QByteArray cmd = QString("TIMEBASE:%1\r\n").arg(points).toLatin1();
        serialPort->write(cmd);
    }
}

void MainWindow::onVoltageScaleChanged()
{
    QString scaleStr = ui->voltageScaleCombo->currentText();
    scaleStr.remove("V/div");
    
    if(scaleStr.contains('m')) {
        scaleStr.remove('m');
        voltageScale = scaleStr.toDouble() * 1e-3;
    } else {
        voltageScale = scaleStr.toDouble();
    }
    
    // 发送设置到下位机
    if(serialPort->isOpen() && isStreaming) {
        QByteArray cmd = QString("VSCALE:%1\r\n").arg(voltageScale).toLatin1();
        serialPort->write(cmd);
    }
}

void MainWindow::onTriggerChanged()
{
    triggerMode = ui->triggerCombo->currentIndex();
    
    // 发送触发设置
    if(serialPort->isOpen() && isStreaming) {
        QByteArray cmd = QString("TRIGGER:%1,%2,%3\r\n")
                        .arg(triggerMode)
                        .arg(triggerLevel)
                        .arg(triggerEdge ? "1" : "0").toLatin1();
        serialPort->write(cmd);
    }
}

void MainWindow::onSaveDataClicked()
{
    QString fileName = QFileDialog::getSaveFileName(this, "保存数据", "", "CSV文件 (*.csv);;文本文件 (*.txt)");
    if(fileName.isEmpty()) return;
    
    QFile file(fileName);
    if(file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QTextStream stream(&file);
        stream << "时间(s),电压(V)\n";
        
        for(int i = 0; i < voltageData.size(); i++) {
            stream << QString("%1,%2\n").arg(timeData[i], 0, 'f', 9).arg(voltageData[i], 0, 'f', 6);
        }
        
        file.close();
        QMessageBox::information(this, "成功", "数据保存成功!");
    }
}

void MainWindow::onLoadDataClicked()
{
    QString fileName = QFileDialog::getOpenFileName(this, "加载数据", "", "CSV文件 (*.csv);;文本文件 (*.txt)");
    if(fileName.isEmpty()) return;
    
    QFile file(fileName);
    if(file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream stream(&file);
        stream.readLine(); // 跳过标题行
        
        voltageData.clear();
        timeData.clear();
        int index = 0;
        
        while(!stream.atEnd()) {
            QString line = stream.readLine();
            QStringList values = line.split(',');
            if(values.size() >= 2) {
                timeData.append(values[0].toDouble());
                voltageData.append(values[1].toDouble());
                index++;
            }
        }
        
        file.close();
        
        // 更新显示
        updateMeasurements();
        calculateFFT();
        updateWaveform();
        
        QMessageBox::information(this, "成功", "数据加载成功!");
    }
}

void MainWindow::onAutoScaleClicked()
{
    if(voltageData.isEmpty()) return;
    
    double max = *std::max_element(voltageData.begin(), voltageData.end());
    double min = *std::min_element(voltageData.begin(), voltageData.end());
    
    // 添加10%的边距
    double margin = (max - min) * 0.1;
    voltageAxis->setRange(min - margin, max + margin);
    
    // 自动调整时间轴
    if(!timeData.isEmpty()) {
        timeAxis->setRange(timeData.first(), timeData.last());
    }
}
// osciloscope.pro (Qt项目文件)
QT       += core gui serialport charts

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = Oscilloscope
TEMPLATE = app

DEFINES += QT_DEPRECATED_WARNINGS

SOURCES += \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    mainwindow.h

FORMS += \
    mainwindow.ui

这个示波器上位机软件包含以下主要功能:

  1. 串口通信:连接STM32下位机,接收波形数据
  2. 波形显示:实时显示时域波形
  3. 频域分析:FFT频谱显示
  4. 测量功能:Vpp、Vrms、频率、周期测量
  5. 参数设置:时基、电压档位、触发设置
  6. 数据存储:保存和加载波形数据
  7. 自动调节:自动调整显示范围

注意:实际应用中需要根据STM32下位机的具体通信协议修改数据解析部分,并可能需要使用更高效的FFT库(如FFTW)进行频域分析。

模块代码设计

STM32模块代码设计(寄存器方式)

1. 系统时钟配置

// system_clock.c
void SystemClock_Config(void) {
    // 使能外部8MHz晶振
    RCC->CR |= RCC_CR_HSEON;
    while(!(RCC->CR & RCC_CR_HSERDY));
    
    // FLASH预取指缓存和等待周期
    FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY_2;
    
    // AHB、APB1、APB2预分频
    RCC->CFGR |= RCC_CFGR_HPRE_DIV1;     // HCLK = SYSCLK = 72MHz
    RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;    // PCLK1 = HCLK/2 = 36MHz
    RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;    // PCLK2 = HCLK = 72MHz
    
    // PLL配置:HSE * 9 = 72MHz
    RCC->CFGR &= ~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL);
    RCC->CFGR |= RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9;
    
    // 使能PLL
    RCC->CR |= RCC_CR_PLLON;
    while(!(RCC->CR & RCC_CR_PLLRDY));
    
    // 选择PLL作为系统时钟源
    RCC->CFGR &= ~RCC_CFGR_SW;
    RCC->CFGR |= RCC_CFGR_SW_PLL;
    while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}

2. ADC模块配置(PA0通道)

// adc.c
#define ADC1_DR_Address ((uint32_t)0x4001244C)
#define BUFFER_SIZE 1024
volatile uint16_t adc_buffer[BUFFER_SIZE];
volatile uint32_t adc_index = 0;

void ADC1_Init(void) {
    // 使能ADC1时钟
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
    
    // 配置PA0为模拟输入
    GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);
    GPIOA->CRL |= GPIO_CRL_CNF0_0;  // 模拟输入模式
    
    // ADC复位
    RCC->APB2RSTR |= RCC_APB2RSTR_ADC1RST;
    RCC->APB2RSTR &= ~RCC_APB2RSTR_ADC1RST;
    
    // ADC校准
    ADC1->CR2 |= ADC_CR2_ADON;      // 开启ADC
    delay_us(1);
    ADC1->CR2 |= ADC_CR2_CAL;       // 开始校准
    while(ADC1->CR2 & ADC_CR2_CAL); // 等待校准完成
    
    // 配置ADC
    ADC1->CR1 = 0;
    ADC1->CR2 = 0;
    
    // 独立模式,数据右对齐
    ADC1->CR2 |= ADC_CR2_CONT;       // 连续转换模式
    ADC1->CR2 &= ~ADC_CR2_ALIGN;     // 右对齐
    
    // 采样时间:239.5周期(对应1Msps)
    ADC1->SMPR2 |= ADC_SMPR2_SMP0_0 | ADC_SMPR2_SMP0_1 | ADC_SMPR2_SMP0_2;
    
    // 规则序列:通道0,序列1
    ADC1->SQR1 = 0;
    ADC1->SQR3 = ADC_SQR3_SQ1_0;     // 通道0在序列1
    
    // 使能扫描模式
    ADC1->CR1 |= ADC_CR1_SCAN;
    
    // 使能ADC并开始转换
    ADC1->CR2 |= ADC_CR2_ADON;
    delay_us(1);
    ADC1->CR2 |= ADC_CR2_ADON;       // 第二次开启开始转换
}

void ADC1_DMA_Init(void) {
    // 使能DMA1时钟
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
    
    // 配置DMA1通道1(ADC1)
    DMA1_Channel1->CCR = 0;
    DMA1_Channel1->CCR |= DMA_CCR1_CIRC;      // 循环模式
    DMA1_Channel1->CCR |= DMA_CCR1_MINC;      // 存储器地址递增
    DMA1_Channel1->CCR &= ~DMA_CCR1_PINC;     // 外设地址不递增
    DMA1_Channel1->CCR &= ~DMA_CCR1_PSIZE;    // 外设数据宽度:16位
    DMA1_Channel1->CCR &= ~DMA_CCR1_MSIZE;    // 存储器数据宽度:16位
    DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;      // 外设到存储器
    
    // 设置数据数量
    DMA1_Channel1->CNDTR = BUFFER_SIZE;
    
    // 设置外设地址(ADC数据寄存器)
    DMA1_Channel1->CPAR = ADC1_DR_Address;
    
    // 设置存储器地址
    DMA1_Channel1->CMAR = (uint32_t)adc_buffer;
    
    // 使能DMA传输完成中断
    DMA1_Channel1->CCR |= DMA_CCR1_TCIE;
    
    // 配置NVIC
    NVIC_EnableIRQ(DMA1_Channel1_IRQn);
    NVIC_SetPriority(DMA1_Channel1_IRQn, 0);
    
    // 使能DMA
    DMA1_Channel1->CCR |= DMA_CCR1_EN;
    
    // 使能ADC的DMA请求
    ADC1->CR2 |= ADC_CR2_DMA;
}

// DMA1通道1中断服务函数
void DMA1_Channel1_IRQHandler(void) {
    if(DMA1->ISR & DMA_ISR_TCIF1) {
        // 传输完成
        DMA1->IFCR |= DMA_IFCR_CTCIF1;
        adc_index = BUFFER_SIZE;
    }
}

// 读取ADC值
uint16_t ADC1_Read(void) {
    ADC1->CR2 |= ADC_CR2_SWSTART;    // 开始转换
    while(!(ADC1->SR & ADC_SR_EOC)); // 等待转换完成
    return ADC1->DR;
}

3. 旋转编码器配置(PA6, PA7)

// encoder.c
volatile int32_t encoder_count = 0;
volatile uint8_t encoder_switch = 0;
static uint8_t last_state = 0;

void Encoder_Init(void) {
    // 使能GPIOA时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    
    // PA6, PA7配置为上拉输入
    GPIOA->CRL &= ~(GPIO_CRL_CNF6 | GPIO_CRL_MODE6 | 
                    GPIO_CRL_CNF7 | GPIO_CRL_MODE7);
    GPIOA->CRL |= GPIO_CRL_CNF6_1 | GPIO_CRL_CNF7_1;  // 上拉/下拉输入
    GPIOA->ODR |= GPIO_ODR_ODR6 | GPIO_ODR_ODR7;      // 上拉
    
    // 初始化状态
    last_state = (GPIOA->IDR >> 6) & 0x03;
}

void Encoder_Process(void) {
    uint8_t current_state = (GPIOA->IDR >> 6) & 0x03;
    
    // 状态变化检测
    static const int8_t state_table[] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
    
    // 计算状态索引
    uint8_t index = (last_state << 2) | current_state;
    
    // 更新计数
    encoder_count += state_table[index];
    
    // 保存当前状态
    last_state = current_state;
}

// 获取编码器计数值
int32_t Encoder_GetCount(void) {
    int32_t count;
    __disable_irq();
    count = encoder_count;
    encoder_count = 0;  // 读取后清零
    __enable_irq();
    return count;
}

4. 独立按键配置(PA8, PA9, PA10)

// buttons.c
#define BUTTON_COUNT 3
volatile uint8_t button_state[BUTTON_COUNT] = {0};
volatile uint32_t button_press_time[BUTTON_COUNT] = {0};

void Buttons_Init(void) {
    // 使能GPIOA时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    
    // PA8, PA9, PA10配置为上拉输入
    GPIOA->CRH &= ~(GPIO_CRH_CNF8 | GPIO_CRH_MODE8 |
                    GPIO_CRH_CNF9 | GPIO_CRH_MODE9 |
                    GPIO_CRH_CNF10 | GPIO_CRH_MODE10);
    
    GPIOA->CRH |= GPIO_CRH_CNF8_1 | GPIO_CRH_CNF9_1 | GPIO_CRH_CNF10_1;  // 上拉/下拉输入
    GPIOA->ODR |= GPIO_ODR_ODR8 | GPIO_ODR_ODR9 | GPIO_ODR_ODR10;        // 上拉
}

void Buttons_Scan(void) {
    static uint32_t last_time = 0;
    uint32_t current_time = SysTick_GetTick();
    
    // 10ms扫描一次
    if(current_time - last_time < 10) return;
    last_time = current_time;
    
    // 读取按键状态(按下为0)
    uint8_t raw_state = ~(GPIOA->IDR >> 8) & 0x07;
    
    for(int i = 0; i < BUTTON_COUNT; i++) {
        if(raw_state & (1 << i)) {
            // 按键按下
            if(button_state[i] == 0) {
                button_state[i] = 1;  // 按下标记
                button_press_time[i] = current_time;
            } else {
                // 长按检测(超过1秒)
                if((current_time - button_press_time[i]) > 1000) {
                    button_state[i] = 2;  // 长按标记
                }
            }
        } else {
            // 按键释放
            if(button_state[i] == 1) {
                // 短按
                button_state[i] = 3;  // 短按事件
            } else if(button_state[i] == 2) {
                // 长按释放
                button_state[i] = 4;  // 长按释放事件
            } else {
                button_state[i] = 0;  // 无按键
            }
        }
    }
}

// 获取按键事件
uint8_t Button_GetEvent(uint8_t button_num) {
    if(button_num >= BUTTON_COUNT) return 0;
    
    uint8_t event = button_state[button_num];
    
    // 清除事件标记(除按下状态外)
    if(event == 3 || event == 4) {
        button_state[button_num] = 0;
    }
    
    return event;
}

5. 定时器配置(用于采样率控制)

// timer.c
void TIM2_Init(uint32_t frequency) {
    // 使能TIM2时钟
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
    
    // 定时器复位
    RCC->APB1RSTR |= RCC_APB1RSTR_TIM2RST;
    RCC->APB1RSTR &= ~RCC_APB1RSTR_TIM2RST;
    
    // 计算ARR和PSC值
    uint32_t arr_value = (72000000 / frequency) - 1;
    
    // 配置预分频器
    TIM2->PSC = 0;  // 不分频
    
    // 配置自动重装载值
    TIM2->ARR = arr_value;
    
    // 配置更新中断
    TIM2->DIER |= TIM_DIER_UIE;
    
    // 配置NVIC
    NVIC_EnableIRQ(TIM2_IRQn);
    NVIC_SetPriority(TIM2_IRQn, 1);
    
    // 使能定时器
    TIM2->CR1 |= TIM_CR1_CEN;
}

void TIM2_SetFrequency(uint32_t frequency) {
    // 禁用定时器
    TIM2->CR1 &= ~TIM_CR1_CEN;
    
    // 计算新的ARR值
    uint32_t arr_value = (72000000 / frequency) - 1;
    
    // 更新ARR
    TIM2->ARR = arr_value;
    
    // 重新使能定时器
    TIM2->CR1 |= TIM_CR1_CEN;
}

// TIM2中断服务函数
void TIM2_IRQHandler(void) {
    if(TIM2->SR & TIM_SR_UIF) {
        TIM2->SR &= ~TIM_SR_UIF;
        
        // 触发ADC采样
        ADC1->CR2 |= ADC_CR2_SWSTART;
    }
}

6. 系统滴答定时器

// systick.c
volatile uint32_t systick_counter = 0;

void SysTick_Init(void) {
    // 配置SysTick为1ms中断
    SysTick->LOAD = 72000 - 1;  // 72MHz/1000 = 72000
    SysTick->VAL = 0;
    SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | 
                   SysTick_CTRL_TICKINT_Msk | 
                   SysTick_CTRL_ENABLE_Msk;
    
    // 配置NVIC
    NVIC_SetPriority(SysTick_IRQn, 0);
}

uint32_t SysTick_GetTick(void) {
    return systick_counter;
}

void delay_ms(uint32_t ms) {
    uint32_t start = systick_counter;
    while((systick_counter - start) < ms);
}

void SysTick_Handler(void) {
    systick_counter++;
}

7. 主程序框架

// main.c
#include "stm32f10x.h"

// 函数声明
void SystemClock_Config(void);
void GPIO_Init(void);
void ADC1_Init(void);
void ADC1_DMA_Init(void);
void Encoder_Init(void);
void Buttons_Init(void);
void TIM2_Init(uint32_t freq);
void SysTick_Init(void);

// 波形处理缓冲区
#define WAVE_BUFFER_SIZE 1024
uint16_t wave_buffer[WAVE_BUFFER_SIZE];
volatile uint8_t data_ready = 0;

int main(void) {
    // 系统初始化
    SystemClock_Config();
    SysTick_Init();
    GPIO_Init();
    
    // 外设初始化
    ADC1_Init();
    ADC1_DMA_Init();
    Encoder_Init();
    Buttons_Init();
    TIM2_Init(100000);  // 初始100kHz采样率
    
    // LCD初始化(简化)
    LCD_Init();
    LCD_Clear(0x0000);
    
    // 主循环
    while(1) {
        // 处理按键
        Buttons_Scan();
        
        // 处理编码器
        Encoder_Process();
        int32_t encoder_change = Encoder_GetCount();
        if(encoder_change != 0) {
            // 调整参数(如触发电平、时基等)
            AdjustParameter(encoder_change);
        }
        
        // 检查按键事件
        for(int i = 0; i < 3; i++) {
            uint8_t event = Button_GetEvent(i);
            if(event == 3) {  // 短按
                HandleButtonPress(i);
            } else if(event == 4) {  // 长按
                HandleButtonLongPress(i);
            }
        }
        
        // 处理ADC数据
        if(data_ready) {
            ProcessWaveData();
            DisplayWaveform();
            data_ready = 0;
        }
        
        // 执行FFT分析(按需)
        if(fft_request) {
            PerformFFT();
            DisplaySpectrum();
            fft_request = 0;
        }
    }
}

// 波形数据处理函数
void ProcessWaveData(void) {
    // 复制DMA缓冲区数据
    for(int i = 0; i < BUFFER_SIZE; i++) {
        wave_buffer[i] = adc_buffer[i];
    }
    
    // 触发检测
    if(trigger_mode != TRIGGER_NONE) {
        ApplyTrigger(wave_buffer, BUFFER_SIZE);
    }
    
    // 电压测量
    CalculateVoltage(wave_buffer, BUFFER_SIZE);
    
    // 频率测量
    CalculateFrequency(wave_buffer, BUFFER_SIZE);
}

// LCD显示函数(简化框架)
void DisplayWaveform(void) {
    // 清屏
    LCD_ClearArea(0, 0, 320, 240, 0x0000);
    
    // 绘制网格
    DrawGrid();
    
    // 绘制波形
    for(int i = 0; i < WAVE_BUFFER_SIZE - 1; i++) {
        int x1 = i * 320 / WAVE_BUFFER_SIZE;
        int y1 = 240 - (wave_buffer[i] * 240 / 4096);
        int x2 = (i + 1) * 320 / WAVE_BUFFER_SIZE;
        int y2 = 240 - (wave_buffer[i + 1] * 240 / 4096);
        LCD_DrawLine(x1, y1, x2, y2, 0x07E0);  // 绿色波形
    }
    
    // 显示测量结果
    DisplayMeasurements();
}

// GPIO初始化
void GPIO_Init(void) {
    // 使能所有GPIO时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | 
                   RCC_APB2ENR_IOPCEN | RCC_APB2ENR_IOPDEN | 
                   RCC_APB2ENR_IOPEEN;
}

8. 测量功能实现

// measurements.c
typedef struct {
    float vpp;      // 峰峰值
    float vrms;     // RMS值
    float freq;     // 频率
    float period;   // 周期
} Measurement_t;

Measurement_t measurements;

void CalculateVoltage(uint16_t *buffer, uint32_t size) {
    uint16_t min = 4095, max = 0;
    uint32_t sum = 0;
    uint32_t sum_sq = 0;
    
    for(uint32_t i = 0; i < size; i++) {
        uint16_t value = buffer[i];
        
        // 查找最大值和最小值
        if(value < min) min = value;
        if(value > max) max = value;
        
        // 累加用于计算平均值和RMS
        sum += value;
        sum_sq += value * value;
    }
    
    // 计算Vpp(假设3.3V参考电压)
    measurements.vpp = (max - min) * 3.3f / 4096.0f;
    
    // 计算Vrms
    float avg = (float)sum / size;
    float avg_sq = (float)sum_sq / size;
    float variance = avg_sq - (avg * avg);
    measurements.vrms = sqrt(variance) * 3.3f / 4096.0f;
}

void CalculateFrequency(uint16_t *buffer, uint32_t size) {
    // 寻找过零点
    uint32_t zero_crossings[50];
    uint32_t cross_count = 0;
    
    for(uint32_t i = 1; i < size && cross_count < 50; i++) {
        // 检测从负到正的过零点(以2048为零点)
        if(buffer[i-1] < 2048 && buffer[i] >= 2048) {
            zero_crossings[cross_count++] = i;
        }
    }
    
    if(cross_count >= 2) {
        // 计算平均周期(采样点)
        uint32_t total_samples = 0;
        for(uint32_t i = 1; i < cross_count; i++) {
            total_samples += zero_crossings[i] - zero_crossings[i-1];
        }
        
        float avg_period_samples = (float)total_samples / (cross_count - 1);
        
        // 转换为时间和频率
        measurements.period = avg_period_samples / current_sample_rate;
        measurements.freq = 1.0f / measurements.period;
    } else {
        measurements.period = 0;
        measurements.freq = 0;
    }
}

9. FFT实现(简化)

// fft.c
#include <math.h>

#define FFT_SIZE 256
#define PI 3.14159265358979323846f

typedef struct {
    float real;
    float imag;
} Complex_t;

Complex_t fft_input[FFT_SIZE];
float fft_output[FFT_SIZE/2];

void PerformFFT(void) {
    // 准备输入数据(汉宁窗)
    for(int i = 0; i < FFT_SIZE; i++) {
        float sample = wave_buffer[i] * 3.3f / 4096.0f;
        float window = 0.5f * (1 - cos(2 * PI * i / (FFT_SIZE - 1)));
        fft_input[i].real = sample * window;
        fft_input[i].imag = 0;
    }
    
    // 执行基2 FFT
    FFT_Base2(fft_input, FFT_SIZE);
    
    // 计算幅度谱
    for(int i = 0; i < FFT_SIZE/2; i++) {
        float real = fft_input[i].real;
        float imag = fft_input[i].imag;
        fft_output[i] = sqrt(real*real + imag*imag);
    }
}

// 基2 FFT算法
void FFT_Base2(Complex_t *data, int n) {
    int i, j, k, m;
    Complex_t temp, w, wn;
    float angle;
    
    // 位反转置换
    j = 0;
    for(i = 0; i < n-1; i++) {
        if(i < j) {
            temp = data[i];
            data[i] = data[j];
            data[j] = temp;
        }
        k = n >> 1;
        while(k <= j) {
            j -= k;
            k >>= 1;
        }
        j += k;
    }
    
    // 蝶形运算
    for(m = 2; m <= n; m <<= 1) {
        angle = -2 * PI / m;
        wn.real = cos(angle);
        wn.imag = sin(angle);
        
        for(k = 0; k < n; k += m) {
            w.real = 1;
            w.imag = 0;
            
            for(j = 0; j < m/2; j++) {
                Complex_t t, u;
                u.real = w.real * data[k+j+m/2].real - w.imag * data[k+j+m/2].imag;
                u.imag = w.real * data[k+j+m/2].imag + w.imag * data[k+j+m/2].real;
                
                t.real = data[k+j].real;
                t.imag = data[k+j].imag;
                
                data[k+j].real = t.real + u.real;
                data[k+j].imag = t.imag + u.imag;
                data[k+j+m/2].real = t.real - u.real;
                data[k+j+m/2].imag = t.imag - u.imag;
                
                // 更新旋转因子
                temp.real = w.real * wn.real - w.imag * wn.imag;
                temp.imag = w.real * wn.imag + w.imag * wn.real;
                w = temp;
            }
        }
    }
}

10. 头文件

// stm32f10x_reg.h
#ifndef __STM32F10X_REG_H
#define __STM32F10X_REG_H

// 寄存器基地址
#define PERIPH_BASE      ((uint32_t)0x40000000)
#define APB1PERIPH_BASE  (PERIPH_BASE + 0x00000)
#define APB2PERIPH_BASE  (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE   (PERIPH_BASE + 0x20000)

// GPIO寄存器结构
typedef struct {
    volatile uint32_t CRL;
    volatile uint32_t CRH;
    volatile uint32_t IDR;
    volatile uint32_t ODR;
    volatile uint32_t BSRR;
    volatile uint32_t BRR;
    volatile uint32_t LCKR;
} GPIO_TypeDef;

// RCC寄存器结构
typedef struct {
    volatile uint32_t CR;
    volatile uint32_t CFGR;
    volatile uint32_t CIR;
    volatile uint32_t APB2RSTR;
    volatile uint32_t APB1RSTR;
    volatile uint32_t AHBENR;
    volatile uint32_t APB2ENR;
    volatile uint32_t APB1ENR;
    volatile uint32_t BDCR;
    volatile uint32_t CSR;
} RCC_TypeDef;

// ADC寄存器结构
typedef struct {
    volatile uint32_t SR;
    volatile uint32_t CR1;
    volatile uint32_t CR2;
    volatile uint32_t SMPR1;
    volatile uint32_t SMPR2;
    volatile uint32_t JOFR1;
    volatile uint32_t JOFR2;
    volatile uint32_t JOFR3;
    volatile uint32_t JOFR4;
    volatile uint32_t HTR;
    volatile uint32_t LTR;
    volatile uint32_t SQR1;
    volatile uint32_t SQR2;
    volatile uint32_t SQR3;
    volatile uint32_t JSQR;
    volatile uint32_t JDR1;
    volatile uint32_t JDR2;
    volatile uint32_t JDR3;
    volatile uint32_t JDR4;
    volatile uint32_t DR;
} ADC_TypeDef;

// DMA通道寄存器结构
typedef struct {
    volatile uint32_t CCR;
    volatile uint32_t CNDTR;
    volatile uint32_t CPAR;
    volatile uint32_t CMAR;
} DMA_Channel_TypeDef;

// 外设基地址
#define GPIOA_BASE    (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE    (APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE    (APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE    (APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE    (APB2PERIPH_BASE + 0x1800)

#define RCC_BASE      (AHBPERIPH_BASE + 0x1000)
#define ADC1_BASE     (APB2PERIPH_BASE + 0x2400)
#define DMA1_BASE     (AHBPERIPH_BASE + 0x0000)

// 外设声明
#define GPIOA         ((GPIO_TypeDef *)GPIOA_BASE)
#define GPIOB         ((GPIO_TypeDef *)GPIOB_BASE)
#define GPIOC         ((GPIO_TypeDef *)GPIOC_BASE)
#define GPIOD         ((GPIO_TypeDef *)GPIOD_BASE)
#define GPIOE         ((GPIO_TypeDef *)GPIOE_BASE)

#define RCC           ((RCC_TypeDef *)RCC_BASE)
#define ADC1          ((ADC_TypeDef *)ADC1_BASE)
#define DMA1_Channel1 ((DMA_Channel_TypeDef *)(DMA1_BASE + 0x08))

#endif /* __STM32F10X_REG_H */

此代码提供了完整的STM32F103ZET6寄存器级开发框架,实现了数字示波器的核心功能。注意:LCD驱动部分需要根据具体使用的TFT-LCD控制器进行相应调整。

项目核心代码

/* 基于STM32的便携式数字示波器 - main.c */
#include "stm32f10x.h"
#include "lcd.h"
#include "adc_dma.h"
#include "signal_processing.h"
#include "ui.h"
#include "encoder.h"
#include "buttons.h"
#include "fft.h"

/* 全局变量定义 */
volatile uint8_t trigger_flag = 0;
volatile uint8_t data_ready = 0;
uint16_t adc_buffer[BUFFER_SIZE];
float voltage_buffer[BUFFER_SIZE];
WaveformInfo waveform = {
    .trigger_mode = AUTO,
    .trigger_edge = RISING,
    .trigger_level = 1.65f,
    .timebase = 10.0f,    /* us/div */
    .voltage_scale = 1.0f, /* V/div */
    .offset_x = 0,
    .offset_y = 0
};

/* 函数声明 */
static void System_Init(void);
static void Display_Waveform(void);
static void Process_Measurements(void);
static void Handle_User_Input(void);

int main(void)
{
    /* 1. 系统初始化 */
    System_Init();
    
    /* 2. 显示开机界面 */
    LCD_Clear(BLACK);
    LCD_ShowString(100, 100, "Digital Oscilloscope", WHITE, BLACK);
    LCD_ShowString(120, 130, "Initializing...", WHITE, BLACK);
    Delay_ms(500);
    
    /* 3. 启动ADC连续采集 */
    ADC_DMA_Start((uint32_t)adc_buffer, BUFFER_SIZE);
    
    /* 4. 主循环 */
    while(1)
    {
        /* 4.1 检查触发状态 */
        if(trigger_flag)
        {
            trigger_flag = 0;
            data_ready = 1;
        }
        
        /* 4.2 数据处理与显示 */
        if(data_ready)
        {
            /* 转换ADC数据为电压值 */
            ADC_To_Voltage(adc_buffer, voltage_buffer, BUFFER_SIZE);
            
            /* 触发处理 */
            Signal_Trigger_Process(voltage_buffer, BUFFER_SIZE, &waveform);
            
            /* 波形显示 */
            Display_Waveform();
            
            /* 测量计算 */
            Process_Measurements();
            
            /* 显示UI界面 */
            UI_Update(&waveform);
            
            data_ready = 0;
        }
        
        /* 4.3 用户输入处理 */
        Handle_User_Input();
        
        /* 4.4 FFT模式处理 */
        if(waveform.display_mode == DISPLAY_FFT)
        {
            FFT_Process(voltage_buffer, BUFFER_SIZE);
            Display_FFT_Spectrum();
        }
    }
}

/* 系统初始化函数 */
static void System_Init(void)
{
    /* 1. 系统时钟配置 - 72MHz */
    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPAEN | 
                    RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | 
                    RCC_APB2ENR_ADC1EN | RCC_APB2ENR_DMA1EN;
    
    /* 2. 硬件模块初始化 */
    LCD_Init();                     /* TFT-LCD初始化 */
    Touch_Init();                   /* 触摸屏初始化 */
    Encoder_Init();                 /* 旋转编码器初始化 */
    Buttons_Init();                 /* 按键初始化 */
    ADC_DMA_Init();                 /* ADC和DMA初始化 */
    
    /* 3. NVIC配置 */
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    /* 4. 变量初始化 */
    waveform.display_mode = DISPLAY_WAVEFORM;
    waveform.measure_mode = MEASURE_AUTO;
}

/* 波形显示函数 */
static void Display_Waveform(void)
{
    uint16_t i, x, y;
    float scaled_voltage;
    
    /* 清除波形区域 */
    LCD_Fill(WAVE_AREA_X, WAVE_AREA_Y, 
             WAVE_AREA_X + WAVE_AREA_WIDTH, 
             WAVE_AREA_Y + WAVE_AREA_HEIGHT, BLACK);
    
    /* 绘制网格 */
    Draw_Grid();
    
    /* 绘制触发电平线 */
    LCD_DrawLine(WAVE_AREA_X, 
                 VOLT_TO_PIXEL(waveform.trigger_level),
                 WAVE_AREA_X + WAVE_AREA_WIDTH,
                 VOLT_TO_PIXEL(waveform.trigger_level),
                 GRAY);
    
    /* 绘制波形 */
    for(i = 0; i < BUFFER_SIZE; i++)
    {
        /* 电压缩放和偏移 */
        scaled_voltage = (voltage_buffer[i] * waveform.voltage_scale) + waveform.offset_y;
        
        /* 坐标转换 */
        x = TIME_TO_PIXEL(i * waveform.timebase * 10) + waveform.offset_x; /* 10 samples per time unit */
        y = VOLT_TO_PIXEL(scaled_voltage);
        
        /* 限制在显示区域内 */
        if(y < WAVE_AREA_Y) y = WAVE_AREA_Y;
        if(y > WAVE_AREA_Y + WAVE_AREA_HEIGHT) y = WAVE_AREA_Y + WAVE_AREA_HEIGHT;
        
        /* 绘制点 */
        if(i == 0)
            LCD_DrawPoint(x, y, CYAN);
        else
            LCD_DrawLine(prev_x, prev_y, x, y, CYAN);
        
        prev_x = x;
        prev_y = y;
    }
}

/* 测量处理函数 */
static void Process_Measurements(void)
{
    MeasurementResults results;
    
    if(waveform.measure_mode == MEASURE_AUTO)
    {
        /* 自动测量 */
        results.vpp = Calculate_Vpp(voltage_buffer, BUFFER_SIZE);
        results.vrms = Calculate_Vrms(voltage_buffer, BUFFER_SIZE);
        results.frequency = Calculate_Frequency(voltage_buffer, BUFFER_SIZE, waveform.timebase);
        results.period = 1.0f / results.frequency;
        
        /* 显示测量结果 */
        Display_Measurements(&results);
    }
    else if(waveform.measure_mode == MEASURE_MANUAL)
    {
        /* 手动测量标记点处理 */
        Handle_Manual_Measurement();
    }
}

/* 用户输入处理函数 */
static void Handle_User_Input(void)
{
    static uint32_t last_input_time = 0;
    
    /* 检查编码器旋转 */
    if(Encoder_Get_Direction() != ENC_NONE)
    {
        int8_t dir = Encoder_Get_Direction();
        
        switch(waveform.current_menu)
        {
            case MENU_TIMEBASE:
                waveform.timebase *= (dir > 0) ? 1.1f : 0.9f;
                Clamp_Float(&waveform.timebase, 0.1f, 1000.0f);
                break;
                
            case MENU_VOLT_SCALE:
                waveform.voltage_scale *= (dir > 0) ? 1.2f : 0.833f;
                Clamp_Float(&waveform.voltage_scale, 0.1f, 5.0f);
                break;
                
            case MENU_TRIG_LEVEL:
                waveform.trigger_level += (dir > 0) ? 0.1f : -0.1f;
                Clamp_Float(&waveform.trigger_level, 0.0f, 3.3f);
                break;
        }
        
        Encoder_Reset();
        last_input_time = Get_Tick_Count();
    }
    
    /* 检查按键 */
    uint8_t key = Buttons_Scan();
    if(key != KEY_NONE)
    {
        switch(key)
        {
            case KEY_MODE:
                waveform.display_mode = (waveform.display_mode + 1) % 3;
                break;
                
            case KEY_TRIGGER:
                waveform.trigger_mode = (waveform.trigger_mode + 1) % 3;
                break;
                
            case KEY_MEASURE:
                waveform.measure_mode = (waveform.measure_mode + 1) % 2;
                break;
                
            case KEY_RUN_STOP:
                if(ADC_DMA_IsRunning())
                    ADC_DMA_Stop();
                else
                    ADC_DMA_Start((uint32_t)adc_buffer, BUFFER_SIZE);
                break;
                
            case KEY_SINGLE:
                waveform.trigger_mode = SINGLE;
                ADC_DMA_Single_Shot((uint32_t)adc_buffer, BUFFER_SIZE);
                break;
        }
        
        last_input_time = Get_Tick_Count();
    }
    
    /* 检查触摸屏 */
    if(Touch_Scan())
    {
        TouchPoint tp = Touch_GetPoint();
        UI_Touch_Handler(tp.x, tp.y, &waveform);
        last_input_time = Get_Tick_Count();
    }
    
    /* 自动隐藏菜单 */
    if((Get_Tick_Count() - last_input_time) > MENU_TIMEOUT)
    {
        UI_Hide_Menu();
    }
}

/* DMA中断服务函数 - ADC采集完成 */
void DMA1_Channel1_IRQHandler(void)
{
    if(DMA1->ISR & DMA_ISR_TCIF1)
    {
        DMA1->IFCR |= DMA_IFCR_CTCIF1;
        
        /* 根据触发模式设置标志 */
        switch(waveform.trigger_mode)
        {
            case AUTO:
                trigger_flag = 1;
                break;
                
            case NORMAL:
                if(Check_Trigger_Condition(voltage_buffer, BUFFER_SIZE, 
                                          waveform.trigger_level, waveform.trigger_edge))
                    trigger_flag = 1;
                break;
                
            case SINGLE:
                if(Check_Trigger_Condition(voltage_buffer, BUFFER_SIZE,
                                          waveform.trigger_level, waveform.trigger_edge))
                {
                    trigger_flag = 1;
                    ADC_DMA_Stop();
                }
                break;
        }
    }
}

/* 辅助函数:浮点数范围限制 */
static void Clamp_Float(float* value, float min, float max)
{
    if(*value < min) *value = min;
    if(*value > max) *value = max;
}

总结

本设计成功实现了一款基于STM32的便携式数字示波器,具备全面的信号采集与分析功能。该系统能够实时采集0-200kHz带宽内的输入信号,并通过波形显示直观呈现,同时提供了自动、常规和单次三种触发模式,支持用户灵活设置触发电平与边沿,确保了波形捕获的精确性与稳定性。此外,示波器集成了电压幅值(如Vpp、Vrms)和信号频率/周期的手动与自动测量功能,并支持波形显示的缩放与平移操作,方便用户进行细节观察。FFT频域分析功能的加入,进一步扩展了其应用范围,允许将时域波形转换为频谱显示,以进行频率成分分析。

在硬件实现上,系统以STM32F103ZET6单片机为核心,驱动3.5寸TFT-LCD电阻触摸屏作为主控与显示模块,提供了友好的用户交互界面。信号调理模块采用OPA2350运放构成的衰减/放大电路,有效将输入信号调理至0-3.3V的ADC量程内,保证了信号适配的可靠性。模数转换模块直接利用STM32内置的12位ADC,配合DMA技术实现了最高1Msps的采样率,满足了高频信号采集的需求。用户输入模块结合旋转编码器与独立按键,实现了参数调节与功能切换的便捷操作。电源模块则基于MP1584EN DC-DC降压芯片,将外部5V输入高效转换为系统所需的3.3V,确保了整体供电的稳定与便携性。

综上所述,该便携式数字示波器设计在有限的硬件资源下,通过优化软硬件架构,实现了高性能的信号处理与显示功能。其紧凑的结构和丰富的特性,使其适用于教育、研发和现场调试等多种场景,体现了嵌入式系统在仪器仪表领域的实用价值与创新潜力。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。