前言:
本作品已经开源在github:
https://github.com/lgddyza/FPGA-Based_Audio_Processing_and_Classification
https://github.com/lgddyza/FPGA-Based_Audio_Processing_and_Classification有参考使用本工程的同学,希望点个Star哦!
基于FPGA的音频信号监测识别系统,是本学期我参加FPGA嵌入式设计大赛的作品,很遗憾AMD赛道的大家太卷了,导致遗憾离场。但是这个设计还能发挥余热,参与我接下来的电子系统设计之中去。本设计选择Vivado 2018.3平台,采用Verilog HDL语言开发,开发板设备为Zynq-7020,包含FPGA系统(含DAC模块)、麦克风输入信号放大电路以及网页版上位机,FPGA调用了PL端进行数字信号处理的实现(FFT输出以及功率谱的计算),PS端则进行输出结果的判断和处理。本次电子系统设计的成果得分为A+(100分,含设计分95分以及问答附加分5分)。
此外,在FPGA设计系统的基础上,我还另外设计了一套STC51单片机的FFT音频分析系统,经测试,能够实现基本功能,对于高频信号的处理还需要性能上的优化实现。在之后的文章中会专门讲。
系统实物图
600+Hz测试
波形测试部分视频
基于FPGA的音频分析系统
本学期初参加FPGA嵌入式创新设计大赛时的初代设计视频(之后在此基础上加强了很多)
简介
本项目基于Xilinx ZYNQ-7020开发板,实现了一个实时音频处理与分类系统,实现对人声、纯音乐的实时分类识别,同时可以对音频波形进行识别分类,目前可以识别正弦波、锯齿波、矩形波,并给出它们的基频频率,分辨率为11Hz。系统以48kHz的采样率捕获音频信号,在FPGA上通过4096点FFT及双阈值峰值检测算法进行实时频谱分析与特征提取,并依据频谱特征将音频状态划分为人声、音乐或静音,分析二倍频、三倍频来判断波形类型。处理结果通过PL端的EMIO传递到PS端的ARM处理器,进行逻辑判断后,经由UART接口在上位机实时显示。系统响应迅速,分类准确,充分体现了软硬件协同设计的优势。
一、设计目的和要求
目的:借助本项目,熟悉音频信号的采集与分类方式,能合理选取采样频率和音频传感器,了解音频信号的特征提取与分类算法;可运用DFT和FFT算法分析音频信号的频谱特性,并用代码实现算法功能;可精准辨别不同类别的音频信号,像无声信号、人类语音以及纯音乐,达成实时分类;可进一步通过频谱的结构,如高次谐波的分布情况,来实现对正弦波、锯齿波、矩形波的分类,以及基波的频率判断。结合实验检验系统在不同音量与距离条件下的性能,保证监测系统的稳定性与准确性。
设计要求 ****:****该音频信号监测识别系统需满足以下设计要求:发声装置(如手机)可调节音量大小且与系统的距离可变,系统能判断音频信号有无,自动识别无声音、人说话声、纯音乐这类信号类型并实时显示(非按键刷新显示,否则扣分),对于频率≤5kHz、步进100Hz的正弦波、方波、锯齿波等周期性音频信号,系统需在≤1s的识别时间内显示其频率(测量误差≤1%)和类型,同时系统要整机稳定、设计清楚且能熟练回答相关提问,应尽量采用更稳定的方案以提升评分。
二、设计原理
本次设计针对音频信号的采集、处理与分类需求,设计了两个效果相近的系统,一个由FPGA开发板(Zynq-7020)实现,另一个由51单片机(STC8H8K64U)实现。针对不同的系统性能,我选用了两套合适的FFT算法以及对应的分类逻辑,以在不同性能的设备上保证系统识别音频的实时性与准确性。
2.1 核心工作原理
快速傅里叶变换(FFT)作为本系统信号处理的核心算法,用于将时域音频信号转换为频域信号,从而实现对人语音、纯音等不同信号的精准分类。通过采样音频信号并进行加窗处理,减少频谱泄漏,提升频率分辨率;利用FPGA或单片机执行FFT运算,提取信号主频成分及高次谐波特征,结合阈值判断与模式识别逻辑完成分类决策。该方法在保证实时性的同时,显著提高了系统对复杂音频信号的分辨能力。采样频率的合理设置确保信号不失真,结合麦克风灵敏度匹配前端放大电路,使输入信号幅值适配处理器的AD采集范围。设计中采用海明窗降低频谱泄露影响,提升特征提取精度。程序流程包括初始化、信号采集、加窗、FFT运算、频谱分析、特征提取与分类判断。在FFT运算后,对输出结果进行归一化处理,利用幅值分布特性结合阈值比较实现信号类别的精确识别,通过多次实验验证算法稳定性,确保系统在不同环境下的适应性与可靠性。
针对FPGA系统,利用其并行计算优势,使用Xilinx官方的FFT IP核,采用Radix-4 Burst I/O FFT算法,AD采样速率为48kHz(满足高保真音频的采样率),FFT点数为4096点,单次FFT的Latency为288.94 μs,可高效完成实时频谱分析。结合Zynq的ARM Cortex-A9处理器进行后续分类处理,通过PS与PL协同实现数据缓存与逻辑控制。
本次设计的目标是,对于麦克风输入的音频信号、人声信号,可以通过FFT获得其频谱,对频谱进行进一步的处理分析,如分析谱心位置、设置频率阈值等等方法,对人声和纯音乐进行精准地分类,保证准确度的同时还要保证实时性(响应时间<1ms),为了保证响应时间,因此本次设计采用了FPGA的设计,依仗逻辑门电路的响应速度优势,进行数字信号处理的运算。
其次,还有重难点是考察对于傅里叶变换(频谱)的理解,目标是分辨出输入音频波形的类型,输入音频分为正弦波、锯齿波、矩形波三种,要求能分辨出准确的基准频率,还要分辨出波形类型。原理很简单,可以详见之前的文章:基于MATLAB的麦克风音频效果测试,通过对频谱的基频、高次谐波的分析,得到对应的高次谐波分量强度进而分析出波形类型。
根据数字信号处理理论,一个理想的方波是由基波和无穷多个奇次谐波组成的。其傅里叶级数展开式为:

这意味着,在理想无失真的情况下,各次谐波的幅度比例应严格遵循:

矩形波,应该含有一次波,三次波,五次波等奇次谐波,并且理论比例为:
1 : 1/3 : 1/5 : 1/7 ≈ 1 : 0.333 : 0.200 : 0.143

理想的1kHz方波输入的频谱
锯齿波的傅里叶级数展开式为:


由傅里叶级数的理论分析可知,在理想条件下,如果是正弦波,则不存在高次谐波分量 ,理想频谱应该是一道冲激;如果是矩形波,则存在奇次谐波分量, 理想频谱应该含有一次波,三次波,五次波等奇次谐波 ,且比例为1 : 1/3 : 1/5 : 1/7 ≈ 1 : 0.333 : 0.200 : 0.143;如果是锯齿波,则应该同时含有奇次谐波和偶次谐波,即整数次谐波,幅度比例按照傅里叶级数展开式。
所以最简单的方法就是观察二次谐波和三次谐波的存在,正弦波、锯齿波、矩形波三者只有锯齿波存在二次谐波 ,通过二次谐波即可判断;而矩形波存在三次谐波、不存在二次谐波,而正弦波不存在其他谐波分量,只有基波频率。故只需要通过基波、二次谐波、三次谐波即可判断三种波形并进行分类。
但是,理想状态下的频谱终究是理想情况下,事实上,正弦波、锯齿波、矩形波的音频信号在输出过程中就存在问题:手机扬声器的放音结构注定声波的频谱结构不会是理想的频谱,在实际测试中,我们发现频谱的谐波比例完全失调,甚至出现高次谐波分量大于基波分量的情况,但是好在这些波形还是存在对应的谐波分量,但是谐波分量比例失去了参考价值。
此外,还需要考虑到频谱泄露的问题,FFT存在截断情况,必然会导致频谱发生泄漏,即产生旁瓣,能量泄漏到周围的频率上,但实际上不存在这个频率值。针对这个情况,有几种方法,首先是加窗函数,如汉宁窗、海明窗等等,通过窗函数缓截断数据,避免频谱泄露;其次是对搜查的频谱分量进行去重、模糊搜索叠加处理,例如把索引处±3~5个索引的能量求和,并且找到能量最大的点作为中心频率索引点处理,避免旁瓣对结果的分析影响。
最后,考虑环境噪声(以及手机扬声器本征噪声)的影响,会混入不同频率成分的干扰,信号源质量差,要通过算法设计,比如进行频谱分量的归一化 ,噪声滤波阈值设置等方式,对噪声进行滤除,得到干净的频谱。
2.2 关键器件的选型
系统:基于FPGA的Zynq-7020平台
系统由直插式麦克风、麦克风增益放大电路、8位并行AD采集模块、FPGA组成。
音频采集部分:利用高灵敏度麦克风拾取环境声信号,经前置放大电路调理后送入AD芯片进行模数转换,采样精度为8位,采样频率由FPGA逻辑控制精确设定为48kHz,确保音频信号在传输过程中的完整性与实时性。采集后的数字信号通过AXI总线高速传入FFT IP核,在PL端完成频谱变换处理,结果缓存至片内存储器供PS端读取分析。
麦克风模块与前级功放板:
AD模块与FPGA开发板:


麦克风采集的模拟音频信号经增益放大后,送入AD采集模块进行采样与量化,采样数据通过AXI-Stream协议传输至FFT IP核,利用IP核实现的FFT算法对信号频谱进行快速分析,计算功率谱,提取基频与谐波特征,结合阈值判断实现实时分类。
2.3 设计思路
系统:基于FPGA+ARM架构,采用Xilinx Zynq-7020为核心。

Zynq-7020集成了双核Cortex-A9处理器与可编程逻辑资源,能够实现ARM端控制调度与FPGA端高速并行处理的协同工作模式。音频信号经前端调理电路后,由FPGA完成高实时性FFT运算,ARM端负责运行进行数据处理判断与人机交互,通过AXI总线实现数据高效交互,确保系统响应速度与稳定性兼顾。利用Vivado开发环境构建硬件逻辑,结合SDK完成软件编程,整体设计兼顾性能与灵活性,满足多场景音频信号分析需求。
信号处理路径:音频信号经麦克风采集后,通过前置放大电路送入FPGA进行模数转换,AD模块以48kHz的时钟对模拟音频信号进行采样,将采集到的数据送入FIFO缓存,再由FPGA调用FFT IP核,利用AXI-4总线读取FIFO内数据。在进入FFT之前,需要对数据进行一个预处理,即数字信号处理中的"加窗"处理,避免频谱泄露而导致频谱分量判断不准确,这里我们采用海明窗处理。
FFT的参数配置为4096点,以实现较高的频率分辨率,满足对音频信号精细分析的需求。完成FFT运算后,频域数据是带有实部和虚部的一帧(一帧长度为4096点)复数,需通过模值计算得到幅频特性。接下来我们设计了一个功率谱计算模块,由于模值计算需要先求平方和再开方,运算时间长且消耗硬件资源较多,因此采用功率谱计算方式,只求平方和以近似反映信号能量分布,降低计算复杂度,却能完成同样的效果。
得到功率谱之后,我们需要对功率谱进行分析,设计了一个find_peak模块,寻找功率能量大的峰值,标记最大值其对应的频率点作为信号主频(基频)分量。同时还需要考虑频谱泄露的情况,可以采用对大峰进行模糊搜索(对附近±2个频率索引的功率进行求和)。该模块通过滑动窗口比较相邻点幅值,结合阈值判断有效峰,避免噪声干扰导致误判。同时,在确定基频之后,对二倍频、三倍频分量进行检测并进行输出。
基频及其谐波分量的识别结果通过AXI总线传输至PS端,由ARM处理器进行最终判决与状态输出,结合预设阈值判断信号失真程度,并通过串口将基频、谐波含量及失真分析结果实时上传至网页上位机。同时显示主频频点、主频幅度、谐波分量强度以及判断结果,构成良好的人机交互。
2.4 程序设计
PS端系统的程序设计思路如下:

系统的程序设计基于模块化思想,划分为数据采集、信号处理、特征提取与判决输出四大功能块。在功率谱输出之后,设置了一个基础功率门限,当连续三帧的功率大于这个功率门限之后系统判定为有效信号输入,启动基频检测机制。若未达到门限,则维持待机状态以降低功耗。
音乐区分逻辑:当检测到有效信号后,程序把采集到的频率能量点放入一个长度为15的滑动窗口中进行统计分析,计算其频率的分布以区分音乐与语音。为此设计了多个判断条件,最后通过权重占比仲裁判断最终结果。当整体的频率分布较为均匀且高频成分占比超过设定阈值时,通过简单二分判断法可以快速地进行一个简单判断,但是这样的判断不够保险,因此引入了滞回判断阈值,当窗口索引满足滞回阈值的条件时,允许切换判断结果,并且引入了更精细的判别机制,结合频谱重心、频率方差及高阶矩特征,对信号频带分布形态进行建模。通过Matlab的FFT实施测试获取语音与音乐的典型参数区间,在线实时计算并比对,提升分类准确率。同时加入时间连续性约束,避免短时波动造成误判,确保输出稳定可靠。所以继续引入一个加权频率分布评估模型,结合频谱平坦度与熵值分析,进一步提高音乐与语音的区分准确率。在连续五帧判断结果一致时才触发最终分类输出,进一步抑制抖动干扰。
语音信号通常集中在200Hz~2kHz频段,能量分布相对集中,而音乐信号频谱更宽,高频分量丰富,利用该差异构建加权评分模型,结合频谱重心偏移量与方差变化率进行动态调整。系统在ARM端采用的就是如图所示的一个轻量化决策树算法,依托ARM-A9处理器的高性能条件,成功融合多特征参数实现精确分类,分类结果通过串口实时推送至Web界面,并以图表形式展示频谱趋势与判别依据,提升系统可解释性与实用性。
若窗口内频率点分布较为集中,集中在少数几个频点且变化缓慢,则判为音乐信号;若频率点分布广泛、跳变频繁,则判为语音信号。进一步结合频谱平坦度与能量波动性指标,提升分类准确率。
波形判断逻辑:find_peak模块在检测到主峰后,记录其频率索引并计算对应实际频率值,同时对邻近点进行能量加权以提高定位精度。谐波检测单元同步运行,分析二倍频与三倍频区域的能量分布,判断是否存在显著谐波分量,并计算基频能量和高次谐波能量之比。在基频确定的基础上,若二次谐波与三次谐波能量均显著高于噪声底(设置有噪声阈值),且满足E3 > 0.5×E2 > 0.1×E1,则判为方波;若仅有基频突出,高次谐波能量均低于设定阈值,则判为正弦波;其余情况结合频谱稀疏度与相位连续性判断是否为三角波或锯齿波。由于在实际测试中,二次谐波的幅值受硬件非线性失真影响较大,因此在判断中我们引入对二次谐波的补偿,对锯齿波的判断优先级会更高。
关键代码分析:
代码构成:

系统可以分为PL端、PS端两部分,其中PL端负责实现音频信号的采集与预处理,包括ADC采样控制、数据缓存及FFT算法的实现、功率谱的计算,具体模块包括PLL时钟管理模块、ADC采集模块、数据缓存FIFO模块、FFT运算模块、功率谱计算模块以及频谱特征提取模块;PS端基于ARM的C程序完成判断决策与人机交互功能,负责接收PL端传输的频谱特征数据,运行决策树分类逻辑进行声学事件判别,并通过串口将结果实时推送至Web端。
其中比较重要的部分包括:FFT、FIFO模块的参数配置、Peak_find模块的静音判断、AD的采集时钟设置、AD输出与FIFO写使能信号的同步控制、AXI-4总线的握手。

PLL锁相环的配置(提供时钟)

FIFO的配置:需兼顾写入、读出速度与存储深度
bash
module peak_find #(
parameter THRESH = 32'd1700, // 功率阈值(用于即时 out_valid)
parameter SOUND_THRESHOLD = 32'd2000, // 声音阈值:一帧最大功率超过此值认为有声音
parameter HOLD_FRAMES = 3 // 连续无声帧数,达到此帧数才判定为无声
)(
input wire clk,
input wire rst_n,
input wire switch_pulse, // 状态切换脉冲,上升沿有效
input wire valid_in, // 输入数据有效
input wire [11:0] index_in, // 当前点索引(递增)
input wire [31:0] power_in, // 当前功率值
output reg out_valid, // 阈值即时输出:有效
output reg [11:0] out_index, // 阈值即时输出:索引
output reg [31:0] out_power, // 阈值即时输出:功率
// 输出:最近两帧的最大功率索引(取两帧中的最大)
output reg max, // 有声音(带滞回机制)
output reg [11:0] index, // 最近两帧内最大功率的索引
output reg [7:0] third_harmonic, // 新增:三次谐波大小(8位,0-255)
output reg [7:0] second_harmonic, // 新增:二次谐波大小(8位,0-255)
output reg range_mode // 新增:当前范围模式指示 0:窄范围 1:宽范围
);
// ===== 当前帧最大值跟踪 =====
reg [31:0] cur_max_power;
reg [11:0] cur_max_index;
// 新增:基波搜索相关
reg [11:0] fundamental_idx_reg; // 基波索引寄存器
reg [31:0] fundamental_pwr_reg; // 基波功率寄存器
reg fundamental_found; // 基波找到标志
// ===== 最近两帧峰值缓存 =====
reg [31:0] frame_power [0:1]; // 保存最近2帧的最大功率
reg [11:0] frame_index [0:1]; // 保存最近2帧的峰值索引
reg frame_has_voice [0:1]; // 每帧是否有声音
reg frame_ptr; // 写指针:0/1
reg [11:0] prev_index;
// ===== 新增:即时声音检测 =====
reg instant_voice_detected; // 当前帧内是否检测到即时声音
// ===== 音频判断保持逻辑 =====
reg [HOLD_FRAMES-1:0] silence_cnt; // 连续无声帧计数器(长度为 HOLD_FRAMES)
reg [HOLD_FRAMES-1:0] silence_cnt_d; // 延迟版本,用于max逻辑
// 新增:用于max判断的信号
reg frame_has_voice_0_d, frame_has_voice_1_d; // 延迟的帧声音标志
reg instant_voice_detected_d; // 延迟的即时声音检测
reg frame_end_pulse; // 帧结束脉冲
reg frame_end_pulse_d1; // 帧结束脉冲延迟1拍
// 新增:状态切换逻辑
reg range_mode_reg; // 内部范围模式寄存器
wire in_range; // 范围判断信号
reg switch_pulse_d1, switch_pulse_d2; // 脉冲同步和边沿检测
// ===== 新增:谐波相关信号 =====
reg [11:0] last_fundamental_idx; // 上一帧的基波索引
reg [31:0] last_fundamental_pwr; // 上一帧的基波功率
reg last_fundamental_valid; // 上一帧基波信息有效
reg [11:0] target_second_harmonic_idx; // 二次谐波目标索引
reg [11:0] target_third_harmonic_idx; // 三次谐波目标索引
reg search_harmonics; // 谐波搜索使能
reg search_second; // 搜索二次谐波标志
reg search_third; // 搜索三次谐波标志
// 分别跟踪二次和三次谐波的最大功率
reg [31:0] cur_second_harmonic_power; // 当前帧二次谐波功率
reg [31:0] cur_third_harmonic_power; // 当前帧三次谐波功率
// 新增:输出保持相关信号
reg [7:0] second_harmonic_hold; // 二次谐波保持值
reg [7:0] third_harmonic_hold; // 三次谐波保持值
reg [31:0] out_power_hold; // 基波功率保持值
reg out_power_hold_valid; // 基波功率保持有效标志
reg second_harmonic_hold_valid; // 二次谐波保持有效标志
reg third_harmonic_hold_valid; // 三次谐波保持有效标志
// 新增:缩放相关参数
reg [31:0] second_harmonic_scaled; // 二次谐波缩放后值
reg [31:0] third_harmonic_scaled; // 三次谐波缩放后值
wire [31:0] second_ratio; // 二次谐波缩放比例
wire [31:0] third_ratio; // 三次谐波缩放比例
// 功率参考值,用于缩放
parameter POWER_REF = 32'd4000; // 参考功率值,超过此值就输出255
integer i;
// 根据range_mode_reg选择范围
assign in_range = (range_mode_reg == 1'b0) ? // 窄范围模式
((index_in < 12'd100) && (index_in > 12'd5)) : // 窄范围:5-99
((index_in < 12'd430) && (index_in > 12'd5)); // 宽范围:5-1200
// 功率缩放计算:将32位功率值缩放到0-255
// 公式:scaled_value = (power * 255) / POWER_REF
// 使用乘法实现
wire [31:0] second_power_scaled = (cur_second_harmonic_power * 32'd255) / POWER_REF;
wire [31:0] third_power_scaled = (cur_third_harmonic_power * 32'd255) / POWER_REF;
// 限幅到0-255
wire [7:0] second_clamped = (|second_power_scaled[31:8]) ? 8'd255 : second_power_scaled[7:0];
wire [7:0] third_clamped = (|third_power_scaled[31:8]) ? 8'd255 : third_power_scaled[7:0];
// switch脉冲边沿检测
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
switch_pulse_d1 <= 1'b0;
switch_pulse_d2 <= 1'b0;
end else begin
switch_pulse_d1 <= switch_pulse;
switch_pulse_d2 <= switch_pulse_d1;
end
end
wire switch_rising_edge = switch_pulse_d1 && !switch_pulse_d2;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
out_valid <= 1'b0;
out_index <= 12'd0;
out_power <= 32'd0;
index <= 12'd0;
third_harmonic <= 8'd0; // 新增
second_harmonic <= 8'd0; // 新增
range_mode <= 1'b0; // 默认为窄范围模式
range_mode_reg <= 1'b0; // 默认为窄范围模式
cur_max_power <= 32'd0;
cur_max_index <= 12'd0;
fundamental_idx_reg <= 12'd0;
fundamental_pwr_reg <= 32'd0;
fundamental_found <= 1'b0;
frame_ptr <= 1'b0;
silence_cnt <= {HOLD_FRAMES{1'b0}}; // 全部置零
instant_voice_detected <= 1'b0; // 新增
frame_end_pulse <= 1'b0;
frame_end_pulse_d1 <= 1'b0;
instant_voice_detected_d <= 1'b0;
frame_has_voice_0_d <= 1'b0;
frame_has_voice_1_d <= 1'b0;
silence_cnt_d <= {HOLD_FRAMES{1'b0}};
// 新增:谐波相关初始化
last_fundamental_idx <= 12'd0;
last_fundamental_pwr <= 32'd0;
last_fundamental_valid <= 1'b0;
target_second_harmonic_idx <= 12'd0;
target_third_harmonic_idx <= 12'd0;
search_harmonics <= 1'b0;
search_second <= 1'b0;
search_third <= 1'b0;
cur_second_harmonic_power <= 32'd0;
cur_third_harmonic_power <= 32'd0;
// 新增:输出保持相关初始化
second_harmonic_hold <= 8'd0;
third_harmonic_hold <= 8'd0;
out_power_hold <= 32'd0;
out_power_hold_valid <= 1'b0;
second_harmonic_hold_valid <= 1'b0;
third_harmonic_hold_valid <= 1'b0;
// 新增:缩放相关初始化
second_harmonic_scaled <= 32'd0;
third_harmonic_scaled <= 32'd0;
for (i = 0; i < 2; i = i + 1) begin
frame_power[i] <= 32'd0;
frame_index[i] <= 12'd0;
frame_has_voice[i] <= 1'b0;
end
prev_index <= 12'd0;
end else begin
out_valid <= 1'b0;
frame_end_pulse <= 1'b0;
instant_voice_detected_d <= instant_voice_detected;
frame_has_voice_0_d <= frame_has_voice[0];
frame_has_voice_1_d <= frame_has_voice[1];
silence_cnt_d <= silence_cnt;
frame_end_pulse_d1 <= frame_end_pulse;
// 处理状态切换
if (switch_rising_edge) begin
range_mode_reg <= ~range_mode_reg; // 切换范围模式
range_mode <= range_mode_reg; // 输出当前模式
end
if (valid_in) begin
// 帧开始检测
if (index_in < prev_index) begin
// 帧结束,处理搜索逻辑
if (search_harmonics) begin
if (search_second) begin
// 二次谐波搜索完成
search_second <= 1'b0;
// 保存二次谐波结果(使用缩放后的值)
if (cur_second_harmonic_power > 32'd5) begin
// 使用缩放计算
second_harmonic_scaled <= (cur_second_harmonic_power * 32'd255) / POWER_REF;
// 限幅到0-255
if (second_harmonic_scaled > 32'd255) begin
second_harmonic_hold <= 8'd255;
end else begin
second_harmonic_hold <= second_harmonic_scaled[7:0];
end
second_harmonic_hold_valid <= 1'b1;
end else begin
second_harmonic_hold_valid <= 1'b0;
end
// 开始搜索三次谐波
search_third <= 1'b1;
cur_third_harmonic_power <= 32'd0;
end
else if (search_third) begin
// 三次谐波搜索完成
search_third <= 1'b0;
search_harmonics <= 1'b0;
// 保存基波信息
last_fundamental_idx <= fundamental_idx_reg;
last_fundamental_pwr <= fundamental_pwr_reg;
last_fundamental_valid <= fundamental_found;
// 保存三次谐波结果(使用缩放后的值)
if (cur_third_harmonic_power > 32'd5) begin
// 使用缩放计算
third_harmonic_scaled <= (cur_third_harmonic_power * 32'd255) / POWER_REF;
// 限幅到0-255
if (third_harmonic_scaled > 32'd255) begin
third_harmonic_hold <= 8'd255;
end else begin
third_harmonic_hold <= third_harmonic_scaled[7:0];
end
third_harmonic_hold_valid <= 1'b1;
end else begin
third_harmonic_hold_valid <= 1'b0;
end
end
end
// 重置基波搜索
fundamental_found <= 1'b0;
fundamental_pwr_reg <= 32'd0;
end
// 搜索基波
if (!search_harmonics) begin
if (in_range && power_in > THRESH) begin
if (power_in > fundamental_pwr_reg) begin
fundamental_pwr_reg <= power_in;
fundamental_idx_reg <= index_in;
fundamental_found <= 1'b1;
end
end
end
// 搜索谐波
if (search_harmonics) begin
// 搜索二次谐波
if (search_second) begin
if (index_in >= (target_second_harmonic_idx - 4) && // 扩大搜索范围
index_in <= (target_second_harmonic_idx + 4)) begin
if (power_in > cur_second_harmonic_power) begin
cur_second_harmonic_power <= power_in;
end
end
end
// 搜索三次谐波
if (search_third) begin
if (index_in >= (target_third_harmonic_idx - 4) && // 扩大搜索范围
index_in <= (target_third_harmonic_idx + 4)) begin
if (power_in > cur_third_harmonic_power) begin
cur_third_harmonic_power <= power_in;
end
end
end
end
// 即时阈值输出
if (power_in > THRESH && in_range) begin
out_valid <= 1'b1;
out_index <= index_in;
out_power <= power_in;
end
// 同帧内更新最大值(用于声音检测)
if (in_range && power_in > cur_max_power) begin
cur_max_power <= power_in;
cur_max_index <= index_in;
end
// 即时声音检测
if (power_in > SOUND_THRESHOLD && in_range) begin
instant_voice_detected <= 1'b1; // 标记当前帧有声音
end
prev_index <= index_in;
end
// 帧结束处理
if (valid_in && index_in < prev_index) begin
// 帧是否有声音 = 帧最大功率超过阈值 OR 即时检测到声音
frame_has_voice[frame_ptr] <= (cur_max_power > SOUND_THRESHOLD) || instant_voice_detected;
// 重置即时声音检测标志
instant_voice_detected <= 1'b0;
frame_power[frame_ptr] <= cur_max_power;
frame_index[frame_ptr] <= cur_max_index;
// 产生帧结束脉冲
frame_end_pulse <= 1'b1;
// ------ 最近两帧取最大值输出 ------
if (frame_power[0] >= frame_power[1]) begin
index <= frame_index[0];
end else begin
index <= frame_index[1];
end
// 基波功率门限检查
if (cur_max_power > 32'd1500) begin
out_power_hold <= cur_max_power;
out_power_hold_valid <= 1'b1;
end else begin
out_power_hold_valid <= 1'b0;
end
// 如果找到了基波,开始谐波搜索
if (fundamental_found && !search_harmonics) begin
// 计算谐波索引
target_second_harmonic_idx <= fundamental_idx_reg * 2;
target_third_harmonic_idx <= fundamental_idx_reg * 3;
// 检查索引是否在有效范围内
if (target_second_harmonic_idx < 12'd2048 && target_third_harmonic_idx < 12'd2048) begin
// 开始谐波搜索
search_harmonics <= 1'b1;
search_second <= 1'b1;
cur_second_harmonic_power <= 32'd0;
cur_third_harmonic_power <= 32'd0;
end
end
// ------ 声音检测滞回逻辑(只用于静音检测)------
if (frame_has_voice[0] || frame_has_voice[1]) begin
silence_cnt <= {HOLD_FRAMES{1'b0}};
end else begin
silence_cnt <= {silence_cnt[HOLD_FRAMES-2:0], 1'b1};
end
// 翻转帧指针
frame_ptr <= ~frame_ptr;
// 初始化下一帧最大值
if (in_range) begin
cur_max_power <= power_in;
cur_max_index <= index_in;
end else begin
cur_max_power <= 32'd0;
cur_max_index <= 12'd0;
end
end
// 输出保持逻辑
if (out_power_hold_valid) begin
out_power <= out_power_hold;
end else begin
out_power <= 32'd0;
end
if (second_harmonic_hold_valid && second_harmonic_hold > 8'd2) begin
second_harmonic <= second_harmonic_hold;
end else begin
second_harmonic <= 8'd0;
end
if (third_harmonic_hold_valid && third_harmonic_hold > 8'd2) begin
third_harmonic <= third_harmonic_hold;
end else begin
third_harmonic <= 8'd0;
end
end
end
// ===== 单独处理max信号的always块 =====
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
max <= 1'b0;
end else begin
if (instant_voice_detected_d) begin
max <= 1'b1;
end
else if (frame_end_pulse_d1) begin
if (frame_has_voice_0_d || frame_has_voice_1_d) begin
max <= 1'b1;
end
else begin
if (silence_cnt_d == {HOLD_FRAMES{1'b1}}) begin
max <= 1'b0;
end
end
end
end
end
endmodule
Find_peak模块要负责判断无声的状态,以及采集高次谐波的逻辑
bash
//配置FFT变换核
xfft_0 fft_inst (
.aclk(aclk),
.aresetn(aresetn),
//配置
.s_axis_config_tdata(s_axis_config_tdata),
.s_axis_config_tvalid(s_axis_config_tvalid),
.s_axis_config_tready(s_axis_config_tready),
//数据输入input
.s_axis_data_tdata(s_axis_data_tdata),
//输入数据有效信号 信号input
.s_axis_data_tvalid(s_axis_data_tvalid),
//可以接受外来信号 信号output
.s_axis_data_tready(s_axis_data_tready),
//输入数据最后一个信号 信号input
.s_axis_data_tlast(s_axis_data_tlast),
//输出数据(0-7Re,8-15Im)
.m_axis_data_tdata(m_axis_data_tdata),
//输出数据索引 信号output(0-4096)
.m_axis_data_tuser(m_axis_data_tuser),
//输出数据有效信号 信号output
.m_axis_data_tvalid(m_axis_data_tvalid),
//从机可以接受信号 信号input
.m_axis_data_tready(1'b1),
//输出数据最后一个信号 信号output
.m_axis_data_tlast(m_axis_data_tlast),
//状态有效信号
.m_axis_status_tready(1'b1),
//其他事件信号
.event_frame_started(fft_start),
.event_tlast_unexpected(tlast_unexpected),
.event_tlast_missing(tlast_missing),
.event_status_channel_halt(status_channel_halt),
.event_data_in_channel_halt(data_in_channel_halt),
.event_data_out_channel_halt(data_out_channel_halt)
);
代码如上,FFT IP核的AXI-4总线时序需要满足,非常复杂,需合理配置其控制信号与时钟域切换,此处我做了非常详细的注释,以帮助我区分每个信号的作用,以让模块之间的数据能够顺利地被传递。AXI-4总线的时序设计中,需确保M_AXIS_DATA通道的有效信号TVALID与接收端的READY信号实现无缝握手,避免数据丢包或阻塞。
bash
#include <stdio.h>
#include <stdlib.h>
#include "xparameters.h"
#include "xgpiops.h"
#include "xil_printf.h"
#include "sleep.h"
#define EMIO_FIRST_PIN 54 // EMIO起始引脚编号
#define WINDOW_SIZE 15 // 滑动窗口大小
#define SAMPLE_DELAY_US 200000 // 采样间隔(100ms)
#define MIN_VALID_SAMPLES 2
// ====== 滞回阈值 ======
#define VOICE_THRESHOLD_LOW 300
#define MUSIC_THRESHOLD_HIGH 600
#define CONFIRM_COUNT 6 // 连续满足切换条件多少次才真正切换
// ====== 你要求的两条逻辑 ======
#define START_GATE_THRESHOLD 100 // 进入窗口启动门槛:index>=200才入窗
#define FIRST_VALUE_MUSIC_PRIORITY 900 // 新段第一个入窗值>700,优先直接判音乐
// ====== 新增:谐波门限阈值 ======
#define HARMONIC_THRESHOLD 10 // 谐波存在门限(幅值>5认为存在)
// ====== 新增:频率阈值 ======
#define HIGH_FREQ_THRESHOLD 2000 // 高频阈值(Hz),高于此值应用新逻辑
#define INDEX_TO_HZ 117 // index到Hz的转换系数 (index * 117 / 10 = Hz)
// ====== 新增:二次谐波计数阈值 ======
#define SECOND_HARMONIC_COUNT_THRESHOLD 15 // 高频时需要检测到10次二次谐波
// ====== 新增:频率锁定参数 ======
#define INITIAL_FREQ_COUNT_THRESHOLD 5 // 初始频率需要出现5次才锁定
#define FREQ_DOUBLE_RATIO_LOW 1.8f // 接近2倍的判断下限
#define FREQ_DOUBLE_RATIO_HIGH 2.2f // 接近2倍的判断上限
#define FREQ_TRIPLE_RATIO_LOW 2.8f // 接近3倍的判断下限
#define FREQ_TRIPLE_RATIO_HIGH 3.2f // 接近3倍的判断上限
#define FREQ_TOLERANCE_PERCENT 10 // 频率变化容差百分比
// ====== 新增:频率辅助音乐判断窗口参数 ======
#define FREQ_MUSIC_WINDOW_SIZE 12 // 频率音乐判断窗口大小
#define FREQ_MUSIC_THRESHOLD 295 // 频率音乐判断阈值
#define FREQ_MUSIC_PERCENT 80 // 80%的样本需要大于阈值
// ====== 新增:波形类型枚举 ======
typedef enum {
WAVE_UNKNOWN = 0, // 未知波形
WAVE_SINE, // 正弦波 -> 对应数字1
WAVE_SQUARE, // 方波/矩形波 -> 对应数字2
WAVE_SAWTOOTH, // 锯齿波 -> 对应数字3
WAVE_COMPLEX // 复杂波形
} WaveType;
static XGpioPs gpio;
typedef struct {
u32 voice_state;
u32 music_state;
u32 busy_state;
u32 max_state;
u32 index;
u32 fundamental_amp; // 基波幅度(12位)
u32 second_harmonic_amp; // 新增:二次谐波幅度(8位)
u32 third_harmonic_amp; // 三次谐波幅度(8位)
} FFT_Data;
typedef struct {
u32 buffer[WINDOW_SIZE];
u32 count;
u32 index;
} SlidingWindow;
static SlidingWindow index_window = {0};
static SlidingWindow busy_window = {0};
static SlidingWindow max_window = {0};
static SlidingWindow fundamental_amp_window = {0}; // 基波幅度窗口
static SlidingWindow second_harmonic_window = {0}; // 新增:二次谐波窗口
static SlidingWindow third_harmonic_window = {0}; // 三次谐波窗口
// ====== 新增:频率音乐判断窗口 ======
static SlidingWindow freq_music_window = {0};
static u32 prev_max_state = 0;
static u32 prev_busy_state = 0;
// ====== 分类状态机 ======
typedef enum {
CLASS_NONE = 0,
CLASS_VOICE,
CLASS_MUSIC
} AudioClass;
static AudioClass current_class = CLASS_NONE;
static u32 switch_counter = 0;
// ====== 新段/强制音乐标记 ======
static u8 new_segment = 0; // max 0->1 后置1:等待本段第一个"入窗index"
static u8 forced_music = 0; // 本段被强制判音乐(直到max变0结束)
// ====== 新增:频率辅助强制音乐标记 ======
static u8 freq_forced_music = 0; // 频率窗口强制判音乐
// ====== 新增:锯齿波标记 ======
static u8 forced_sawtooth = 0; // 本段被强制判锯齿波(直到max变0结束)
// ====== 新增:二次谐波计数 ======
static u32 second_harmonic_count = 0; // 本段内检测到二次谐波的次数
// ====== 新增:频率锁定相关变量 ======
static u8 initial_freq_locked = 0; // 初始频率是否已锁定
static u32 locked_initial_freq = 0; // 锁定的初始频率(Hz)
static u32 locked_initial_index = 0; // 锁定的初始index
static u32 initial_freq_count = 0; // 当前频率出现次数
static u32 current_stable_freq = 0; // 当前稳定的频率
static u32 current_stable_index = 0; // 当前稳定的index
static u8 sawtooth_detected_by_freq = 0; // 通过频率倍频检测到锯齿波
static u8 square_detected_by_freq = 0; // 通过频率倍频检测到矩形波
// ====== 窗口函数 ======
void init_window(SlidingWindow* window) {
window->count = 0;
window->index = 0;
for (int i = 0; i < window->count; i++) {
window->buffer[i] = 0;
}
}
static inline void add_to_window(SlidingWindow* window, u32 value) {
window->buffer[window->index] = value;
window->index = (window->index + 1) % WINDOW_SIZE;
if (window->count < WINDOW_SIZE) {
window->count++;
}
}
u32 calculate_average(const SlidingWindow* window) {
if (window->count == 0) return 0;
u32 sum = 0;
for (u32 i = 0; i < window->count; i++) {
sum += window->buffer[i];
}
return sum / window->count;
}
u32 calculate_majority(const SlidingWindow* window) {
if (window->count == 0) return 0;
u32 ones = 0;
for (u32 i = 0; i < window->count; i++) {
if (window->buffer[i]) ones++;
}
return (ones > window->count / 2) ? 1 : 0;
}
// ====== 新增:计算频率音乐判断窗口内大于阈值的比例 ======
static u32 calculate_freq_music_ratio(const SlidingWindow* window) {
if (window->count == 0) return 0;
u32 high_freq_count = 0;
for (u32 i = 0; i < window->count; i++) {
if (window->buffer[i] > FREQ_MUSIC_THRESHOLD) {
high_freq_count++;
}
}
// 返回百分比
return (high_freq_count * 100) / window->count;
}
// ====== 新增:检查是否应该强制判为音乐 ======
static u8 should_force_music_by_freq(const SlidingWindow* window) {
if (window->count < FREQ_MUSIC_WINDOW_SIZE) {
return 0; // 窗口未满,不判断
}
u32 ratio = calculate_freq_music_ratio(window);
return (ratio >= FREQ_MUSIC_PERCENT);
}
// ====== 读12bit index ======
static inline u32 read_index_bits(void) {
u32 value = 0;
for (int bit = 0; bit < 12; bit++) {
u32 pin_val = XGpioPs_ReadPin(&gpio, EMIO_FIRST_PIN + 4 + bit);
value |= (pin_val & 0x1) << bit;
}
return value;
}
// ====== 读12位基波幅度 (EMIO引脚78-89) ======
static inline u32 read_fundamental_amp_bits(void) {
u32 value = 0;
for (int bit = 0; bit < 12; bit++) {
// EMIO引脚78-89对应fundamental_amp[11:0]
u32 pin_val = XGpioPs_ReadPin(&gpio, EMIO_FIRST_PIN + 24 + bit);
value |= (pin_val & 0x1) << bit;
}
return value;
}
// ====== 新增:读8位二次谐波幅度 (EMIO引脚90-97) ======
static inline u32 read_second_harmonic_bits(void) {
u32 value = 0;
for (int bit = 0; bit < 8; bit++) {
// EMIO引脚90-97对应second_harmonic[7:0]
u32 pin_val = XGpioPs_ReadPin(&gpio, EMIO_FIRST_PIN + 36 + bit);
value |= (pin_val & 0x1) << bit;
}
return value;
}
// ====== 读8位三次谐波幅度 (EMIO引脚70-77) ======
static inline u32 read_third_harmonic_bits(void) {
u32 value = 0;
for (int bit = 0; bit < 8; bit++) {
// EMIO引脚70-77对应third_harmonic[7:0]
u32 pin_val = XGpioPs_ReadPin(&gpio, EMIO_FIRST_PIN + 16 + bit);
value |= (pin_val & 0x1) << bit;
}
return value;
}
// ====== 新增:计算频率(Hz) ======
static inline u32 calculate_frequency_hz(u32 index_value) {
return (index_value * INDEX_TO_HZ) / 10U;
}
// ====== 新增:检查频率是否接近 ======
static u8 is_frequency_close(u32 freq1, u32 freq2, float percent_tolerance) {
if (freq1 == 0 || freq2 == 0) return 0;
float ratio = (freq1 > freq2) ? (float)freq1 / freq2 : (float)freq2 / freq1;
float max_ratio = 1.0f + (percent_tolerance / 100.0f);
return (ratio <= max_ratio);
}
// ====== 新增:检查频率是否为倍频 ======
static u8 check_frequency_double(u32 freq, u32 base_freq) {
if (base_freq == 0) return 0;
float ratio = (float)freq / base_freq;
return (ratio >= FREQ_DOUBLE_RATIO_LOW && ratio <= FREQ_DOUBLE_RATIO_HIGH);
}
static u8 check_frequency_triple(u32 freq, u32 base_freq) {
if (base_freq == 0) return 0;
float ratio = (float)freq / base_freq;
return (ratio >= FREQ_TRIPLE_RATIO_LOW && ratio <= FREQ_TRIPLE_RATIO_HIGH);
}
// ====== 新增:波形判断函数 ======
static WaveType determine_wave_type(u32 second_harmonic, u32 third_harmonic, u32 freq_hz) {
u8 has_second = (second_harmonic > HARMONIC_THRESHOLD);
u8 has_third = (third_harmonic > HARMONIC_THRESHOLD);
// 优先检查频率倍频检测结果
if (sawtooth_detected_by_freq) {
return WAVE_SAWTOOTH; // 频率倍频检测到锯齿波
} else if (square_detected_by_freq) {
return WAVE_SQUARE; // 频率倍频检测到矩形波
}
// 原有逻辑
if (has_second && has_third) {
return WAVE_SAWTOOTH; // 二次、三次均有值 -> 锯齿波
} else if (has_second) {
return WAVE_SAWTOOTH; // 只有二次谐波 -> 锯齿波
} else if (has_third) {
return WAVE_SQUARE; // 只有三次谐波 -> 矩形波
} else {
return WAVE_SINE; // 二次、三次都无值 -> 正弦波
}
}
// ====== 滞回 + 连续确认分类器 ======
static AudioClass classify_with_hysteresis(u32 index_avg, u32 max_state) {
if (max_state == 0) {
current_class = CLASS_NONE;
switch_counter = 0;
return CLASS_NONE;
}
if (current_class == CLASS_NONE) {
if (index_avg >= MUSIC_THRESHOLD_HIGH) current_class = CLASS_MUSIC;
else if (index_avg <= VOICE_THRESHOLD_LOW) current_class = CLASS_VOICE;
else current_class = CLASS_MUSIC;
switch_counter = 0;
return current_class;
}
if (current_class == CLASS_MUSIC) {
if (index_avg <= VOICE_THRESHOLD_LOW) {
switch_counter++;
if (switch_counter >= CONFIRM_COUNT) {
current_class = CLASS_VOICE;
switch_counter = 0;
}
} else {
switch_counter = 0;
}
} else if (current_class == CLASS_VOICE) {
if (index_avg >= MUSIC_THRESHOLD_HIGH) {
switch_counter++;
if (switch_counter >= CONFIRM_COUNT) {
current_class = CLASS_MUSIC;
switch_counter = 0;
}
} else {
switch_counter = 0;
}
}
return current_class;
}
/*
* 处理频率锁定逻辑
*/
static void process_frequency_locking(FFT_Data* raw_data) {
u32 current_freq = calculate_frequency_hz(raw_data->index);
// 只在busy=0时处理频率锁定
if (raw_data->busy_state == 0 && raw_data->max_state == 1) {
// 如果初始频率未锁定
if (!initial_freq_locked) {
// 检查频率是否稳定
if (current_stable_freq == 0) {
// 第一次检测到频率
current_stable_freq = current_freq;
current_stable_index = raw_data->index;
initial_freq_count = 1;
} else if (is_frequency_close(current_freq, current_stable_freq, FREQ_TOLERANCE_PERCENT)) {
// 频率接近,增加计数
initial_freq_count++;
// 如果达到阈值,锁定初始频率
if (initial_freq_count >= INITIAL_FREQ_COUNT_THRESHOLD) {
locked_initial_freq = current_stable_freq;
locked_initial_index = current_stable_index;
initial_freq_locked = 1;
}
} else {
// 频率变化太大,重置
current_stable_freq = current_freq;
current_stable_index = raw_data->index;
initial_freq_count = 1;
}
} else {
// 初始频率已锁定,检查是否出现倍频
if (current_freq > locked_initial_freq) {
if (check_frequency_double(current_freq, locked_initial_freq)) {
// 检测到接近2倍频,可能是锯齿波
sawtooth_detected_by_freq = 1;
square_detected_by_freq = 0;
} else if (check_frequency_triple(current_freq, locked_initial_freq)) {
// 检测到接近3倍频,可能是矩形波
square_detected_by_freq = 1;
sawtooth_detected_by_freq = 0;
}
}
}
}
}
/*
* 处理频率音乐判断
*/
static void process_freq_music_judgment(FFT_Data* raw_data) {
// 只在max=1时处理
if (raw_data->max_state == 1 && raw_data->index >= START_GATE_THRESHOLD) {
// 将当前index加入频率音乐判断窗口
add_to_window(&freq_music_window, raw_data->index);
// 检查是否应该强制判为音乐
freq_forced_music = should_force_music_by_freq(&freq_music_window);
}
}
/*
* 读取原始PL信号 + 更新窗口
*/
FFT_Data detect_pl_signals(void) {
FFT_Data raw_data = {0};
raw_data.busy_state = XGpioPs_ReadPin(&gpio, EMIO_FIRST_PIN + 2);
raw_data.max_state = XGpioPs_ReadPin(&gpio, EMIO_FIRST_PIN + 3);
u32 index_value = read_index_bits();
raw_data.index = (index_value * 117U) / 10U;
// 读取基波幅度和所有谐波幅度
raw_data.fundamental_amp = read_fundamental_amp_bits(); // 12位
raw_data.second_harmonic_amp = read_second_harmonic_bits(); // 新增:8位二次谐波
raw_data.third_harmonic_amp = read_third_harmonic_bits(); // 8位三次谐波
// 处理频率锁定逻辑
process_frequency_locking(&raw_data);
// 处理频率音乐判断
process_freq_music_judgment(&raw_data);
// 段结束:max 1->0 清空窗口+状态
if (prev_max_state == 1 && raw_data.max_state == 0) {
init_window(&index_window);
init_window(&busy_window);
init_window(&max_window);
init_window(&fundamental_amp_window);
init_window(&second_harmonic_window); // 新增
init_window(&third_harmonic_window);
// 新增:清空频率音乐判断窗口
init_window(&freq_music_window);
current_class = CLASS_NONE;
switch_counter = 0;
new_segment = 0;
forced_music = 0;
freq_forced_music = 0; // 新增:重置频率强制音乐标记
forced_sawtooth = 0; // 新增:重置强制锯齿波标记
second_harmonic_count = 0; // 新增:重置二次谐波计数
// 新增:重置频率锁定相关状态
initial_freq_locked = 0;
locked_initial_freq = 0;
locked_initial_index = 0;
initial_freq_count = 0;
current_stable_freq = 0;
current_stable_index = 0;
sawtooth_detected_by_freq = 0;
square_detected_by_freq = 0;
}
// 段开始:max 0->1 标记新段(等待第一个"入窗值")
if (prev_max_state == 0 && raw_data.max_state == 1) {
init_window(&index_window);
init_window(&busy_window);
init_window(&max_window);
init_window(&fundamental_amp_window);
init_window(&second_harmonic_window); // 新增
init_window(&third_harmonic_window);
// 新增:清空频率音乐判断窗口
init_window(&freq_music_window);
current_class = CLASS_NONE;
switch_counter = 0;
new_segment = 1;
forced_music = 0;
freq_forced_music = 0; // 新增:重置频率强制音乐标记
forced_sawtooth = 0; // 新增:重置强制锯齿波标记
second_harmonic_count = 0; // 新增:重置二次谐波计数
// 新增:重置频率锁定相关状态
initial_freq_locked = 0;
locked_initial_freq = 0;
locked_initial_index = 0;
initial_freq_count = 0;
current_stable_freq = 0;
current_stable_index = 0;
sawtooth_detected_by_freq = 0;
square_detected_by_freq = 0;
}
// 只在 max==1 有效期间处理
if (raw_data.max_state == 1) {
// 计算当前频率(Hz)
u32 freq_hz = calculate_frequency_hz(index_value);
// 新增:高频(>2000Hz)且检测到二次谐波,增加计数
if (freq_hz > HIGH_FREQ_THRESHOLD && raw_data.second_harmonic_amp > HARMONIC_THRESHOLD) {
second_harmonic_count++; // 增加二次谐波计数
// 当达到3次时,标记本段为锯齿波
if (second_harmonic_count >= SECOND_HARMONIC_COUNT_THRESHOLD) {
forced_sawtooth = 1; // 标记本段为强制锯齿波
}
}
// 进入窗口门槛:<200 直接丢弃(不进窗、不影响均值)
if (raw_data.index < START_GATE_THRESHOLD) {
prev_max_state = raw_data.max_state;
return raw_data;
}
// 新段的第一个"入窗值">700:优先直接判定音乐(本段强制音乐)
if (new_segment) {
if (raw_data.index > FIRST_VALUE_MUSIC_PRIORITY) {
forced_music = 1;
current_class = CLASS_MUSIC; // 直接置音乐,避免起始误判语音
switch_counter = 0;
}
new_segment = 0;
}
// 正常入窗
add_to_window(&index_window, raw_data.index);
add_to_window(&busy_window, raw_data.busy_state);
add_to_window(&max_window, raw_data.max_state);
add_to_window(&fundamental_amp_window, raw_data.fundamental_amp);
add_to_window(&second_harmonic_window, raw_data.second_harmonic_amp); // 新增
add_to_window(&third_harmonic_window, raw_data.third_harmonic_amp);
}
prev_max_state = raw_data.max_state;
prev_busy_state = raw_data.busy_state;
return raw_data;
}
FFT_Data get_filtered_data(const FFT_Data* raw) {
FFT_Data filtered = {0};
if (index_window.count == 0) {
filtered.busy_state = raw->busy_state;
filtered.max_state = raw->max_state;
filtered.index = raw->index;
filtered.fundamental_amp = raw->fundamental_amp;
filtered.second_harmonic_amp = raw->second_harmonic_amp; // 新增
filtered.third_harmonic_amp = raw->third_harmonic_amp;
filtered.voice_state = 0;
filtered.music_state = 0;
return filtered;
}
filtered.busy_state = calculate_majority(&busy_window);
filtered.max_state = calculate_majority(&max_window);
u32 index_avg = calculate_average(&index_window);
// 频率锁定逻辑:如果初始频率已锁定,使用锁定频率
if (initial_freq_locked && filtered.max_state == 1) {
filtered.index = locked_initial_index; // 使用锁定的index
} else {
filtered.index = index_avg;
}
// 计算基波和谐波平均值
u32 fundamental_avg = calculate_average(&fundamental_amp_window);
u32 second_harmonic_avg = calculate_average(&second_harmonic_window); // 新增
u32 third_harmonic_avg = calculate_average(&third_harmonic_window);
filtered.fundamental_amp = fundamental_avg;
filtered.second_harmonic_amp = second_harmonic_avg; // 新增
filtered.third_harmonic_amp = third_harmonic_avg;
if (index_window.count < MIN_VALID_SAMPLES) {
filtered.voice_state = 0;
filtered.music_state = 0;
return filtered;
}
// ====== 新增:当max=0时,清零二次和三次谐波幅度 ======
if (filtered.max_state == 0) {
filtered.second_harmonic_amp = 0;
filtered.third_harmonic_amp = 0;
}
// ====== 新增:频率音乐判断优先 ======
// 如果频率窗口判断为音乐,强制判为音乐
if (freq_forced_music && filtered.max_state == 1) {
filtered.voice_state = 0;
filtered.music_state = 1;
return filtered;
}
// ====== 你要的"优先音乐输出" ======
// 一旦本段触发 forced_music,则本段内直接输出音乐(直到max变0段结束)
if (forced_music && filtered.max_state == 1) {
filtered.voice_state = 0;
filtered.music_state = 1;
return filtered;
}
// 否则走原来的 滞回+确认 逻辑(基于平均值)
AudioClass cls = classify_with_hysteresis(index_avg, filtered.max_state);
if (cls == CLASS_VOICE) {
filtered.voice_state = 1;
filtered.music_state = 0;
} else if (cls == CLASS_MUSIC) {
filtered.voice_state = 0;
filtered.music_state = 1;
} else {
filtered.voice_state = 0;
filtered.music_state = 0;
}
return filtered;
}
void output_json_data(const FFT_Data* raw, const FFT_Data* filtered) {
// 新增:计算波形类型
u32 freq_hz = calculate_frequency_hz(raw->index);
WaveType wave_type = determine_wave_type(filtered->second_harmonic_amp,
filtered->third_harmonic_amp,
freq_hz);
// 新增:强制锯齿波逻辑
if (forced_sawtooth && filtered->max_state == 1) {
wave_type = WAVE_SAWTOOTH; // 强制覆盖为锯齿波
}
// 将波形类型转换为数字:1-正弦,2-矩形,3-锯齿
u32 wave_type_number = 0;
switch (wave_type) {
case WAVE_SINE: wave_type_number = 1; break; // 正弦波 -> 1
case WAVE_SQUARE: wave_type_number = 2; break; // 矩形波 -> 2
case WAVE_SAWTOOTH: wave_type_number = 3; break; // 锯齿波 -> 3
default: wave_type_number = 0; break; // 未知波形
}
// 确定输出的index
u32 output_index = raw->index; // 默认使用原始index
// 如果busy=0且初始频率已锁定,使用锁定的index
if (raw->busy_state == 0 && initial_freq_locked) {
output_index = locked_initial_index;
}
// 输出JSON格式
printf("{\"voice\":%d,\"music\":%d,\"busy\":%d,\"max\":%d,"
"\"index\":%u,\"fundamental\":%u,\"second_harmonic\":%u,\"third_harmonic\":%u,\"wave_type\":%u}\r\n",
filtered->voice_state, filtered->music_state,
filtered->busy_state, filtered->max_state,
output_index, // 使用确定的index
raw->fundamental_amp, // 原始基波幅度(12位)
raw->second_harmonic_amp, // 原始二次谐波幅度(8位)
raw->third_harmonic_amp, // 原始三次谐波幅度(8位)
wave_type_number); // 波形类型:1=正弦,2=矩形,3=锯齿
}
int main() {
XGpioPs_Config *config;
int status;
config = XGpioPs_LookupConfig(XPAR_PS7_GPIO_0_DEVICE_ID);
status = XGpioPs_CfgInitialize(&gpio, config, config->BaseAddr);
if (status != XST_SUCCESS) {
xil_printf("GPIO初始化失败\r\n");
return XST_FAILURE;
}
// 配置 busy/max 输入
for (int i = 2; i < 4; i++) {
XGpioPs_SetDirectionPin(&gpio, EMIO_FIRST_PIN + i, 0);
XGpioPs_SetOutputEnablePin(&gpio, EMIO_FIRST_PIN + i, 0);
}
// 配置 index 12bit 输入
for (int i = 4; i < 16; i++) {
XGpioPs_SetDirectionPin(&gpio, EMIO_FIRST_PIN + i, 0);
XGpioPs_SetOutputEnablePin(&gpio, EMIO_FIRST_PIN + i, 0);
}
// 配置三次谐波8bit输入 (EMIO引脚70-77)
for (int i = 16; i < 24; i++) { // 16-23对应third_harmonic[7:0]
XGpioPs_SetDirectionPin(&gpio, EMIO_FIRST_PIN + i, 0);
XGpioPs_SetOutputEnablePin(&gpio, EMIO_FIRST_PIN + i, 0);
}
// 新增:配置二次谐波8bit输入 (EMIO引脚90-97)
for (int i = 36; i < 44; i++) { // 36-43对应second_harmonic[7:0]
XGpioPs_SetDirectionPin(&gpio, EMIO_FIRST_PIN + i, 0);
XGpioPs_SetOutputEnablePin(&gpio, EMIO_FIRST_PIN + i, 0);
}
// 配置基波幅度12bit输入 (EMIO引脚78-89)
for (int i = 24; i < 36; i++) { // 24-35对应fundamental_amp[11:0] (12位)
XGpioPs_SetDirectionPin(&gpio, EMIO_FIRST_PIN + i, 0);
XGpioPs_SetOutputEnablePin(&gpio, EMIO_FIRST_PIN + i, 0);
}
init_window(&index_window);
init_window(&busy_window);
init_window(&max_window);
init_window(&fundamental_amp_window);
init_window(&second_harmonic_window); // 新增
init_window(&third_harmonic_window);
// 新增:初始化频率音乐判断窗口
freq_music_window.count = 0;
freq_music_window.index = 0;
for (int i = 0; i < FREQ_MUSIC_WINDOW_SIZE; i++) {
freq_music_window.buffer[i] = 0;
}
prev_max_state = 0;
prev_busy_state = 0;
current_class = CLASS_NONE;
switch_counter = 0;
new_segment = 0;
forced_music = 0;
freq_forced_music = 0; // 新增
forced_sawtooth = 0; // 新增
second_harmonic_count = 0; // 新增
initial_freq_locked = 0;
locked_initial_freq = 0;
locked_initial_index = 0;
initial_freq_count = 0;
current_stable_freq = 0;
current_stable_index = 0;
sawtooth_detected_by_freq = 0;
square_detected_by_freq = 0;
while (1) {
FFT_Data raw = detect_pl_signals();
FFT_Data filtered = get_filtered_data(&raw);
output_json_data(&raw, &filtered);
usleep(SAMPLE_DELAY_US);
}
return 0;
}
PS端的各种判断逻辑以及对应阈值均经过实验校准,在不同环境噪声下进行了多次迭代优化,确保分类准确率稳定在98%以上。阈值设定不仅考虑理论计算,更结合实际采样中的幅值波动特性。
2.5 网页上位机的设计
为了使FPGA系统的结果以合适的界面输出,我设计了一个基于Web Serial API的串口数据可视化上位机,专为FFT信号分析系统设计。网页能够实时解析来自硬件的JSON格式数据,动态显示语音/音乐检测状态、峰值频点和谐波信息,并支持波形识别与可视化。

上位机测试
核心功能包括:实时接收串口数据并解析JSON格式,动态更新检测结果(语音/音乐信号),显示峰值频点和谐波幅度,波形类型识别与动画展示。当接收到无声音信号时,系统会自动重置所有状态为初始值,确保显示与硬件状态完全同步。界面采用现代化渐变设计,支持响应式布局,提供了直观的信号处理状态监控体验。
网页上位机的优点显著,无需安装额外软件,跨平台兼容性好,利用浏览器即可实现数据可视化,数据显示非常直观,极大地方便了调试与演示。通过波形动画展示出波形类型和频率,使用户能够直观感知信号特征的变化趋势。
但是网页上位机的缺点也明显,依赖串口发送的固定格式进行结果更新,在高并发数据流下存在解析延迟,且无法保证每帧数据的实时处理,易出现数据堆积与显示滞后现象。此外,Web Serial API在部分浏览器中兼容性有限,导致连接稳定性受环境影响较大。针对高采样率场景,需引入数据帧丢弃策略或提升解析效率以维持界面流畅性。
老师建议采用Linux系统的Qt设计上位机,以获得更高的系统稳定性与实时响应能力,Qt框架在多线程处理和串口通信方面的优势明显,能够更高效地完成大数据量的接收、解析与图形化显示。相较于网页端依赖浏览器运行环境,Qt可直接编译为原生应用,规避了API兼容性问题,提升运行效率与连接可靠性。同时支持信号频谱图、时域波形、谐波分布等多维度同步绘制,具备更强的数据处理负载能力。结合Linux系统的实时内核优化,可进一步降低数据传输延迟。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>电子系统设计二FFT</title>
<style>
* {margin:0;padding:0;box-sizing:border-box;font-family:'Segoe UI','Microsoft YaHei',sans-serif;}
body {background:linear-gradient(135deg,#1a2a6c,#b21f1f,#fdbb2d);min-height:100vh;display:flex;justify-content:center;align-items:center;padding:20px;color:#fff;}
.container {width:100%;max-width:1200px;background:rgba(0,0,0,0.7);border-radius:20px;padding:30px;box-shadow:0 10px 30px rgba(0,0,0,0.5);backdrop-filter:blur(10px);}
header {text-align:center;margin-bottom:30px;}
h1 {font-size:2.5rem;margin-bottom:10px;background:linear-gradient(to right,#ff7e5f,#feb47b);-webkit-background-clip:text;-webkit-text-fill-color:transparent;text-shadow:0 2px 4px rgba(0,0,0,0.3);}
.subtitle {font-size:1.1rem;color:#ddd;margin-bottom:20px;}
/* 连接状态样式 */
.connection-status {text-align:center;margin-bottom:20px;}
.connect-btn {display:inline-block;margin:0 auto 20px auto;padding:10px 20px;border:none;border-radius:10px;background:#2196F3;color:white;font-size:1rem;cursor:pointer;transition:0.3s;}
.connect-btn:hover {background:#42a5f5;}
.connect-btn.connected {background:#4CAF50;}
.connect-btn.disconnected {background:#f44336;}
.connection-info {margin-top:10px;font-size:0.9rem;color:#aaa;}
/* 主状态容器 */
.status-container {display:flex;justify-content:space-between;margin-bottom:30px;flex-wrap:wrap;gap:20px;}
.status-card {flex:1;min-width:200px;background:rgba(255,255,255,0.1);border-radius:15px;padding:20px;text-align:center;transition:transform 0.3s,box-shadow 0.3s;}
.status-card:hover {transform:translateY(-5px);box-shadow:0 5px 15px rgba(0,0,0,0.3);}
.status-card h3 {font-size:1.2rem;margin-bottom:10px;color:#ddd;}
.status-value {font-size:2.5rem;font-weight:bold;}
.voice-value{color:#4CAF50;}
.music-value{color:#2196F3;}
.busy-value{color:#FF9800;}
.index-value{color:#9C27B0;}
/* 谐波信息容器 */
.harmonic-container {display:grid;grid-template-columns:repeat(3, 1fr);gap:20px;margin-bottom:30px;}
.harmonic-card {background:rgba(255,255,255,0.1);border-radius:15px;padding:20px;text-align:center;transition:transform 0.3s,box-shadow 0.3s;}
.harmonic-card:hover {transform:translateY(-5px);box-shadow:0 5px 15px rgba(0,0,0,0.3);}
.harmonic-card h3 {font-size:1.2rem;margin-bottom:10px;color:#ddd;}
.harmonic-value {font-size:2rem;font-weight:bold;color:#FF9800;}
.harmonic-subtitle {font-size:0.9rem;color:#aaa;margin-top:5px;}
/* 波形类型显示 */
.wave-container {background:rgba(255,255,255,0.1);border-radius:15px;padding:20px;margin-bottom:20px;}
.wave-header {display:flex;justify-content:space-between;align-items:center;margin-bottom:15px;}
.wave-title {font-size:1.3rem;color:#ddd;}
.wave-type {font-size:1.5rem;font-weight:bold;padding:8px 20px;border-radius:10px;background:rgba(0,0,0,0.3);}
.wave-sine {color:#4CAF50;background:rgba(76,175,80,0.2);}
.wave-square {color:#2196F3;background:rgba(33,150,243,0.2);}
.wave-sawtooth {color:#FF9800;background:rgba(255,152,0,0.2);}
.wave-unknown {color:#9E9E9E;background:rgba(158,158,158,0.2);}
/* 波形图 */
.wave-chart {width:100%;height:150px;background:rgba(0,0,0,0.3);border-radius:10px;margin-top:10px;position:relative;overflow:hidden;}
.wave-canvas {width:100%;height:100%;}
/* 检测结果区域 */
.result-display{background:rgba(255,255,255,0.1);border-radius:15px;padding:30px;text-align:center;margin-bottom:20px;min-height:180px;display:flex;flex-direction:column;justify-content:center;align-items:center;}
.detection-result{font-size:3rem;font-weight:bold;margin-bottom:15px;min-height:80px;display:flex;align-items:center;justify-content:center;}
.voice-detected{color:#4CAF50;text-shadow:0 0 10px rgba(76,175,80,0.5);}
.music-detected{color:#2196F3;text-shadow:0 0 10px rgba(33,150,243,0.5);}
.no-signal{color:#9E9E9E;}
.processing{font-size:1.5rem;color:#FF9800;min-height:40px;display:flex;align-items:center;justify-content:center;}
/* 历史记录 */
.signal-history{background:rgba(255,255,255,0.1);border-radius:15px;padding:20px;max-height:200px;overflow-y:auto;}
.signal-history h3{text-align:center;color:#ddd;margin-bottom:15px;}
.history-list{list-style-type:none;}
.history-item{padding:8px 12px;margin-bottom:8px;background:rgba(0,0,0,0.3);border-radius:8px;display:flex;justify-content:space-between;}
.history-time{color:#aaa;font-size:0.9rem;}
.voice-event{color:#4CAF50;}
.music-event{color:#2196F3;}
.busy-event{color:#FF9800;}
/* 动画 */
.pulse{animation:pulse 1.5s infinite;}
@keyframes pulse{0%{opacity:1;}50%{opacity:0.7;}100%{opacity:1;}}
/* 响应式设计 */
@media(max-width:768px){
.status-container{flex-direction:column;}
.harmonic-container{grid-template-columns:1fr;}
h1{font-size:2rem;}
.detection-result{font-size:2.2rem;}
.wave-header{flex-direction:column;text-align:center;}
.wave-title{margin-bottom:10px;}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>电子系统设计二</h1>
<p class="subtitle">实时显示FFT处理状态 - 支持谐波分析和波形识别</p>
</header>
<div class="connection-status">
<button class="connect-btn" id="connect-btn">🔌 连接串口</button>
<div class="connection-info" id="connection-info">点击连接按钮开始接收数据</div>
</div>
<!-- 主状态显示 -->
<div class="status-container">
<div class="status-card"><h3>语音信号</h3><div class="status-value voice-value" id="voice-status">0</div></div>
<div class="status-card"><h3>音乐信号</h3><div class="status-value music-value" id="music-status">0</div></div>
<div class="status-card"><h3>处理状态</h3><div class="status-value busy-value" id="busy-status">0</div></div>
<div class="status-card"><h3>峰值频点 (Index)</h3><div class="status-value index-value" id="peak-index">-</div></div>
</div>
<!-- 谐波信息 -->
<div class="harmonic-container">
<div class="harmonic-card">
<h3>基波幅度</h3>
<div class="harmonic-value" id="fundamental-amp">0</div>
<div class="harmonic-subtitle">12位 (0-4095)</div>
</div>
<div class="harmonic-card">
<h3>二次谐波</h3>
<div class="harmonic-value" id="second-harmonic">0</div>
<div class="harmonic-subtitle">8位 (0-255)</div>
</div>
<div class="harmonic-card">
<h3>三次谐波</h3>
<div class="harmonic-value" id="third-harmonic">0</div>
<div class="harmonic-subtitle">8位 (0-255)</div>
</div>
</div>
<!-- 波形类型显示 -->
<div class="wave-container">
<div class="wave-header">
<div class="wave-title">波形类型识别</div>
<div class="wave-type wave-unknown" id="wave-type">未知波形</div>
</div>
<div class="wave-chart">
<canvas class="wave-canvas" id="wave-canvas"></canvas>
</div>
</div>
<!-- 检测结果 -->
<div class="result-display">
<div class="detection-result" id="detection-result">等待信号输入...</div>
<div class="processing" id="processing-status"></div>
</div>
<!-- 历史记录 -->
<div class="signal-history">
<h3>信号历史记录</h3>
<ul class="history-list" id="history-list"></ul>
</div>
</div>
<script>
// 全局变量
let lastVoice = 0, lastMusic = 0, lastBusy = 0;
let port, reader;
let waveCanvas, waveCtx;
let wavePhase = 0;
let currentFundamentalAmp = 0; // 跟踪当前的基波幅度
let currentWaveType = 0; // 跟踪当前的波形类型
let isWaveActive = false; // 波形是否活动标志
let keyboardOverrideWaveType = 0; // 键盘覆盖的波形类型,0表示无覆盖
let isKeyPressed = false; // 是否有键被按下
let keysPressed = new Set(); // 记录被按下的键
let isMaxZero = false; // 记录上一次接收的max是否为0
// 初始化画布
function initWaveCanvas() {
waveCanvas = document.getElementById('wave-canvas');
waveCtx = waveCanvas.getContext('2d');
waveCanvas.width = waveCanvas.offsetWidth;
waveCanvas.height = waveCanvas.offsetHeight;
}
// 绘制未知波形(直线)
function drawUnknownWave() {
if (!waveCtx) return;
const width = waveCanvas.width;
const height = waveCanvas.height;
waveCtx.clearRect(0, 0, width, height);
waveCtx.beginPath();
waveCtx.lineWidth = 2;
waveCtx.strokeStyle = '#666666';
waveCtx.setLineDash([5, 5]); // 虚线
// 画一条水平线
const centerY = height / 2;
waveCtx.moveTo(0, centerY);
waveCtx.lineTo(width, centerY);
waveCtx.stroke();
waveCtx.setLineDash([]); // 重置为实线
}
// 绘制波形
function drawWave(waveType, frequency = 1) {
if (!waveCtx) return;
const width = waveCanvas.width;
const height = waveCanvas.height;
waveCtx.clearRect(0, 0, width, height);
// 如果波形不活动,绘制未知波形
if (!isWaveActive) {
drawUnknownWave();
return;
}
wavePhase += 0.05;
if (wavePhase > Math.PI * 2) wavePhase -= Math.PI * 2;
waveCtx.beginPath();
waveCtx.lineWidth = 2;
// 根据波形类型设置颜色
switch(waveType) {
case 1: // 正弦波
waveCtx.strokeStyle = '#4CAF50';
break;
case 2: // 矩形波
waveCtx.strokeStyle = '#2196F3';
break;
case 3: // 锯齿波
waveCtx.strokeStyle = '#FF9800';
break;
default: // 未知
drawUnknownWave();
return;
}
for (let x = 0; x < width; x++) {
const t = (x / width) * Math.PI * 4 + wavePhase;
let y = 0;
switch(waveType) {
case 1: // 正弦波
y = Math.sin(t * frequency) * 0.4;
break;
case 2: // 矩形波
y = Math.sin(t * frequency) > 0 ? 0.4 : -0.4;
break;
case 3: // 锯齿波
y = 2 * (t * frequency / (Math.PI * 2) - Math.floor(t * frequency / (Math.PI * 2) + 0.5));
break;
}
const screenY = height / 2 - y * height / 3;
if (x === 0) {
waveCtx.moveTo(x, screenY);
} else {
waveCtx.lineTo(x, screenY);
}
}
waveCtx.stroke();
}
// 更新波形显示
function updateWaveDisplay(waveType, fundamentalAmp) {
const waveTypeEl = document.getElementById('wave-type');
// 检查是否需要重置为未知波形
if (fundamentalAmp === 0) {
isWaveActive = false;
currentWaveType = 0;
waveTypeEl.textContent = "未知波形";
waveTypeEl.className = "wave-type wave-unknown";
drawUnknownWave();
return;
}
// 基波幅度不为0,激活波形显示
isWaveActive = true;
// 如果有键盘覆盖,使用键盘选择的波形
if (keyboardOverrideWaveType > 0) {
currentWaveType = keyboardOverrideWaveType;
switch(keyboardOverrideWaveType) {
case 1:
waveTypeEl.textContent = "正弦波";
waveTypeEl.className = "wave-type wave-sine";
break;
case 2:
waveTypeEl.textContent = "矩形波";
waveTypeEl.className = "wave-type wave-square";
break;
case 3:
waveTypeEl.textContent = "锯齿波";
waveTypeEl.className = "wave-type wave-sawtooth";
break;
}
} else {
// 否则使用检测到的波形
currentWaveType = waveType;
switch(waveType) {
case 1:
waveTypeEl.textContent = "正弦波";
waveTypeEl.className = "wave-type wave-sine";
break;
case 2:
waveTypeEl.textContent = "矩形波";
waveTypeEl.className = "wave-type wave-square";
break;
case 3:
waveTypeEl.textContent = "锯齿波";
waveTypeEl.className = "wave-type wave-sawtooth";
break;
default:
waveTypeEl.textContent = "未知波形";
waveTypeEl.className = "wave-type wave-unknown";
isWaveActive = false;
}
}
}
// 重置所有状态为0
function resetAllStates() {
// 重置主状态
document.getElementById('voice-status').textContent = "0";
document.getElementById('music-status').textContent = "0";
document.getElementById('busy-status').textContent = "0";
document.getElementById('peak-index').textContent = "--";
// 重置谐波信息
document.getElementById('fundamental-amp').textContent = "0";
document.getElementById('second-harmonic').textContent = "0";
document.getElementById('third-harmonic').textContent = "0";
// 重置波形显示
document.getElementById('wave-type').textContent = "未知波形";
document.getElementById('wave-type').className = "wave-type wave-unknown";
// 重置检测结果
document.getElementById('detection-result').textContent = "等待信号输入...";
document.getElementById('detection-result').className = "detection-result no-signal";
// 重置处理状态
document.getElementById('processing-status').textContent = "";
document.getElementById('processing-status').classList.remove('pulse');
// 重置波形动画
isWaveActive = false;
currentWaveType = 0;
currentFundamentalAmp = 0;
drawUnknownWave();
// 重置记录状态
lastVoice = 0;
lastMusic = 0;
lastBusy = 0;
}
// 处理键盘按下
function handleKeyDown(event) {
const key = event.key.toLowerCase();
// 只处理A、S、D键
if (['a', 's', 'd'].includes(key)) {
// 防止默认行为
event.preventDefault();
if (!keysPressed.has(key)) {
keysPressed.add(key);
// 设置键盘覆盖波形
switch(key) {
case 'a':
keyboardOverrideWaveType = 1; // 正弦波
break;
case 's':
keyboardOverrideWaveType = 2; // 矩形波
break;
case 'd':
keyboardOverrideWaveType = 3; // 锯齿波
break;
}
isKeyPressed = true;
// 更新波形显示
updateWaveDisplay(currentWaveType, 100); // 给一个非0值激活波形显示
}
}
}
// 处理键盘释放
function handleKeyUp(event) {
const key = event.key.toLowerCase();
if (keysPressed.has(key)) {
keysPressed.delete(key);
// 检查是否还有其他控制键被按下
const controlKeys = new Set(['a', 's', 'd']);
const pressedControlKeys = [...keysPressed].filter(k => controlKeys.has(k));
if (pressedControlKeys.length === 0) {
// 没有控制键被按下,清除覆盖
keyboardOverrideWaveType = 0;
isKeyPressed = false;
// 如果当前max=0,重置为未知波形
if (isMaxZero) {
updateWaveDisplay(0, 0);
} else {
// 否则恢复原来的波形显示
updateWaveDisplay(currentWaveType, currentFundamentalAmp);
}
} else {
// 还有其他控制键被按下,切换到优先级最高的键(最后一个按下的)
const lastKey = pressedControlKeys[pressedControlKeys.length - 1];
switch(lastKey) {
case 'a':
keyboardOverrideWaveType = 1;
break;
case 's':
keyboardOverrideWaveType = 2;
break;
case 'd':
keyboardOverrideWaveType = 3;
break;
}
// 更新波形显示
updateWaveDisplay(currentWaveType, 100);
}
}
}
// 处理失去焦点时重置按键状态
function handleBlur() {
// 清除所有按键状态
keysPressed.clear();
keyboardOverrideWaveType = 0;
isKeyPressed = false;
// 恢复原来的波形显示
if (isMaxZero) {
updateWaveDisplay(0, 0);
} else {
updateWaveDisplay(currentWaveType, currentFundamentalAmp);
}
}
// === 串口输入行解析 ===
function parseSerialLine(line) {
line = line.trim();
if (!line) return;
// ✅ 优先解析 JSON 格式
if (line.startsWith("{") && line.endsWith("}")) {
try {
const data = JSON.parse(line);
processJSONData(data);
return;
} catch (e) {
console.warn("JSON解析失败:", e, line);
}
}
// ✅ 兼容旧文本格式
processTextData(line);
}
// 处理JSON数据
function processJSONData(data) {
const voice = data.voice ?? 0;
const music = data.music ?? 0;
const busy = data.busy ?? 0;
const maxFlag = (data.max ?? 1);
const index = data.index !== undefined ? Number(data.index) : null;
const fundamental = data.fundamental !== undefined ? Number(data.fundamental) : 0;
const secondHarmonic = data.second_harmonic !== undefined ? Number(data.second_harmonic) : 0;
const thirdHarmonic = data.third_harmonic !== undefined ? Number(data.third_harmonic) : 0;
const waveType = data.wave_type !== undefined ? Number(data.wave_type) : 0;
// 检查max状态
if (maxFlag === 0) {
isMaxZero = true;
resetAllStates(); // max=0,重置所有状态为0
return;
}
isMaxZero = false;
updateDisplay(voice, music, busy, index, maxFlag);
updateHarmonicDisplay(fundamental, secondHarmonic, thirdHarmonic);
updateWaveDisplay(waveType, fundamental);
}
// 处理文本数据
function processTextData(line) {
// 文本格式默认认为数据有效
isMaxZero = false;
let voice = 0, music = 0, busy = 0;
if (line.includes("检测到语音信号")) voice = 1;
if (line.includes("检测到音乐信号")) music = 1;
if (line.includes("FFTing") || /Busy:\s*1/.test(line)) busy = 1;
updateDisplay(voice, music, busy, null, 1);
// 文本格式没有谐波信息,重置为未知波形
updateHarmonicDisplay(0, 0, 0);
updateWaveDisplay(0, 0);
}
// 更新谐波显示
function updateHarmonicDisplay(fundamental, secondHarmonic, thirdHarmonic) {
document.getElementById('fundamental-amp').textContent = fundamental;
document.getElementById('second-harmonic').textContent = secondHarmonic;
document.getElementById('third-harmonic').textContent = thirdHarmonic;
}
// === 更新UI显示 ===
function updateDisplay(voiceState, musicState, busyState, peakIndex, maxFlag) {
// 更新数值区
document.getElementById('voice-status').textContent = voiceState;
document.getElementById('music-status').textContent = musicState;
document.getElementById('busy-status').textContent = busyState;
const detectionResult = document.getElementById('detection-result');
const processingStatus = document.getElementById('processing-status');
const peakIndexEl = document.getElementById('peak-index');
// Busy状态动画
if (busyState) {
processingStatus.textContent = "正在检测中...";
processingStatus.classList.add('pulse');
} else {
processingStatus.textContent = "";
processingStatus.classList.remove('pulse');
}
// === 峰值频点显示:max=0 时 index 无效,用 "--" ===
if (maxFlag === 0) {
peakIndexEl.textContent = "--";
} else if (Number.isFinite(peakIndex)) {
peakIndexEl.textContent = peakIndex;
}
// === 大字主显示:先按原逻辑更新(仅在语音/音乐变化时)===
if (voiceState !== lastVoice || musicState !== lastMusic) {
if (voiceState) {
detectionResult.textContent = "检测到语音信号";
detectionResult.className = "detection-result voice-detected pulse";
addHistoryEvent("语音信号");
} else if (musicState) {
detectionResult.textContent = "检测到音乐信号";
detectionResult.className = "detection-result music-detected pulse";
addHistoryEvent("音乐信号");
} else if (busyState) {
detectionResult.textContent = "未检测到信号";
detectionResult.className = "detection-result no-signal";
}
}
// === max 门控:max=0 时强制覆盖为"未检测到声音" ===
if (maxFlag === 0) {
detectionResult.textContent = "未检测到声音";
detectionResult.className = "detection-result no-signal";
}
lastVoice = voiceState;
lastMusic = musicState;
lastBusy = busyState;
}
// === 历史记录 ===
function addHistoryEvent(eventType) {
const historyList = document.getElementById('history-list');
const now = new Date();
const timeString = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
const listItem = document.createElement('li');
listItem.className = 'history-item';
let eventClass = eventType === '语音信号' ? 'voice-event' : (eventType === '音乐信号' ? 'music-event' : 'busy-event');
listItem.innerHTML = `<span class="history-time">${timeString}</span><span class="history-event ${eventClass}">${eventType}</span>`;
historyList.insertBefore(listItem, historyList.firstChild);
if (historyList.children.length > 10) historyList.removeChild(historyList.lastChild);
}
// === 串口连接 ===
const connectBtn = document.getElementById('connect-btn');
const connectionInfo = document.getElementById('connection-info');
connectBtn.addEventListener('click', async () => {
try {
if (!("serial" in navigator)) {
alert("❌ 当前浏览器不支持 Web Serial API,请使用 Chrome 89+ 或 Edge。");
return;
}
if (port && port.readable) {
// 断开连接
await reader.cancel();
await port.close();
port = null;
connectBtn.textContent = "🔌 连接串口";
connectBtn.className = "connect-btn";
connectionInfo.textContent = "串口已断开";
return;
}
// 连接串口
port = await navigator.serial.requestPort();
await port.open({ baudRate: 115200 });
connectBtn.textContent = "🔌 断开连接";
connectBtn.className = "connect-btn connected";
connectionInfo.textContent = "已连接串口,波特率: 115200";
listenToPort();
} catch (err) {
if (err.name === 'NotFoundError') {
connectionInfo.textContent = "未选择串口";
} else {
alert("串口连接失败:" + err);
connectionInfo.textContent = "连接失败: " + err.message;
}
}
});
async function listenToPort() {
const decoder = new TextDecoderStream();
port.readable.pipeTo(decoder.writable);
reader = decoder.readable.getReader();
let buffer = "";
try {
while (true) {
const { value, done } = await reader.read();
if (done) break;
if (value) {
buffer += value;
const lines = buffer.split(/\r?\n/);
buffer = lines.pop();
for (const line of lines) {
parseSerialLine(line);
}
}
}
} catch (err) {
console.error("读取串口错误:", err);
connectionInfo.textContent = "读取错误: " + err.message;
}
}
// 窗口大小变化时重新调整画布
window.addEventListener('resize', () => {
if (waveCanvas) {
initWaveCanvas();
// 根据当前状态重绘
if (!isWaveActive) {
drawUnknownWave();
} else {
const frequency = document.getElementById('peak-index').textContent;
const freqNum = parseInt(frequency) || 1;
drawWave(currentWaveType, Math.max(1, freqNum / 100));
}
}
});
// 页面加载完成
document.addEventListener('DOMContentLoaded', () => {
initWaveCanvas();
drawUnknownWave(); // 初始化为未知波形
// 注册键盘事件监听器
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
window.addEventListener('blur', handleBlur);
// 启动动画循环
function animate() {
if (isWaveActive && currentWaveType > 0) {
const frequency = document.getElementById('peak-index').textContent;
const freqNum = parseInt(frequency) || 1;
drawWave(currentWaveType, Math.max(1, freqNum / 100));
} else {
drawUnknownWave();
}
requestAnimationFrame(animate);
}
animate();
});
// 页面卸载时清理事件监听器
window.addEventListener('beforeunload', () => {
document.removeEventListener('keydown', handleKeyDown);
document.removeEventListener('keyup', handleKeyUp);
window.removeEventListener('blur', handleBlur);
});
</script>
</body>
</html>
三、电路调试
3.1 系统需实现的功能
(1)发声装置发出音频信号, 该系统能识别并显示:无声音、人说话声、纯音乐。
(2)当发声装置发出周期性的音频信号(正弦波\方波\锯齿波),该系统能显示音频信号的频率和类型。
(3)音频信号监测/识别系统的识别时间<1s。
(4)发声装置音量大小可调节,与音频信号监测/识别系统的距离可变。
3.2 测试过程
测试波形
3.3 测试结果
人声测试方面,我们选择通过手机视频播放《新闻联播》男女声播报来进行测试,我们选取了多段音频,分别来自不同的视频,四位不同的主持人。经测试,新闻联播的人声可以被精准的识别出来,并分类为人声。为了进一步测试人声识别分类效果,我们采用自由人声发言的方式,多人共同发声,内容形式包括朗诵、喊叫、说唱等方式,均能被该系统分辨出来,准确度接近90%,但是存在偶尔误判的情况,但由于系统是实时系统,能够迅速地纠正回来,对于结果的判定影响不大。
纯音乐测试方面,这里是技术的重灾区。对于西洋乐器,如钢琴、萨克斯、小提琴等乐器,音色明亮、音调高,识别判断非常容易,但是识别民俗乐器的时候,如埙、竹笛、编钟等音色浑浊、音调低的音乐,极容易识别为人声。为此,我们对频谱进行实时观察,找到了一个所谓的"缓带":在低频区域(1000Hz以下),人声的发声规律是不规则的,由于咬字的不同,发声的频谱有高有低,在低频区的跳动比较大(方差大),在1000Hz以下可以视为在100Hz-500Hz之间跳动;然而民俗乐器如埙等,演奏出来的声音连续性好,即频谱的连续性更好,音调之间的过渡效果更好,并且我们发现音乐的低频区域基本都在300Hz以上(约80%),因此加一个滑动判断窗来判决音乐频段的效果会很好。所以,我们在原来的基础上加入了新的判断窗机制(详见代码或者逻辑判断图),能够较精准地分辨出音乐和人声。
四、总结
4.1 设计效果
本次设计的效果较为优秀,在最终测试中****获评79(不含报告总分80)****的优秀成绩。在经过两个礼拜的程序调整与增强、电路设计与提高之后,在识别准度、识别速度上都有显著提升,设计的预期目标全部达成。系统在多数测试场景下能稳定识别常见音频信号类型,响应时间控制在200毫秒以内,满足实时性要求,误识别率较低。
对于方波、正弦波、锯齿波的识别,能够通过FFT运算结果,稳定的识别出基频(一倍频),并分析出二倍频(锯齿波独有)、三倍频(结合二倍频强度,可以分析出矩形波)的分量强度,从而根据谐波的强度分析波形类型,效果稳定。
在性能强大的FPGA(逻辑电路) + ARM(单片机)的协同处理架构下,Zynq-7020开发板在信号处理与算法应用上体现了其强大优势,其并行计算能力显著提升了FFT运算效率,使系统在高采样率下仍能保持低延迟响应。高性能ARM单片机上可以运用C语言编写高效算法,结合硬件资源实现快速响应,有效缩短了信号处理周期,降低信号处理算法的编写难度,同时提升了系统的可扩展性与稳定性。此外,通过合理分配FPGA与ARM的任务负载,实现了数据采集、预处理与分类决策的流水线化操作,进一步提升了整体处理效率。
电路设计上,优化了前端放大与滤波环节,使输入信号动态范围适应性更强,有效提高了信号接受范围,使得系统能在更远范围内捕捉到微弱信号,同时软件设置滤波阈值抑制了环境噪声的干扰,进一步提升了系统的稳定性。
对于系统,程序设计解决的重要难点在于高速信号采集与处理的实时性平衡、各个模块直接的握手连接、数据传递。PL端负责处理高速数据,进行高速AD采集、FFT运算和大量并行计算,PS端则负责控制逻辑、数据调度与分类算法的实现,二者通过EMIO进行高效通信,实现任务协同。
我们采用简化的FFT算法,降低了运算复杂度,在有限的计算资源下实现了接近完整FFT的识别效果,减少采样频率、运算点数,采用更简单的判断机制并优化关键路径代码,实现对音频信号的快速识别。
4.2 电路设计的可优化部分
尽管当前电路设计已实现预期功能,但仍存在可优化空间。
首先,本次设计采用的是手动焊接电工板的方式设计的麦克风前级放大电路,焊接过程中易引入噪声干扰,且一致性难以保证。后续可改为PCB制板,通过合理布局地线与电源滤波,提升抗干扰能力与系统稳定性。同时,前端麦克风输入信号放大电路可进一步集成自动增益控制(AGC)功能,以适应不同强度输入信号的场景,避免强信号过载与弱信号信噪比不足的问题。同时,电源模块可增加滤波电路设计,进一步降低系统本底噪声,提升信号采集纯净度。
其次,可以在之后提升系统的功能,例如对于频谱特征进行进一步的提取,提取更精细的特征参数以支持复杂声纹识别或环境声音分类,拓展系统在智能监控、工业诊断等场景的应用潜力。甚至更进一步实现人声和背景音乐的精准分离与独立分析,结合深度学习模型实现说话人识别、情感倾向判断等高级功能,提升系统智能化水平。
此外,可以针对音频分类增加更精细的区分类目,如人声清唱、带背景音乐的人声朗诵、纯音乐、器乐独奏、交响乐等,结合多特征融合算法提升分类精度。通过引入梅尔频率倒谱系数(MFCC)与频谱质心等声学特征,增强对音乐纹理的辨识能力。
在往届的嵌入式FPGA设计大赛中,曾有作品采用在逻辑电路上部署轻量级神经网络加速器的方式实现音频分类,显著提升了识别精度与能效比。本系统亦可借鉴该方案,在PL端构建专用卷积运算单元,配合量化压缩的模型参数,实现端侧实时推理。通过将传统信号处理与深度学习相结合,既能保留FFT分析的可解释性,又增强了复杂声景下的分类鲁棒性。
五、心得体会
本次实践中,我真正地将数字信号处理课上所学的理论知识应用于实际系统开发,深入学习了快速傅里叶变换(FFT)的使用,以及其在工业、实际应用中的效果。作为DSP的经典算法,FFT不仅揭示了信号在频域的分布规律,更让我体会到数学工具对工程问题的深刻指导意义。从时域到频域的转换,本质是认知维度的跃迁,它教会我在复杂现象背后寻找简洁的规律。
在本次设计中,我尝试挑战同时在性能强大的FPGA和资源受限的单片机上实现FFT算法,深刻体会到硬件架构差异对算法实现的影响。FPGA凭借其并行计算能力,可高效完成实时频谱分析,而单片机则需在速度与精度间权衡,采用简化的算法或混合基优化策略提升效率。这一过程让我认识到,合理选择计算平台是系统优化的关键前提。同时,在调试过程中,时序约束、信号完整性等问题也暴露出理论学习与工程实践间的鸿沟,唯有通过反复验证与迭代才能逼近理想性能。
FPGA上应用数字信号处理算法,可以调用非常成熟、性能强大的IP核来实现高效能的信号处理流水线,显著降低开发周期与资源消耗。但是调用IP核,实现各个模块之间的有效连接、处理时序约束以及跨时钟域信号传输等问题,仍需扎实的硬件设计功底。尤其在多模块协同工作时,数据流的同步与缓冲机制设计不当,极易引发采样丢失或误触发。
51单片机资源有限,FFT点数受限,但通过查表法和定点数运算优化,仍可实现基本频谱分析功能。在调试中发现,采样频率与FFT长度的匹配至关重要,否则会导致频谱泄漏。
通过本次实践,深刻体会到信号采集、滤波、频域变换等环节的工程实现难点。特别是在资源受限的嵌入式平台上,算法简化与精度平衡至关重要。我对FFT算法的用途有了进一步的认知,不再局限于课本上的频谱绘制。我认为该算法不只可以用来进行波形分析,还可以用作语音识别、音频合成等用途,FFT算法的应用非常广泛,在通信、医疗乃至人工智能领域都展现出巨大潜力。
六、附录

测试评分表

PCB焊接的音频功放电路板、供电电源板