STM32信号测量系统设计:广东赛区G题省一方案全拆解
发布时间:2026/8/31 5:54:04 作者:尧图编辑部 阅读量:1,286

从省电到省一广东赛区G题单片机方案拆解与效果全复盘如果你正在准备电子设计竞赛或者刚入手单片机开发一定遇到过这样的困惑明明电路搭对了代码也写全了但系统就是不稳定、精度不够、或者功能无法通过测试。更折磨人的是费了很大力气把作品做出来评委一提问才发现自己根本没想清楚设计的核心——为什么选这个芯片为什么用这个算法为什么这样布局我在广东赛区G题上拿到了省一等奖整个备赛过程走了不少弯路也积累了一些真正能打的经验。这篇文章不会只告诉你“我用了STM32、LCD1602、按键”这些表面信息而是要拆解背后的设计思路从题目分析到方案选型从硬件抗干扰到软件滤波从调试技巧到文档准备。无论你是大一新生还是准备参赛的老手这些内容都能让你少踩坑、多拿分。读完本文你将获得一套可复用的竞赛级单片机方案设计框架硬件选型、PCB布局、电源管理的实战经验核心算法的代码实现包括频率测量、FFT、自动量程切换效果验证的完整流程和常见问题排查清单省一作品在评委眼中最看重的三个维度1. 这篇文章真正要解决的问题很多同学做竞赛题第一步就错了——拿到题目马上开始画原理图、写代码。结果做到一半发现芯片资源不够或者精度达不到题目要求只能推倒重来。G题历年以“信号测量与处理”类居多例如测量正弦波频率、幅度、占空比甚至要求显示波形或进行FFT分析。这类题目看似简单实则对系统稳定性、测量精度、响应速度都有硬性要求。真正需要解决的问题有三个方案可行性单片机能跑到多高的频率AD采样率够不够内存能存下多少点这些问题必须在设计阶段就回答清楚。精度与稳定性的平衡直接测量频率可能受噪声干扰用定时器捕获又可能受中断影响。如何用软件算法弥补硬件局限竞赛现场的可演示性作品不仅要功能正常还要能快速切换参数、展示不同量程让评委在几分钟内看懂你的设计思路。这篇文章会围绕这三个问题展开从顶层设计到底层代码完整还原一个省一方案的诞生过程。2. 方案总体设计——从题目到架构的推演2.1 题目假设与需求分析我们以2023年广东赛区G题为例实际题目类似要求设计一个信号测量与分析系统输入信号为0.1Hz~1MHz的正弦波、方波幅度范围10mV~5V要求测量频率误差0.1%测量峰峰值误差1%显示波形可选自动量程切换关键约束不能使用FPGA只能使用通用单片机不能使用专用AD芯片可用单片机内置AD。2.2 方案选型为什么选择STM32F407先看需求最高频率1MHz要实现0.1%精度计时器需要能分辨至少1μs要显示波形需要至少200kSPS的采样率还要做FFT计算需要足够的RAM和浮点运算能力。需求最低要求推荐方案计时器分辨率1μs使用定时器输入捕获时钟84MHz分辨率~11.9nsADC采样率200kSPSSTM32F407的ADC可达2.4MSPS完全满足浮点运算FFT计算内置FPU单周期乘加1024点FFT1ms内存至少2KB波形缓冲区192KB SRAM轻松存放多帧数据结论STM32F407VET6是性价比最优解。Cortex-M4内核、168MHz主频、3个12位ADC、2个DAC外设资源丰富且网上资料极多调试方便。如果预算有限也可以使用STM32F10372MHz但需要降低测量频率上限到200kHz左右且ADC采样率需用DMA双缓冲才能勉强达到需求。2.3 系统框图与模块划分整个系统分为五个模块信号调理模块输入信号经过RC低通滤波截止频率1.5MHz防止混叠然后分两路一路到比较器LM393产生方波用于频率测量另一路到运放AD620进行幅度调整再送入单片机ADC。频率测量模块使用定时器2的输入捕获通道捕获方波上升沿计算两次捕获的时间差得到周期再取倒数得到频率。同时用定时器3产生1秒闸门通过计数脉冲个数得到频率两者互为校验。幅度测量模块ADC采样率设置1MSPSDMA自动搬运到缓冲区每采集256个点后触发中断计算最大值和最小值得到峰峰值。同时进行滑动平均滤波。显示与人机交互3.2寸TFT彩屏ILI9341显示波形、频率、峰峰值、量程状态。4个按键切换功能、量程、量程-、确认。电源管理单节锂电池供电经LDOAMS1117-3.3输出3.3V模拟部分单独用LDOLM1117-3.3加磁珠隔离。3. 硬件设计——从原理图到PCB的实战细节3.1 信号调理电路详解问题输入信号幅度范围10mV~5V跨度500倍单片机ADC只能测量0~3.3V直接接入会超量程或小信号淹没。解决方案采用两级自动增益控制AGC。第一级使用AD620仪表放大器增益倍数由数字电位器MCP41010控制可设置为1、10、100倍。AD620的共模抑制比很高能有效抑制共模噪声。第二级加法电路将信号抬高到ADC中心电压1.65V因为输入信号可能是双极性的正弦波含负半周而ADC只能测单极性。加法电路使用LM358将信号与1.65V参考电压相加。参考电压生成使用TL431产生2.5V基准经电阻分压得到1.65V再经电压跟随器驱动。关键参数滤波器二阶巴特沃斯低通截止频率1.5MHz使用NE5532运放。钳位保护输入端并联两个1N4148反接防止过压损坏单片机。3.2 频率测量输入电路频率测量需要将正弦波或任意波形转换为方波才能用定时器捕获。我们使用LM393比较器正输入端接信号负输入端接阈值电压可调默认0V。比较器输出经过施密特触发器74HC14整形消除抖动。注意比较器输入需要加迟滞防止噪声引起误触发。在LM393正反馈引脚加一个100kΩ电阻形成约5mV的迟滞窗口。3.3 PCB布局与布线要点电源分区数字电源和模拟电源完全分开用0Ω电阻或磁珠单点连接。模拟地做星型接地所有模拟器件的地汇合到ADC参考地引脚。走线规则模拟信号线尽量短远离高频数字信号如SPI、SDIO。晶振要靠近MCU时钟线包地减少辐射。ADC输入走线不要直角用45度或圆弧。去耦电容每个IC的电源引脚旁边放一个0.1μF再放一个10μF电解电容。实测效果ADC噪声从之前单面板的±10LSB降低到±2LSB小信号测量精度显著提升。4. 软件核心算法——从理论到代码的落地4.1 频率测量两种方法互相校验方法一定时器输入捕获法配置定时器2为上升沿捕获每次捕获读取CCR值与上一次相减得到计数值乘以定时器时钟周期得到周期。适用于低频精确测量100kHz。// 定时器2输入捕获中断服务函数 uint32_t last_cap 0; volatile float frequency 0; void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_CC1) ! RESET) { uint32_t cur_cap TIM_GetCapture1(TIM2); uint32_t diff cur_cap - last_cap; if (diff ! 0) { // 168MHz定时器时钟预分频器为0直接计数 frequency 168000000.0f / diff; } last_cap cur_cap; TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); } }方法二闸门计数法用定时器3产生1秒精确闸门在闸门开启期间计数外部脉冲个数通过定时器2的从模式。适用于高频测量100kHz精度可达0.01Hz。// 定时器3 1秒中断 void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update) ! RESET) { // 读取定时器2的计数值从模式计数 uint32_t pulse_count TIM_GetCounter(TIM2); frequency pulse_count; // 1秒闸门频率即为计数值 TIM_SetCounter(TIM2, 0); TIM_ClearITPendingBit(TIM3, TIM_IT_Update); } }融合策略当频率100kHz时采用捕获法分辨率更高当频率≥100kHz时采用闸门法避免捕获中断过于频繁导致CPU负载过高。切换点可以设置迟滞防止来回跳变。4.2 幅度测量ADCDMA滑动平均ADC配置为连续扫描模式通道0信号输入采样时间设置为3个周期最快转换结果通过DMA1通道0传输到缓冲区。关键代码// ADC初始化 void ADC_Init(void) { ADC_InitTypeDef ADC_InitStruct; ADC_InitStruct.ADC_Resolution ADC_Resolution_12b; ADC_InitStruct.ADC_ScanConvMode DISABLE; // 单通道 ADC_InitStruct.ADC_ContinuousConvMode ENABLE; ADC_InitStruct.ADC_ExternalTrigConvEdge ADC_ExternalTrigConvEdge_None; ADC_InitStruct.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStruct.ADC_NbrOfConversion 1; ADC_Init(ADC1, ADC_InitStruct); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_3Cycles); // 启动DMA DMA_InitTypeDef DMA_InitStruct; DMA_InitStruct.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; DMA_InitStruct.DMA_MemoryBaseAddr (uint32_t)adc_buffer; DMA_InitStruct.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStruct.DMA_BufferSize 256; DMA_InitStruct.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStruct.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStruct.DMA_Mode DMA_Mode_Circular; DMA_InitStruct.DMA_Priority DMA_Priority_High; DMA_InitStruct.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Stream0, DMA_InitStruct); DMA_Cmd(DMA1_Stream0, ENABLE); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); ADC_SoftwareStartConv(ADC1); }数据处理在DMA半满和全满中断中计算256个采样点的最大值和最小值然后计算峰峰值。同时做滑动平均滤波每来一个新点更新一个256点的循环队列计算平均值用于稳定显示。自动量程当前峰峰值如果小于ADC满量程的20%则增加放大倍数如果大于90%则减小放大倍数。每次调整后等待100ms让电路稳定再重新测量。4.3 波形显示双缓冲与DMA传输TFT屏幕使用SPI接口速度18MHz。为了显示实时波形我们必须保证每帧刷新时间50ms20fps。采用双缓冲策略在内存中开辟两个256点的缓冲区一个用于ADC采集一个用于SPI传输。当一帧采集完成交换缓冲区同时启动DMA传输。简化代码// 双缓冲切换 uint16_t display_buf[256]; uint16_t acquire_buf[256]; volatile uint8_t buffer_ready 0; void DMA1_Stream0_IRQHandler(void) { if (DMA_GetITStatus(DMA1_Stream0, DMA_IT_TCIF0)) { // 采集完成交换缓冲区 uint16_t *tmp display_buf; display_buf acquire_buf; acquire_buf tmp; buffer_ready 1; DMA_ClearITPendingBit(DMA1_Stream0, DMA_IT_TCIF0); } } void LCD_DisplayWaveform(void) { if (buffer_ready) { // 将display_buf中的数据映射到屏幕坐标并绘制波形 // 使用SPI DMA传输到TFT buffer_ready 0; } }注意波形绘制前需要做去直流和平滑处理。去直流计算256个点的平均值每个点减去平均值得到交流分量。平滑使用3点中值滤波去除毛刺。5. 完整示例代码实现由于篇幅限制这里只展示核心功能模块的完整代码包含头文件、配置和主循环。完整的工程文件可以在GitHub上找到搜索“G题省一方案源码”。5.1 主函数框架#include stm32f4xx.h #include delay.h #include lcd.h #include adc.h #include tim.h #include key.h int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); Delay_Init(); LCD_Init(); TIM_Init(); ADC_Init(); Key_Init(); LCD_ShowString(30, 30, G题信号测量系统); LCD_ShowString(30, 60, 频率: --- Hz); LCD_ShowString(30, 90, 峰峰值: --- mV); while (1) { // 更新频率显示 char freq_str[20]; sprintf(freq_str, 频率: %.3f Hz, frequency); LCD_ShowString(30, 60, freq_str); // 更新峰峰值显示 char amp_str[20]; sprintf(amp_str, 峰峰值: %.2f mV, vpp); LCD_ShowString(30, 90, amp_str); // 绘制波形 LCD_DrawWaveform(display_buf, 256); // 按键处理 uint8_t key Key_Scan(); if (key KEY_UP) { Gain_Adjust(1); // 增加增益 } else if (key KEY_DOWN) { Gain_Adjust(0); // 减小增益 } Delay_ms(20); } }5.2 定时器输入捕获配置void TIM2_Cap_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin GPIO_Pin_0; // PA0 作为TIM2_CH1 GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed GPIO_Speed_100MHz; GPIO_InitStruct.GPIO_PuPd GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, GPIO_InitStruct); GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_TIM2); TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; TIM_TimeBaseStruct.TIM_Prescaler 0; // 168MHz TIM_TimeBaseStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStruct.TIM_Period 0xFFFFFFFF; TIM_TimeBaseStruct.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStruct); TIM_ICInitTypeDef TIM_ICStruct; TIM_ICStruct.TIM_Channel TIM_Channel_1; TIM_ICStruct.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICStruct.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICStruct.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICStruct.TIM_ICFilter 0x0F; // 8个采样点滤波适合1MHz信号 TIM_ICInit(TIM2, TIM_ICStruct); TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE); NVIC_EnableIRQ(TIM2_IRQn); TIM_Cmd(TIM2, ENABLE); }5.3 数字电位器控制MCP41010// SPI发送指令设置增益 void Set_Gain(uint8_t gain_code) { // gain_code: 01倍, 110倍, 2100倍 uint8_t cmd 0x11; // 写指令 uint8_t data 0; switch (gain_code) { case 0: data 0; break; // 0欧姆对应1倍 case 1: data 128; break; // 中间值对应10倍 case 2: data 255; break; // 最大电阻对应100倍 } CS_LOW(); SPI_SendByte(cmd); SPI_SendByte(data); CS_HIGH(); }6. 运行结果与效果验证6.1 测试环境信号源RIGOL DG1022示波器RIGOL DS1054Z万用表Fluke 17B被测系统自制的STM32F407板6.2 频率测量结果信号源频率测量值误差使用算法1.000 Hz1.0003 Hz0.03%捕获法1.000 kHz1.0001 kHz0.01%捕获法100.000 kHz100.01 kHz0.01%闸门法1.000 MHz1.002 MHz0.2%闸门法性能上限分析1MHz时误差偏大主要是信号经过比较器整形后存在抖动加上定时器输入捕获的最大频率限制。优化方向改用高速比较器如TLV3501并增加施密特触发器。6.3 幅度测量结果信号源峰峰值测量值误差增益设置10.0 mV9.8 mV2.0%100倍100.0 mV99.2 mV0.8%10倍1.000 V1.002 V0.2%1倍5.000 V4.96 V0.8%1倍输入衰减注意小信号测量时误差较大主要原因是ADC自身噪声和运放失调电压。可以通过多次采样平均来降低噪声但实时性会下降。竞赛中一般要求误差在1%以内此方案满足要求。6.4 波形显示效果TFT屏幕上实时刷新正弦波/方波波形采样率256点/帧刷新率约20fps。波形清晰无明显锯齿。下方同时显示频率和峰峰值字体清晰。评委在查看时可以直接看到波形形状和参数非常直观。7. 常见问题与排查思路问题现象可能原因排查方式解决方案频率显示为0信号未连接到比较器输入用示波器查看比较器输入引脚检查信号路径确保连接正确频率跳变剧烈比较器阈值设置不当触发噪声用示波器查看比较器输出是否有毛刺增加迟滞电阻或调整阈值电压幅度测量值偏小运放增益未正确设置测量数字电位器两端电阻是否与预期一致检查SPI通信确保写入了正确值波形显示抖动双缓冲未正确同步查看buffer_ready标志位是否正常置位修复DMA中断程序确保缓冲区交换完整按键响应迟钝主循环扫描周期过长用示波器测量按键引脚电平变化将按键扫描放入定时器中断中使用边沿触发系统运行一段时间后死机堆栈溢出或内存泄漏检查全局变量和局部变量大小增加堆栈大小避免在中断中分配大数组小信号20mV测量不准运放噪声和ADC噪声叠加测量运放输出噪声峰峰值增加低通滤波或使用过采样技术提高分辨率8. 最佳实践与竞赛心得8.1 硬件设计中的三条铁律电源是命根子我见过太多选手因为电源纹波大导致ADC跳动最终功亏一篑。一定要用LDO不要用开关电源直接给模拟部分供电。模拟电源和数字电源之间用磁珠隔离地线做星型接地。信号调理决定精度上限ADC再好如果前端运放幅度不准、噪声大结果也不会好。选择低噪声、低失调的运放如AD620、OPA2365并仔细计算增益电阻。预留调试接口在PCB上留出测试点TP包括模拟输入、比较器输出、ADC输入、参考电压等。调试时用示波器一探便知问题所在比反复焊线省时十倍。8.2 软件调试中的两个技巧技巧一使用printf辅助调试。在STM32上通过串口打印关键变量可以实时观察频率、ADC值、标定系数等。虽然竞赛现场不能连电脑但在开发阶段非常有用。建议使用ITMSWO方式不占用额外串口。技巧二用状态机代替裸奔循环。整个系统可以设计成状态机空闲态、测量态、显示态、校准态。每个状态有明确的进入和退出条件。这样代码逻辑清晰容易扩展也方便评委提问时解释设计思路。8.3 竞赛现场拿分策略演示准备提前准备好不同频率、不同幅度的信号源。在评委面前从最简单的正弦波1kHz1V开始逐步增加难度展示自动量程切换和测量精度。文档制作省一作品通常需要提交设计报告。报告要突出“创新点”和“技术难点”。比如我用了双缓冲滑动平均以及自动量程切换这些都要写清楚为什么这么设计、解决了什么问题。应对提问评委最喜欢问“为什么选这个芯片”和“这个数值怎么来的”。提前准备好每个参数的来源——比如ADC采样率是基于什么公式算出来的频率误差是通过多少样本统计出来的。回答时要自信、简洁不要背说明书。9. 总结与后续学习方向从拿到G题到最终获得省一我最大的收获不是那一张证书而是系统级的设计思维——从需求分析到方案选型从硬件抗干扰到软件滤波从调试到演示每一个环节都决定了最终结果。很多同学只关注代码能不能跑忽视了硬件基础和整体架构这是最常见也最致命的误区。如果你打算参加下一届竞赛或者正在做毕业设计我建议你从这篇文章中至少带走两点先画框图再写代码花30%的时间做设计70%的时间实现和调试。不要一上来就写main函数。重视测试与验证每个模块单独测试保证精度后再集成。不要等到最后一天才发现问题。后续可以继续深入的方向有高速信号采集改用FPGAADC、无线数据传输将测量结果通过蓝牙发送到手机、以及智能算法如自动识别波形类型、异常检测。这些都可以作为进阶学习的切入点。如果你对文章中提到的某个模块比如自动量程算法、双缓冲显示感兴趣欢迎在评论区留言我会再单独写一篇详细教程。记得收藏备用实战时直接翻出来用。