整体方案与软件实现)
我们选择的是 2026 年全国大学生电子设计竞赛赛区赛 G 题——周期信号测量分析装置。我在队里主要负责软件所以这篇文章不会把设计报告简单重排一遍而是重点回答下面几个更适合软件手的问题- 500 kHz 的信号如何用 STM32F407 内部 ADC 稳定采集-1024 点 FFT 的原始频率栅格约为 1 kHz如何得到 500 Hz 粒度的测频结果- 峰峰值、真有效值和各次谐波幅值分别应该怎样计算- 为什么时域波形没有直接显示 ADC 原始点而是用频谱结果重构- 1 MHz 以上的强干扰为什么必须优先在模拟前端处理下面按照“题目分析—总体方案—软件原理—关键实现—调试经验”的顺序展开。一、先拆题G 题到底要求测什么题目的被测信号由一个基波与 1 个或 2 个谐波组成也就是每次最多有 3 个有效频率分量。这道题真正的三个矛盾第一输入幅度很小。最小只有 50 mVpp如果直接送入 3.3 V、12 位 ADC只能使用很小一部分动态范围。第二最高被测频率达到500 kHz而干扰从 1 MHz 开始二者间隔很窄。采样率必须略高于 1 MS/s同时模拟滤波器还要在 500 kHz 附近保持较小衰减、在 1 MHz 处提供足够抑制。第三1024 点 FFT 在约 1.0244 MS/s 采样率下原始频点间隔约为 1 kHz。若只读取最大整数频点不仅难以得到500 Hz 粒度非整周期采样还会带来明显的频率和幅值误差。因此这道题并不是“调用一次 FFT 然后找最大值”这么简单而是模拟前端、采样时基、频谱算法、参数计算和显示协议共同配合的系统题。二、总体方案整个系统的数据路径如下1. 输入信号经过 BNC 接口和 50 Ω 终端匹配2. 八阶 I 型切比雪夫低通滤波器通过 10500 kHz 被测分量并抑制 1 MHz 以上干扰3. 信号放大 12 倍将几十毫伏的输入扩展到 ADC 更合适的量程4. 叠加 1.65 V 直流偏置把双极性信号平移到 03.3 V5. TIM3 的更新事件触发 ADC1DMA 将 1024 个 12 位采样值搬入内存6. STM32F407 完成 FFT、精细测频、谐波检测、Vpp/Vrms 计算以及时频波形生成7. 通过 USART1 将参数和波形数据发送到串口屏。1. 为什么采用八阶模拟低通而不是只做数字滤波工程的定时器触发采样率约为对应奈奎斯特频率约为 512.2 kHz刚好覆盖题目要求的 500 kHz。但这也意味着 1 MHz 干扰在采样后可能折叠到24.4 kHz 正好落在有效频带内。一旦混叠已经发生后面的数字低通无法判断这个 24.4 kHz 分量究竟是真信号还是 1 MHz 干扰的“假象”。更严重的是强干扰还可能先让放大器或 ADC 饱和。因此需要在输入前级加上滤波器。具体衰减倍数可由最大干扰幅值和最小有效幅值算得。2. 12 倍放大和 1.65 V 抬升如何确定题目最大输入为 250 mVpp放大 12 倍后约为 3 Vpp。再以 1.65 V 为中心抬升理想范围约为 0.153.15 V既充分利用 ADC 量程又保留一定上下裕量。对于最小 50 mVpp 输入放大后也有约 0.6 Vpp明显提高了 ADC 码值利用率和小信号测量稳定性。软件最终显示原输入电压时再除以前端标称增益 12。为什么要放大假设单片机测量误差为1.2mv经过12倍放大后测量误差变为0.1mv。三、软件架构把“测量”和“显示”解耦程序不是一直无条件采样和刷新而是由串口屏命令驱动-命令 1完成多帧采样、FFT、参数计算和平均随后显示默认 3 周期波形与频谱-命令 2 或 3只切换显示 1 个或 3 个周期复用最近一次测量缓存不重新采样、不重新做正向 FFT- 标志位fft_measurement_ready标记当前是否已有一组可用于显示的有效测量结果。这样做有两个好处。一是按键切换周期数时响应很快二是参数、频谱和波形来自同一组测量结果不会因重复采样而出现三者不一致。主循环的逻辑可以概括为if (hmi_rx_ready) { if (hmi_rx_data 4) { Signal_MeasureAnalyzeAndPrintParameters(); Signal_ReconstructAndPrintWaveform(3); Signal_NormalizeAndPrintSpectrum(); } else if (hmi_rx_data 1 || hmi_rx_data 3) { Signal_ReconstructAndPrintWaveform(hmi_rx_data); } hmi_rx_ready 0; HAL_UART_Receive_IT(huart1, hmi_rx_data, 1); }串口接收中断只负责置位不在中断中执行 FFT 或大量发送避免把耗时业务塞进 ISR。四、ADC TIM DMA先把采样时基做稳本工程的关键配置如下| MCU | STM32F407VGT6主频 168 MHz || ADC | ADC1通道 0PA012 位右对齐 || ADC 采样时间 | 3 cycles || 触发源 | TIM3 TRGO上升沿触发 || TIM3 | PSC0ARR81触发频率约 1.0244 MHz || DMA | DMA2 Stream0半字传输Normal 模式 || 每帧长度 | 1024 点 || 串口 | USART1115200 bit/s8N1 |单帧采样代码很简单我是根据博主学习四臂西瓜AdcConvEnd 0U; __HAL_TIM_SET_COUNTER(htim3, 0U); HAL_ADC_Start_DMA(hadc1, (uint32_t *)adcBuff, FFT_LENGTH); HAL_TIM_Base_Start(htim3); while (!AdcConvEnd) { /* 等待 DMA 填满 1024 点 */ } HAL_TIM_Base_Stop(htim3); HAL_ADC_Stop_DMA(hadc1);DMA 完成回调只置位void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { AdcConvEnd 1U; } }这里必须让定时器触发 ADC而不是在 while 循环里软件启动转换。FFT 的频率轴完全依赖采样间隔触发抖动或实际采样率写错都会直接变成测频系统误差。理论定时器频率与程序中的标定常量只相差几个 ppm。正式调试时仍建议用已知频率信号反算实际 Fs把晶振误差一起校准进去。五、FFT 预处理去直流、选窗和幅值补偿1. 去直流模拟前端为了适配单电源 ADC给交流信号叠加了约 1.65 V 偏置。如果直接 FFT巨大的直流谱线及其泄漏会污染低频区域因此先计算一帧平均值并去除ADC 码值换算为采样端电压2. 为什么幅值 FFT 用 Flat-top精细测频又用 Hann工程中把两个目标分开处理-幅值谱分析使用 Flat-top 窗主瓣较宽但对非整周期采样时的幅值栅栏误差不敏感更适合题目重点考核的毫伏级幅值测量-小数频点精细搜索使用 Hann 窗主瓣更窄频率峰值位置更集中适合测频。这比全流程只固定使用一种窗函数更实用。窗函数没有绝对的“最好”关键是匹配测量目标。3. 窗函数相干增益补偿加窗会降低谱线幅值因此不能直接把 |FFT[k]| 当作正弦峰值。程序采用其中 Kcal0.9935 是根据实物测试得到的幅值修正系数需要我们人为校正。核心实现为magnitudeScale 2.0f / windowSum / FFT_AMPLITUDE_CALIBRATION_DIVISOR; for (int k 1; k FFT_LENGTH; k) { fft_outputbuf[k] * magnitudeScale; }最后还要除以前端放大倍数 12才是信号源端的电压。六、重点约 1 kHz 的 FFT 栅格如何得到 500 Hz 测频结果1024 点 FFT 的原始频点间隔为如果真实信号是 10.5 kHz它会落在两个整数频点之间。只取最大整数谱线结果会在约 10 kHz 或 11 kHz 跳动幅值也会受到栅栏效应影响。我们的做法是“FFT 定位 小数频点 DFT 精修”三点抛物线插值1. 先用常规 FFT 找到基波附近的整数频点 k02. 在 k0±1 bin 范围内以 0.05 bin 步长粗搜3. 在粗搜最优点附近以 0.002 bin 步长细搜4. 对细搜最优点及其左右点做三点抛物线插值5. 得到小数频点 kfrac再换算为频率粗搜步长大约对应 50 Hz细搜步长大约对应 2 Hz。这里的步长只是搜索网格不等于最终绝对精度最终误差还受采样时钟、噪声、窗函数和前端相位幅度响应影响。实测误差可在几hz。小数频点 DFT 没有为每个候选点重新调用 1024 次三角函数而是使用复数旋转因子递推最后基波结果按 500 Hz 网格处理并检查已选谐波是否与基波保持整数倍关系。需要特别说明**500 Hz 是精细测频后的结果粒度不是 1024 点裸 FFT 的频点间隔也不代表系统能无条件分开任意两个仅相差 500 Hz 的独立谱线。** 本题组委会答疑时明确各分量是基波与其整数次谐波谱线间隔远大于 500 Hz所以这种实现能够满足当前场景。此时注意使用外部晶振可提高精度七、三个参数怎么算幅值、真有效值和峰峰值1. 各频率分量幅值对基波和每个有效谐波在精确小数频点处计算 Hann 加窗 DFT。周期 Hann 窗的系数和为 N/2所以正弦峰值为再除以模拟前端增益 12得到输入端的峰值幅度单位转换为 mV 后送屏显示。2. 真有效值不能用 Vpp 简单换算只有纯正弦才满足 VrmsVpp/(2√2)。本题是基波与多个谐波的叠加信号不能继续套用这个公式。不同整数次谐波在一个基波周期内正交因此去掉直流后对应代码非常直接float amplitudeSquareSum 0.0f; for (uint32_t i 0; i measured_harmonic_count; i) { float a measured_harmonic_amplitudes_v[i]; amplitudeSquareSum a * a; } arm_sqrt_f32(0.5f * amplitudeSquareSum, measured_signal_rms_v);3. 峰峰值为什么采用频谱重构比较容易想到的方法直接对 1024 个 ADC 点求 max-min 看似最简单但在高频端每周期采样点很少采样时刻不一定正好落在真正的峰值上会产生明显的欠估计本题测试时把各分量初相位设为 0因此程序根据测得的幅值与谐波次数重构一个周期在一个基波周期内均匀计算 4096 点再取这样可以显著减小“ADC 恰好没采到峰值”的误差。如果要把这套程序扩展到任意初相位的信号就不能统一按零相位重构而应从复数频谱中保留每个分量的相位。但组委会答疑的时候说了初始信号基波和谐波之间相位差为0因此计算FFT时可不考虑相位八、波形和频谱如何送到串口屏我之前发过一篇博客将stm32 与串口屏通信HAL库陶晶驰串口屏-CSDN博客1. 时域波形先生成标准单周期再重采样为 1/3 周期参数测量结束后程序缓存最多 5 个有效频率分量。为了让屏幕稳定显示完整周期程序没有直接截取原始 ADC 数组而是构造共轭对称频谱随后调用 CMSIS-DSP IFFT得到恰好一个基波周期的 1024 点标准波形。再通过线性插值把它重采样成 532 个显示点使整张波表中恰好包含 1 个或 3 个完整周期。最后把浮点波形线性归一化到 0220使用串口屏 addt 透传模式一次发送printf(addt s1.id,0,%u\xff\xff\xff, 532U); HAL_Delay(100U); for (int i 531; i 0; i--) { printf(%c, normalized_reconstructed_wave_table[i]); } printf(\x01\xff\xff\xff);2. 频谱只显示题目关心的有效谱线题目每次最多只有基波和两个谐波因此没有必要把 512 个 FFT 点原样全发给屏幕。程序把 0510 kHz 映射到 532 个横坐标点把基波和两个最强谐波映射成稀疏谱线纵轴以当前最大分量为满量程归一化。3. 串口屏调试中的几个细节- 串口配置为115200bit/s、8N1- 普通指令后必须发送 3 个 0xFF九、调试中最值得注意的几个坑1. 信号源的“50 Ω 幅值”和“高阻幅值”不要混淆装置输入端有 50 Ω 终端。很多信号源面板显示值会随负载模式变化同一个设置在 High-Z 和 50 Ω 模式下实际端口电压可能相差一倍。做毫伏级误差标定前一定要先统一负载定义再用示波器在装置输入 BNC 处复核。2. Fs 必须使用实测或校准值频率公式是 fk·Fs/N所以采样率误差会一比一传递到频率结果。不要只把程序中的采样率写成整数 1 MHz应根据定时器配置计算理论值并用标准频率信号校准晶振和时钟误差。此时注意使用外部晶振可提高精度3. 幅值校准最好分层进行工程里使用统一的 0.9935 幅值修正系数。如果时间充足更好的办法是测出前端随频率变化的增益有必要的话我没有做这个建立分段线性查表补偿尤其要关注接近 500 kHz 的通带边缘。十、结语今年的电赛很简单大家都能做到满分最终测评比指标精度。由于我们在测试时没有想到会这样以为比的报告我让硬件队友不用再微调了。需要代码文件评论区留下QQ邮箱。