电赛信号题准备包实战:FFT、ADC采样与相位差测量避坑指南

发布时间:2026/10/20 13:56:00
电赛信号题准备包实战:FFT、ADC采样与相位差测量避坑指南 简介这份资源面向备战2023年全国大学生电子设计竞赛信号类题目的参赛者与嵌入式学习者聚焦信号处理算法在STM32H7平台上的落地实现。压缩包共约2000个文件以C源码1291个与头文件553个为主体辅以txt说明、少量cpp与md文档整体约333.54MB规模接近完整工程仓库。内容围绕STM32H7高性能微控制器展开涉及HAL库外设驱动、定时器与HRTIM配置、I2C通信以及线性插值、变换测试等信号处理相关模块可支撑实时采集、滤波与解调等典型赛题场景。已有1029人学习下载说明其在电赛备赛群体中具备一定参考价值。读者可借此梳理信号处理理论到嵌入式代码的映射关系参考工程目录组织方式复用外设驱动与算法实现并对照测试数据调试优化从而提升赛题方案的设计效率与实现质量。1. 从一份电赛信号题准备包说起它到底能帮你省下多少试错时间如果你正在准备电子设计竞赛的信号类赛题或者刚拿到“2023年电赛信号题所做的一些准备.zip”这个包大概率会经历一个很典型的阶段知道要考信号处理但不知道从哪下手手头有开发板、有示波器却不知道怎么把“理论上的频谱分析”变成“板子上跑得起来的代码”。这份资源就是冲着这个痛点去的——它不是一份教程而是一个已经跑通过信号题准备流程的工程包里面大概率包含常用信号发生、采集、频谱分析、滤波、参数测量等模块的参考实现和调试记录。我拆过不少类似的竞赛准备包很多都是“代码能跑但不知道为什么这么写”或者“参数对不上实际波形”。这份包的价值在于它把信号题里最耗时间的几个环节——比如ADC采样率怎么定、FFT点数怎么选、窗函数怎么加、幅值相位怎么校准——都落成了可复现的步骤。适合谁适合已经学过数字信号处理但没做过完整信号链的在校生也适合带队的年轻工程师想快速搭一个验证平台。如果你指望它直接给你比赛答案那会失望但如果你想在三天内把信号采集到分析的链路跑通它比翻三本教材都快。2. 信号题准备包的结构拆解从文件命名看设计意图2.1 压缩包里的典型目录与文件类型拿到一个竞赛准备包我习惯先看目录结构因为命名方式往往暴露了作者的调试顺序。这份包解压后常见的是按“信号产生→采集→处理→显示/测量”分文件夹或者按“基础模块→综合例程→测试数据”分层。里面大概率有.c/.h源码、.ioc或.syscfg之类的工程配置、.csv或.txt的采样数据、以及若干.md或.txt的调试笔记。注意不同人整理习惯不同但核心模块跑不出这几类文件/文件夹名示例作用你该关注什么signal_gen/产生正弦、方波、三角波可能用DAC或PWM频率分辨率、幅值控制方式adc_sample/ADC采集与DMA搬运采样率配置、触发源、数据对齐fft_analyze/FFT及频谱计算点数、窗函数、幅值归一化filter_design/FIR/IIR滤波系数生成方式、阶数、群延迟measure/频率、幅值、相位差测量过零检测还是FFT插值test_data/实测波形数据用来验证你的算法是否一致如果你拿到的包目录名和这个不一样没关系按功能归类就行。关键是先找到“从模拟信号到数字序列”的那条主线别一上来就扎进FFT代码里。2.2 先跑通最小链路信号发生→ADC→串口输出很多新手翻车是因为想一口气把FFT和测量全跑通结果卡在ADC数据不对。我的建议是先不管FFT只做一件事——让ADC采一段已知信号通过串口把原始数据打出来用Excel或Python画个图确认采样率和幅值是对的。这一步在准备包里通常有现成工程你只需要改几个参数。以常见的STM32ADCDMA为例关键配置是// adc_config.c 片段定时器触发ADCDMA搬运 // 假设系统时钟72MHz定时器ARR719则触发频率72MHz/(7191)100kHz // 即采样率100kHz满足大多数电赛信号题要求信号频率一般20kHz TIM_TimeBaseInitTypeDef TIM_InitStruct; TIM_InitStruct.TIM_Prescaler 0; TIM_InitStruct.TIM_Period 719; // 决定采样率 TIM_InitStruct.TIM_ClockDivision 0; TIM_InitStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_InitStruct); ADC_InitTypeDef ADC_InitStruct; ADC_InitStruct.ADC_ContinuousConvMode DISABLE; // 由定时器触发不连续 ADC_InitStruct.ADC_ExternalTrigConv ADC_ExternalTrigConv_T2_TRGO; ADC_InitStruct.ADC_DataAlign ADC_DataAlign_Right; ADC_Init(ADC1, ADC_InitStruct); DMA_InitTypeDef DMA_InitStruct; DMA_InitStruct.DMA_BufferSize 1024; // 一次搬1024个点 DMA_InitStruct.DMA_MemoryBaseAddr (uint32_t)adc_buf; DMA_InitStruct.DMA_DIR DMA_DIR_PeripheralSRC; DMA_Init(DMA1_Channel1, DMA_InitStruct);这段代码的逻辑是定时器2以100kHz频率产生TRGO事件触发ADC单次转换DMA把每次转换结果搬到adc_buf数组搬满1024个点后停止。参数上你要盯住三个TIM_Period决定采样率DMA_BufferSize决定一次分析的数据量ADC_DataAlign决定数据是左对齐还是右对齐影响后续幅值计算。如果串口打出来的波形频率对不上先查定时器时钟源是不是72MHz再查ADC采样周期是否太长导致实际转换时间超过触发间隔。提示采样率不是越高越好。电赛信号题常见信号频率在1kHz~20kHz按奈奎斯特至少2倍但工程上取5~10倍更稳。100kHz采样率对应20kHz信号只有5个点/周期做FFT时谱线会泄漏建议至少200kHz或降低信号频率。2.3 用Python快速验证采集数据别在单片机上硬调拿到串口数据后别急着在单片机里写FFT。先把数据存成CSV用Python的numpy和matplotlib画出来看时域波形和频谱。这一步能帮你排除“是采集错了还是算法错了”。准备包里如果有test_data文件夹直接拿现成数据练手更快。import numpy as np import matplotlib.pyplot as plt # 读取串口保存的CSV假设一列采样率100kHz data np.loadtxt(adc_data.csv, delimiter,) fs 100000 N len(data) t np.arange(N) / fs # 时域 plt.figure() plt.plot(t[:200], data[:200]) # 只看前200个点避免太密 plt.xlabel(Time (s)) plt.ylabel(ADC Value) plt.title(Time Domain) # 频域加汉宁窗减少泄漏 window np.hanning(N) spectrum np.fft.fft(data * window) freq np.fft.fftfreq(N, 1/fs) magnitude np.abs(spectrum) / (N/2) # 归一化到幅值 plt.figure() plt.plot(freq[:N//2], magnitude[:N//2]) plt.xlabel(Frequency (Hz)) plt.ylabel(Magnitude) plt.title(Spectrum) plt.show()逻辑说明np.hanning(N)加窗是为了减少频谱泄漏代价是主瓣变宽np.abs(spectrum)/(N/2)是把FFT结果换算成实际幅值对于单频信号加窗后还要乘窗函数增益系数汉宁窗约2.0这里简化处理。参数上fs必须和实际采样率一致否则频率轴全错N取2的幂次如1024、2048能让FFT更快但准备包里如果用了非2的幂numpy也能算只是慢一点。如果频谱峰值对应的频率和你信号源设置差很多先检查信号源输出是否真的接对了再检查ADC参考电压和输入偏置。3. FFT与参数测量从频谱到频率、幅值、相位差3.1 FFT点数、窗函数与频率分辨率的关系电赛信号题里最常考的就是“测量正弦波频率、幅值、相位差”而FFT是绕不开的工具。但很多人直接套用1024点FFT结果频率分辨率不够两个靠近的频率分不开。频率分辨率 采样率 / FFT点数。比如采样率100kHz1024点FFT分辨率约97.7Hz测1kHz信号误差可能到几十Hz。要提高到1Hz分辨率要么增加点数到100k点内存吃不消要么用插值算法。准备包里通常会有两种做法一种是固定点数FFT峰值搜索插值校正另一种是过零检测测频率FFT只用来测幅值。我一般推荐混合方案频率用插值FFT或过零检测幅值用FFT加窗校正相位差用互相关或FFT相位差。窗函数选择上如果信号是单频且能整周期采样矩形窗最好否则汉宁窗或汉明窗。下面是一个加窗后峰值插值的例子# 在上一段基础上找峰值并做双线插值 peak_idx np.argmax(magnitude[:N//2]) if 0 peak_idx N//2 - 1: alpha magnitude[peak_idx - 1] beta magnitude[peak_idx] gamma magnitude[peak_idx 1] # 双线插值公式汉宁窗 delta 0.5 * (alpha - gamma) / (alpha - 2*beta gamma) true_freq (peak_idx delta) * fs / N true_mag beta * (1 - delta**2) * 2 / 1.0 # 汉宁窗增益约2这里简化 print(f频率: {true_freq:.2f} Hz, 幅值: {true_mag:.3f})这段代码的核心是delta它利用峰值左右两根谱线的幅度比来修正频率偏移。注意插值公式依赖窗函数类型汉宁窗和矩形窗的公式不同准备包里如果用了别的窗你得对应改。参数上peak_idx不能取0或N/2-1否则插值失效。如果测出来的频率跳动很大先看信号源是否稳定再看ADC是否引入了太多噪声。3.2 相位差测量的两种实现与精度对比相位差测量在电赛里常见于“两路信号相位差”题目。方法一两路信号分别做FFT取峰值处相位相减。方法二两路信号做互相关找峰值位置换算相位差。方法一实现简单但对采样同步要求高方法二抗噪好但计算量大。准备包里如果两路ADC是同步采样的比如双ADC模式或两个ADC同时触发方法一足够。如果两路是分时采样的方法二更稳。// 方法一FFT相位差伪代码基于CMSIS-DSP arm_rfft_fast_instance_f32 fft; arm_rfft_fast_init_f32(fft, 1024); float32_t fft_in1[1024], fft_out1[1024]; float32_t fft_in2[1024], fft_out2[1024]; // 填充adc_buf1和adc_buf2后 arm_rfft_fast_f32(fft, fft_in1, fft_out1, 0); arm_rfft_fast_f32(fft, fft_in2, fft_out2, 0); // 找峰值bin计算相位 int peak_bin find_peak(fft_out1, 1024); float phase1 atan2f(fft_out1[2*peak_bin1], fft_out1[2*peak_bin]); float phase2 atan2f(fft_out2[2*peak_bin1], fft_out2[2*peak_bin]); float phase_diff phase1 - phase2; // 归一化到[-pi, pi] while (phase_diff PI) phase_diff - 2*PI; while (phase_diff -PI) phase_diff 2*PI;逻辑说明CMSIS-DSP的arm_rfft_fast_f32输出是交错排列的实部和虚部fft_out[2*k]是实部fft_out[2*k1]是虚部。atan2f算出的相位是主值相减后要归一化。参数上peak_bin必须两路一致否则相位差没意义。如果两路信号幅值差很多先做归一化再算相位。常见坑是ADC采样不是同时的导致相位差里混入了时间差这时候要么改用同步采样模式要么用互相关法。注意相位差测量对采样时钟抖动很敏感。如果发现相位差跳动超过5度先检查ADC触发是否由同一个定时器发出再检查两路输入是否经过了相同的模拟前端比如同样的运放缓冲。4. 避坑与排查信号题准备包里最容易翻车的五个点4.1 现象FFT频谱峰值频率对但幅值差一倍原因ADC数据没有减去直流偏置或者FFT后没有做幅值归一化。很多准备包默认信号是双极性但实际ADC输入是单极性0~3.3V信号被抬升了1.65VFFT后直流分量很大交流幅值计算时如果没去掉直流或者窗函数增益没除就会差一倍。 解决在FFT前减去均值data data - np.mean(data)并确认窗函数增益系数。汉宁窗增益约2.0矩形窗1.0汉明窗约1.85。4.2 现象采样率设了200kHz但实际波形频率只有一半原因定时器时钟源不是你以为的那个频率。比如STM32的APB1定时器时钟可能是36MHz而不是72MHzTIM_Period算出来的触发频率就偏了。 解决用示波器测ADC触发引脚如果有输出或者用已知频率信号源校准。更直接的办法采一个已知频率方波数周期算实际采样率。4.3 现象两路信号相位差测出来一直在漂原因两路ADC不是同步采样或者模拟前端两路滤波器的群延迟不一致。 解决改用双ADC同步模式或者软件上做互相关。模拟前端尽量用相同型号运放和相同阻容值。4.4 现象滤波后信号幅值衰减严重原因FIR滤波器阶数不够或者截止频率设得太靠近通带边缘。电赛里常用FIR如果阶数只有十几阶过渡带很宽通带边缘衰减大。 解决增加阶数到64以上或者改用IIR椭圆滤波器。注意IIR相位非线性测相位差时慎用。4.5 现象串口打印数据丢包FFT结果乱跳原因串口波特率太低或DMA搬运和串口发送冲突。 解决提高波特率到921600或者用DMA发送。如果数据量太大先存到RAM再慢慢发别边采边发。5. 进阶技巧用准备包里的测试数据反推算法边界准备包里如果有test_data文件夹别只拿来跑一遍就完事。我习惯做一件事把测试数据按信噪比分类然后看算法在什么信噪比下开始失效。比如给原始数据叠加不同幅度的高斯白噪声重新跑FFT和测量记录频率误差和幅值误差。这样你能知道准备包里的算法在什么条件下可信比赛时遇到弱信号心里有数。# 用准备包里的干净数据加噪声测试算法鲁棒性 clean np.loadtxt(test_data/sine_1k.csv, delimiter,) fs 200000 for snr_db in [40, 30, 20, 10]: noise np.random.randn(len(clean)) signal_power np.mean(clean**2) noise_power signal_power / (10**(snr_db/10)) noisy clean np.sqrt(noise_power) * noise # 这里调用你的频率测量函数 freq_est measure_freq(noisy, fs) print(fSNR{snr_db}dB, 频率估计{freq_est:.2f}Hz)这段代码的逻辑是根据目标信噪比计算噪声功率叠加到干净信号上再跑你的测量函数。参数上snr_db从高到低观察频率估计什么时候开始偏离。如果准备包里的算法在20dB就崩了比赛时遇到弱信号就得换更稳的过零检测或互相关。我一般会把这个测试跑一遍把每个模块的“可信信噪比下限”记在笔记里。另一个技巧是把准备包里的FFT点数从1024改成2048或4096看频率分辨率提升多少但也要看RAM够不够。很多单片机跑4096点FFT已经吃力这时候可以考虑用抽取滤波降低采样率再FFT等效提高分辨率。这些边界条件准备包不会直接告诉你得自己试。从那以后我每次拿到信号题准备包都强制先跑一遍“干净数据→加噪→看算法失效点”的流程再决定比赛时用哪套参数。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询