当前位置: 首页 > news >正文

模拟频谱分析仪(Linux c++ Qt)

        此Demo由三个小项目组成,分布是模拟的硬件采集频谱数据端,后台处理端以及qt前端,于Linux系统下进行开发,使用的软件为clion和QtCreator,编程语言为c++,使用了linux下的boost库(1.72),多线程和TCP,UDP以及c++的一些新特性,为本人自己想的练手的小项目.

1.项目架构

        整体设计采集端不断产生数据,通过udp广播给后台处理端,后台处理端将数据通过tcp传给qt前端,而控制指令由qt前端通过udp传给采集端(理论上应该是qt前端到后台处理端再传给采集端(作者偷懒了,直接前端连接采集端)[后续也可以封装自己独特的通讯协议]


2.运行结果

模拟硬件采集频谱数据端运行结果:

 后台处理端运行结果:

  qt前端运行结果:

 

3.实现过程

        a.模拟硬件采集端实现

        项目结构如图:

 

        整个项目使用Cmake进行管理,结果清晰.

        config.h为配置文件,定义了udp端口地址以及频谱相关的参数, 方便更改代码如下,其中的关键字constexpr可以在编译时进行求值,能提高程序运行效率

//配置文件#ifndef FPGADEMO_CONFIG_H
#define FPGADEMO_CONFIG_H#include <cstdlib> //c++头文件  主要用于定义一些常量 关键字constexpr 可以在编译时进行求职 提高程序运行效率//UDP广播配置
constexpr u_int16_t Udp_BroadCasT_Port = 8888;   //udp端口号  广播端口用于在网络中向所有设备发送消息。
constexpr const char * Udp_BroadCast_IP = "255.255.255.255"; //定义了 UDP 广播的 IP 地址,值为 "255.255.255.255",这是一个特殊的 IP 地址,表示向本地网络中的所有设备发送广播消息//控制端口配置
constexpr u_int16_t Udp_Config_Port = 8889; //控制端口用于接收和处理控制命令//数据配置/*Sample_Rate:定义了采样率,值为 44100 Hz。采样率表示每秒对信号进行采样的次数,44100 Hz 是音频处理中常用的采样率。FFT_Size:定义了快速傅里叶变换(FFT)的大小,值为 1024。FFT 是一种用于将时域信号转换为频域信号的算法,FFT 大小决定了频域分辨率。Data_Interval_Ms:定义了数据发送的时间间隔,单位为毫秒,值为 20 毫秒。这个参数用于控制数据发送的频率。Spectrum_Size:定义了频谱数据的大小,值为 FFT_Size / 2。在 FFT 变换中,频谱数据的有效部分通常是 FFT 结果的一半。* */
constexpr int Sample_Rate = 44100; //采样率
constexpr int FFT_Size = 1024;  //FFT大小
constexpr int Data_Interval_Ms = 20; //数据发送间隔
constexpr int Spectrum_Size = FFT_Size / 2; //频谱数据大小// 套接字重用配置
constexpr bool REUSE_ADDRESS = true;      // 允许地址重用 在网络编程中,地址重用允许在同一端口上同时绑定多个套接字,避免因端口被占用而导致的错误#endif //FPGADEMO_CONFIG_H

        logger.h是封装的一个打印日志的实例(另外一个博客中封装介绍了更好的版本)


#ifndef FPGADEMO_LOGGER_H
#define FPGADEMO_LOGGER_H#include <iostream>
#include <string>
#include <iomanip>  //输入输出流操作符库 用于格式化输出
#include <chrono>   //时间处理库英语获取当前时间 添加时间的时间戳/*** @brief 日志记录类,提供不同级别的日志输出功能** 使用单例模式确保全局唯一日志实例* 支持不同级别的日志输出:INFO, WARNING, ERROR* 自动添加时间戳和日志级别前缀*/
class Logger {
public:// 日志级别枚举enum class Level{INFO,WARNING,ERROR};// 获取单例实例static Logger& getInstance(){static Logger instance;return instance;}// 禁用拷贝和赋值,确保单例的正确性Logger(const Logger&) = delete;Logger& operator=(const Logger&) = delete;// 日志输出方法void log(Level level, const std::string& message){auto now = std::chrono::system_clock::now();auto now_time = std::chrono::system_clock::to_time_t(now);std::cout << "[" << std::put_time(std::localtime(&now_time), "%Y-%m-%d %H:%M:%S") << "] ";switch(level) {case Level::INFO: std::cout << "[INFO] "; break;case Level::WARNING: std::cout << "[WARNING] "; break;case Level::ERROR: std::cout << "[ERROR] "; break;}std::cout << message << std::endl;}private:Logger() = default; // 私有构造函数确保单例
};// 日志宏定义,方便使用
#define LOG_INFO(msg) Logger::getInstance().log(Logger::Level::INFO, msg)
#define LOG_WARNING(msg) Logger::getInstance().log(Logger::Level::WARNING, msg)
#define LOG_ERROR(msg) Logger::getInstance().log(Logger::Level::ERROR, msg)#endif //FPGADEMO_LOGGER_H

         data_generator类用于生成频谱数据

#ifndef FPGADEMO_DATA_GENERATOR_H
#define FPGADEMO_DATA_GENERATOR_H#include <vector>
#include <cstdint>
#include <random>
#include <mutex>
#include <boost/noncopyable.hpp>/*** @brief 模拟数据生成器,生成频谱数据*/class data_generator : private boost::noncopyable {public:data_generator();~data_generator() = default;/*** @brief 生成模拟频谱数据* @return 包含频谱数据的vector,大小为SPECTRUM_SIZE*/std::vector<float> generateSpectrumData();/*** @brief 设置基频* @param freq 基频频率(Hz)*/void setBaseFrequency(float freq);private:float baseFrequency;                 // 当前基频std::mt19937 rng;                    // 随机数生成器std::uniform_real_distribution<float> noiseDist; // 噪声分布std::mutex mutex_;                   // 保护基频的互斥锁
};#endif //FPGADEMO_DATA_GENERATOR_H

        udp_Brodcaster用于广播频谱数据以及接受前端发送的指令,接收和发送互不影响

//
// Created by Administrator on 2025/4/25.
//#ifndef FPGADEMO_UDPBROADCASTER_H
#define FPGADEMO_UDPBROADCASTER_H#include <vector>   //容器
// 包含 Boost.Asio 库的头文件,Boost.Asio 是一个跨平台的网络和底层 I/O 编程库,用于实现异步网络操作
#include <boost/asio.hpp>
// 包含 Boost.Thread 库的头文件,用于实现多线程编程
#include <boost/thread.hpp>
// 包含 Boost.Atomic 库的头文件,提供原子操作,可用于多线程环境下的同步
#include <boost/atomic.hpp>
//包含 Boost.Signals2 库的头文件,用于实现信号与槽机制,允许对象之间进行松耦合的通信
#include <boost/signals2.hpp>
#include "config.h"
/*** @brief UDP广播器类,负责发送频谱数据和接收控制命令** 使用Boost.Asio进行异步网络操作* 使用独立线程进行数据广播* 支持地址重用选项解决"Address already in use"问题*/
class UdpBroadcaster {
public:UdpBroadcaster();~UdpBroadcaster();/*** @brief 开始广播*/void startBroadcasting();/*** @brief 停止广播*/void stopBroadcasting();/*** @brief 设置要广播的数据* @param data 频谱数据*/void setBroadcastData(const std::vector<float>& data);/*** @brief 信号:当收到控制命令时触发*/// 定义一个信号,当收到控制命令时,会调用所有连接到这个信号的槽函数boost::signals2::signal<void(float)> onControlCommandReceived;private:void broadcastThread();               // 广播线程函数,用于在独立线程中进行数据广播void startReceiveControl();          // 开始接收控制命令,启动异步接收操作//处理接收到的控制指令的回调函数,当接收到控制指令命令时会被调用void handleControlCommand(const boost::system::error_code& error,std::size_t bytes_transferred);// Boost.Asio 的 I/O 服务对象,负责管理异步操作的事件循环boost::asio::io_service ioService_;// UDP 广播套接字,用于发送频谱数据boost::asio::ip::udp::socket broadcastSocket_;// UDP 控制套接字,用于接收控制命令boost::asio::ip::udp::socket controlSocket_;// 广播端点,指定广播数据的目标地址和端口boost::asio::ip::udp::endpoint broadcastEndpoint_;// 控制端点,指定接收控制命令的本地地址和端口boost::asio::ip::udp::endpoint controlEndpoint_;// 远程端点,记录发送控制命令的客户端地址和端口boost::asio::ip::udp::endpoint remoteEndpoint_;// 广播线程对象,用于执行广播线程函数boost::thread broadcastThread_;// I/O 服务线程对象,用于运行 I/O 服务的事件循环boost::thread ioServiceThread_;// 原子布尔变量,用于表示广播是否正在运行,可在多线程环境下安全访问boost::atomic<bool> running_;// 当前要广播的频谱数据,使用 vector 存储浮点数std::vector<float> currentData_;// 互斥锁,用于保护 currentData_ 的并发访问,确保线程安全boost::mutex dataMutex_;// 控制命令缓冲区,用于存储接收到的控制命令,大小为一个浮点数的字节数std::array<char, sizeof(float)> controlBuffer_;
};#endif //FPGADEMO_UDPBROADCASTER_H

         fpga_simulator拥有协调数据生成和广播,处理控制指令以及提供运行状态

#ifndef FPGADEMO_FPGASIMULATOR_H
#define FPGADEMO_FPGASIMULATOR_H
#include "data_generator.h"
#include "UdpBroadcaster.h"
#include "Logger.h"
/*** @brief FPGA模拟器主类** 负责协调数据生成和广播* 处理控制命令* 提供运行状态管理*/class FpgaSimulator {
public:FpgaSimulator();~FpgaSimulator();/*** @brief 运行模拟器*/void run();void stop();
private:data_generator dataGen_;              // 数据生成器UdpBroadcaster broadcaster_;         // UDP广播器boost::atomic<bool> running_;
};#endif //FPGADEMO_FPGASIMULATOR_H
        b.后台处理端实现

          项目结构如图:

        此部分也是Cmake进行管理,其中config,logger以及udp相关与上面的模拟端类似,不再说明

        tcpserver用于作为tcp的服务端,负责处理客户端连接和数据广播,线程池处理多个客户端以及提供连接断开事件通知

//
// Created by Administrator on 2025/4/30.
//#ifndef BACKEND_SERVER_TCPSERVER_H
#define BACKEND_SERVER_TCPSERVER_H#include <vector>
#include <set>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/signals2.hpp>
#include "config.h"
#include "Logger.h"
#include <boost/asio/post.hpp>  // Required for boost::asio::post
/*** @brief TCP服务器类** 处理客户端连接和数据广播* 使用线程池处理多个客户端* 提供连接/断开事件通知*/
class TcpServer {
public:using ClientConnectedCallback = std::function<void(boost::asio::ip::tcp::socket&)>;using ClientDisconnectedCallback = std::function<void(boost::asio::ip::tcp::socket&)>;TcpServer();~TcpServer();void start();void stop();void broadcastData(const std::vector<float>& data);void setClientConnectedCallback(ClientConnectedCallback callback);void setClientDisconnectedCallback(ClientDisconnectedCallback callback);private:void acceptThread();void startAccept();void handleAccept(boost::asio::ip::tcp::socket* socket,const boost::system::error_code& error);void clientHandler(boost::asio::ip::tcp::socket* socket);boost::asio::io_service ioService_;boost::asio::ip::tcp::acceptor acceptor_;boost::thread acceptThread_;boost::asio::thread_pool ioThreadPool_; //线程池boost::atomic<bool> running_;std::set<boost::asio::ip::tcp::socket*> clients_;boost::mutex clientsMutex_;ClientConnectedCallback clientConnectedCallback_;ClientDisconnectedCallback clientDisconnectedCallback_;const size_t threadPoolSize_ = 4;  // 显式存储线程池大小
};#endif //BACKEND_SERVER_TCPSERVER_H

         backendServer部分用于整和UDP接收和TCP服务功能,管理数据转发流程,

#ifndef BACKEND_SERVER_H
#define BACKEND_SERVER_H#include "UdpReceiver.h"
#include "TcpServer.h"
#include "Logger.h"
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <csignal>/*** @brief 后端服务器主类* * 整合UDP接收和TCP服务功能* 管理数据转发流程* 提供完整的生命周期管理*/
class BackendServer {
public:BackendServer();~BackendServer();void run();void stop();private:void onUdpDataReceived(const std::vector<float>& data);void onTcpClientConnected(boost::asio::ip::tcp::socket& socket);void onTcpClientDisconnected(boost::asio::ip::tcp::socket& socket);UdpReceiver udpReceiver_;TcpServer tcpServer_;std::vector<float> lastReceivedData_;boost::mutex dataMutex_;boost::atomic<bool> running_;
};#endif // BACKEND_SERVER_H
        c.qt前端实现

        项目结构如图:

        整个项目有qt的.pro文件进行管理(可以封装成qmake管理的形式)

        logger类负责日志打印不进行介绍

        spectrumwidget封装了项目需要的频谱控件

#ifndef SPECTRUMWIDGET_H
#define SPECTRUMWIDGET_H#include <QWidget>
#include <QVector>class SpectrumWidget : public QWidget
{Q_OBJECTpublic:explicit SpectrumWidget(QWidget *parent = nullptr);void setData(const QVector<float>& data);void setColorGradient(const QLinearGradient& gradient);void setBackgroundColor(const QColor& color);void setBarWidthRatio(float ratio);protected:void paintEvent(QPaintEvent *event) override;private:QVector<float> spectrumData;QLinearGradient gradient;QColor backgroundColor;float barWidthRatio;
};#endif // SPECTRUMWIDGET_H

         tcp,udp类不再讲解,mainwindow主要绘制了界面,整和这些功能

4.知识点总结

        1. UDP协议

        理论

                无连接协议,发送数据前不需要建立连接

                不保证数据顺序和可靠性,但传输效率高

                适合实时性要求高、可容忍少量丢失的场景(如视频流、实时游戏)

        基础实现

// 创建UDP socket
boost::asio::ip::udp::socket socket(ioService);
socket.open(boost::asio::ip::udp::v4());// 设置地址重用
socket.set_option(boost::asio::socket_base::reuse_address(true));// 绑定端口
socket.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port));// 异步接收
socket.async_receive_from(buffer(data), senderEndpoint,[](const boost::system::error_code& error, size_t bytes) {// 处理接收数据});
         2. TCP协议

        理论

                面向连接的可靠传输协议

                保证数据顺序和完整性

                适合需要可靠传输的场景(如文件传输、远程控制)

        基础实现

// 创建TCP acceptor
boost::asio::ip::tcp::acceptor acceptor(ioService,boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port));// 异步接受连接
boost::asio::ip::tcp::socket socket(ioService);
acceptor.async_accept(socket, [](const boost::system::error_code& error) {// 处理新连接});// 异步读写
boost::asio::async_read(socket, buffer(data),[](const boost::system::error_code& error, size_t bytes) {// 处理读取数据});
        3. 多线程编程

        理论

                并发执行多个任务

                提高CPU利用率

                需要处理线程同步问题

        基础实现

#include <thread>
#include <vector>void worker(int id) {std::cout << "Worker " << id << " running\n";
}int main() {std::vector<std::thread> threads;// 创建线程for(int i = 0; i < 5; ++i) {threads.emplace_back(worker, i);}// 等待所有线程完成for(auto& t : threads) {t.join();}
}
        4. 原子量(atomic)

        理论

        不可分割的操作

        无需锁的线程安全访问

        适合简单变量的并发访问

        基础实现

#include <atomic>std::atomic<int> counter(0);void increment() {for(int i = 0; i < 1000; ++i) {counter.fetch_add(1, std::memory_order_relaxed);}
}// 多线程安全访问
std::thread t1(increment);
std::thread t2(increment);
         5. C++信号槽绑定

         理论

                观察者模式的实现

                松耦合的事件通知机制

                信号发出时自动调用连接的槽函数

        基础实现

#include <boost/signals2.hpp>// 定义信号
boost::signals2::signal<void(int)> valueChanged;// 连接槽函数
valueChanged.connect([](int newValue) {std::cout << "Value changed to " << newValue << "\n";
});// 触发信号
valueChanged(42);
        6. 回调函数

        理论

                函数作为参数传递

                异步操作完成后调用

                实现控制反转

        基础实现

// 定义回调类型
using Callback = std::function<void(const std::string&)>;void fetchData(Callback callback) {// 模拟异步操作std::thread([callback]() {std::this_thread::sleep_for(std::chrono::seconds(1));callback("Data loaded");}).detach();
}// 使用回调
fetchData([](const std::string& result) {std::cout << result << "\n";
});
        7. auto关键字

        理论

                自动类型推导

                编译时确定类型

                简化复杂类型声明

        基础实现

auto i = 42; // int
auto d = 3.14; // double
auto v = std::vector<int>{1, 2, 3}; // std::vector<int>// 结合lambda
auto func = [](int x) { return x * 2; };
        8. Lambda表达式

        理论

                匿名函数对象

                可以捕获上下文变量

                简化回调和小函数定义

        基础实现

// 基本形式
auto sum = [](int a, int b) { return a + b; };// 捕获局部变量
int factor = 3;
auto multiply = [factor](int x) { return x * factor; };// 在算法中使用
std::vector<int> nums{1, 2, 3};
std::sort(nums.begin(), nums.end(), [](int a, int b) { return a > b; });
9. vector容器

        理论

                动态数组

                连续内存存储

                自动管理内存

        基础实现

#include <vector>// 创建和初始化
std::vector<int> numbers = {1, 2, 3};// 添加元素
numbers.push_back(4);// 遍历
for(auto n : numbers) {std::cout << n << " ";
}// 预分配空间
numbers.reserve(100);
        10. 线程池

        理论

                预先创建一组线程

                避免频繁创建销毁线程开销

                任务队列管理待执行任务

        基础实现

#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>class ThreadPool {
public:ThreadPool(size_t threads) {for(size_t i = 0; i < threads; ++i) {workers.emplace_back([this] {while(true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(queueMutex);condition.wait(lock, [this] { return stop || !tasks.empty(); });if(stop && tasks.empty()) return;task = std::move(tasks.front());tasks.pop();}task();}});}}template<class F>void enqueue(F&& f) {{std::unique_lock<std::mutex> lock(queueMutex);tasks.emplace(std::forward<F>(f));}condition.notify_one();}~ThreadPool() {{std::unique_lock<std::mutex> lock(queueMutex);stop = true;}condition.notify_all();for(std::thread &worker : workers)worker.join();}private:std::vector<std::thread> workers;std::queue<std::function<void()>> tasks;std::mutex queueMutex;std::condition_variable condition;bool stop = false;
};

 

     

相关文章:

  • Qt:(创建项目)
  • PageOffice在线打开word文件,并实现切换文件
  • 【RustDesk 】中继1:压力测试 Python 版 RustDesk 中继服务器
  • 阿里云 ECS 服务器进阶指南:存储扩展、成本优化与架构设计
  • WPF之RadioButton控件详解
  • AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建
  • 【Python学习路线】零基础到项目实战系统
  • 论文阅读:2024 ICML In-Context Unlearning: Language Models as Few-Shot Unlearners
  • 模型部署技巧(一)
  • WHAT - 《成为技术领导者》思考题(第四章)
  • Netflix系统架构解析
  • 基于Docker的Elasticsearch ARM64架构镜像构建实践
  • 解决GoLand无法Debug的问题
  • (Go Gin)Gin学习笔记(二):路由配置、基本路由、表单参数、上传单个文件、上传多个文件、浅扒路由原理
  • SMMU相关知识
  • 【蓝桥杯】第十六届蓝桥杯C/C++大学B组个人反思总结
  • 章越科技赋能消防训练体征监测与安全保障,从传统模式到智能跃迁的实践探索
  • (Go Gin)Gin学习笔记(四)Gin的数据渲染和中间件的使用:数据渲染、返回JSON、浅.JSON()源码、中间件、Next()方法
  • rk3568安全启动功能实践
  • BUUCTF——Fakebook 1
  • 5月起,这些新规将施行
  • 首开股份:一季度净利润亏损约10.79亿元,签约金额63.9亿元
  • 全文丨中华人民共和国民营经济促进法
  • 五一小长假,带着小狗去上海音乐厅
  • 李在明涉嫌违反《公职选举法》案将于5月1日宣判
  • 太好玩了!坐进大卫·霍克尼的敞篷车进入他画笔下的四季