2026电赛H题全复盘:信号链调测与队长生存指南
2026/9/2 3:40:36 网站建设 项目流程

电赛结束后的第一个晚上,我把清单最后一栏“提交物”打上勾,盯着桌面上那堆焊锡丝、杜邦线和示波器探头,忽然意识到,整个四天三夜里最累的不是哪一个模块,而是“队长”这个角色。2026电赛H题完赛,技术上的成就感确实很足,但“干队长伤身体”这句话,也是真话。

这一篇文章不打算写成“模板题解”或者“获奖感言”。我想从带队完赛的角度,把 H 题从备赛、方案设计、代码实现、调测排错,到最后封箱的全过程拆开,重点说清楚三件事:H 题到底在考什么;哪几个环节最容易让队伍翻车;以及怎么把任务拆到队员手上,既把题做完,又能让队长少受点罪。如果你正在备赛,或者刚打完一场硬仗准备复盘,这篇文章可以直接收藏。

1. H题在比什么:把“信号链”三个字先刻进脑子里

H 题在电赛里通常定位为系统类题目,常见组合是信号测量、信号发生、数据采集与显示控制。这类题和纯硬件题、纯软件题都不一样,它的核心特征是“软硬耦合”:模拟前端出问题,数字端看到的数据全是错的;数字端时序出了问题,模拟前端做得再干净也白搭。很多队伍第一天信心满满,第二天开始怀疑人生,原因就在这。

从架构上看,H 题绕不开下面这条链路:

输入信号调理 → ADC / 比较器 → 主控(MCU/DSP) → 计算处理 → 输出显示 / 控制信号。

这条链路上的每一个环节都有自己的指标。信号调理决定输入端能不能把真实信号“翻译”成数字逻辑可识别的范围;ADC 决定采样精度和速度;主控决定算法能不能跑得过来;显示和控制决定最终的人机体验。难点在于,这些环节不是孤立的。

我见过不少队伍把大量时间花在算法仿真上,结果上电后一测,发现 ADC 采集到的波形有台阶、有过冲、有直流偏置,算法再漂亮也救不回来。反过来,也有的队伍硬件调得很扎实,但代码没有做临界条件保护,数据一跳变就死机。

所以 H 题真正的胜负手,不是某一门科目学得有多深,而是哪支队伍能在四天三夜里“少犯错”。哪支队伍能保证每个模块单独可测、每个接口都有明确约定、每个指标都有验证方法,哪支队伍就能稳定完赛。

2. 从题目到方案:先把模块拆开,再想集成

拿到题目后的前两个小时,是最影响后续节奏的。很多队伍上来就开焊、开写代码,这是一个非常常见的误区。更稳妥的顺序是先做需求拆解,再确定整体方案。

2.1 把指标转换成可验证的模块需求

不管当年的 H 题具体是什么,你都可以从题目描述中提取出这几类信息:

  • 输入信号的类型、频率范围、幅度范围;
  • 需要实现的功能指标,例如测量精度、分辨率、响应时间;
  • 显示、交互、数据记录等附加要求;
  • 供电方式和场地限制。

把这些信息写在一张共享表格里,然后给每个模块定义“输入是什么、输出是什么、可测指标是什么”。比如信号调理模块的输入是毫伏级正弦波,输出是 0~3.3V 的摆幅,那验收方式就是用信号发生器灌入固定幅值,再用示波器测量输出是否在预期范围内。

2.2 主控选型:用熟不用新

主控是整个系统的核心。STM32F103 和 STM32F407 是电赛里用得最多的选择,资源资料多、库函数成熟、团队里一般也有人用过。如果手头的题目需要更多浮点运算或 FFT,F407 或 H7 会更从容。这里有一条很实用的建议:比赛期间不要轻易换主控平台。就算换的芯片性能更好,重新迁移外设驱动、调试启动配置,也要消耗大半天时间,这笔账不划算。

2.3 信号发生与测量方案:先保证“能测准”,再追求“能测快”

测量类 H 题的常见难点是频率测量、幅值测量、波形识别。频率测量既可以用定时器输入捕获,也可以用外部中断计数;幅值测量既可以通过 ADC 采样计算,也可以通过峰值检波电路配合 ADC 读取。比较推荐的方案是“模拟前端 + 主控内置 ADC + 定时器捕获”的组合,先用运放把信号调理到合适范围,再用定时器捕获频率,用 ADC 采集幅值。这套方案物料成本低、实现周期短、排查起来也直观。

如果题目涉及信号发生,比如需要输出正弦波或任意波形,常见实现有 DDS 芯片(如 AD9833)或者 MCU + DAC 查表输出。DDS 芯片的优点是频率稳定、相位连续,缺点是初始化和配置要多写一点代码;MCU + DAC 的方案更灵活,但输出频率上限受 DAC 速度和查询表大小限制。建议比赛前把两种方案的驱动都写好并验证过,现场再根据题目选择。

3. 环境准备:工具、物料、开发环境一次配齐

电赛最怕的不是题目难,而是赛前工具没配齐,比赛时花几个小时找一根杜邦线。这里列一份比较实用的清单,可以照着检查。

3.1 软件工具链

  • STM32CubeMX:用于生成初始化代码,减少低级寄存器配置错误。
  • Keil MDK 或 IAR:主流的嵌入式编译环境。Keil 的工程配置相对简单,IAR 的代码优化效果好一些,选一个团队最熟的即可。
  • 串口调试助手或 Vofa、SerialPlot:用于观察输出数据和波形。
  • 立创EDA 或嘉立创专业版:如果赛前需要画 PCB,这是最常用的工具。
  • Git:强烈建议搭建一个本地 Git 仓库,不需要远程服务器,本地分支就够用,至少能避免“改坏代码没法回退”的悲剧。

3.2 硬件工具与耗材

万用表、示波器、函数信号发生器、直流稳压电源、逻辑分析仪,这五样是电赛调试的必备设备。示波器要注意带宽,至少要能覆盖题目信号频率的 5 倍以上。

元器件方面,常用的运放有 OPA2277、LMV358、TL072 等;ADC 如果主控内置不够用,可以考虑外部 ADC,如 ADS1256 或 AD7606;DDS 芯片准备 AD9833 即可;显示部分可以用 OLED 或串口屏,OLED 占用引脚少,串口屏交互方便。所有主控板建议准备两块,一块用来调代码,一块备用,防止现场烧坏。

3.3 提前写好三份“保底驱动”

赛前最重要的一件事,是把三份基础驱动提前写好并验证过,分别是:ADC 多通道采集驱动、定时器输入捕获驱动、UART 打印驱动。这三份驱动是几乎所有 H 题都会用到的底层模块。提前写好,意味着比赛开始后的前半天不用从零开始,而是可以直接进入应用逻辑开发。

4. 软件架构与关键代码实现

嵌入式软件最大的坑,是代码写到后期堆成一团,调一个问题带动三个问题。为了避免这种情况,建议从一开始就按照“底层驱动—模块接口—应用逻辑”三层来组织。

4.1 文件结构与模块划分

一个稳妥的工程目录结构大概是这样的:

Project ├─ Core │ ├─ Inc │ └─ Src ├─ Drivers │ ├─ CMSIS │ └─ STM32F4xx_HAL_Driver ├─ App │ ├─ app_main.c // 应用主循环 │ ├─ app_main.h │ ├─ adc_task.c // ADC 采集任务 │ ├─ freq_task.c // 频率测量任务 │ └─ display_task.c // 显示刷新任务 └─ Bsp ├─ uart_debug.c └─ led_indicator.c

BSP 层放板级外设驱动,App 层放每个功能模块,Core 是 STM32 的标准库文件。这样划分的好处是,每一个人负责的代码文件相互独立,合在一起不至于冲突。

4.2 ADC 采集模块:用 DMA,别用轮询

如果使用 STM32 HAL 库,ADC 采样建议优先采用 DMA 方式。DMA 可以自动把 ADC 转换结果搬到内存里,主控不用在等待转换上浪费时间。下面是一个基于 STM32F407 的单通道 ADC DMA 采集示例,可以直接作为工程模板。

// 文件路径:Bsp/adc_task.c #include "adc_task.h" #include "main.h" static ADC_HandleTypeDef hadc1; static DMA_HandleTypeDef hdma_adc1; static volatile uint32_t adc_value = 0; void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_DMA2_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_1; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); hdma_adc1.Instance = DMA2_Stream0; hdma_adc1.Init.Channel = DMA_CHANNEL_0; hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; hdma_adc1.Init.MemInc = DMA_MINC_DISABLE; hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; hdma_adc1.Init.Mode = DMA_CIRCULAR; HAL_DMA_Init(&hdma_adc1); __HAL_LINKDMA(hadc, DMA_Handle, hdma_adc1); } void Adc_Init(void) { hadc1.Instance = ADC1; hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; hadc1.Init.Resolution = ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode = DISABLE; hadc1.Init.ContinuousConvMode = ENABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; HAL_ADC_Init(&hadc1); ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_1; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES; HAL_ADC_ConfigChannel(&hadc1, &sConfig); HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&adc_value, 1); } uint16_t Adc_Get_Value(void) { return (uint16_t)(adc_value & 0xFFFF); }

这段代码的关键逻辑是:初始化 ADC1,配置 PA1 为模拟输入,然后通过 DMA2 的 Stream0 循环搬运转换结果。由于 DMA 采用的是 circular 模式,ADC 会不停采样,无需 CPU 介入。上层逻辑只需要调用Adc_Get_Value()读取最新的值。

注意,如果你的芯片是 STM32F103 或其它系列,DMA 通道和 ADC 通道映射可能不一样,需要查阅对应数据手册。最稳妥的方式是先用 CubeMX 生成一个 DMA ADC 工程,再把上面的逻辑对照着移植过去。

4.3 频率测量模块:用定时器输入捕获

频率测量的思路很多,门槛最低、稳定性最好的是定时器输入捕获。以 STM32F407 为例,TIM2 的 CH1 可以配置为上升沿捕获,每次捕获到一个上升沿,就记录当前计数器的值。两次捕获的差值,就是信号的周期,周期取倒数就得到频率。

// 文件路径:Bsp/freq_task.c #include "freq_task.h" #include "main.h" static TIM_HandleTypeDef htim2; static volatile uint32_t last_capture = 0; static volatile uint32_t period_ticks = 0; void Freq_Timer_Init(void) { __HAL_RCC_TIM2_CLK_ENABLE(); htim2.Instance = TIM2; htim2.Init.Prescaler = 84 - 1; // 84MHz / 84 = 1MHz,即 1us 计数一次 htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 0xFFFFFFFF; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(&htim2); TIM_IC_InitTypeDef sConfigIC = {0}; sConfigIC.Channel = TIM_CHANNEL_1; sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; sConfigIC.ICFilter = 0x0F; HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); } void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { uint32_t now = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1); period_ticks = now - last_capture; last_capture = now; } } float Freq_Get_Hz(void) { if (period_ticks == 0) { return 0.0f; } return 1000000.0f / (float)period_ticks; }

关于这段代码,有几个细节需要强调。Prescaler的值决定了计数器的分辨率,84MHz 的系统时钟经过 84 分频后,计数器每 1 微秒加一次。这样测量 1kHz 信号时,周期大约是 1000 个 tick,精度足够。另一个容易被忽略的是输入捕获的滤波配置,如果信号有明显抖动,在ICFilter里设置一个较大的值可以滤掉毛刺,但要权衡延时,频率很高时需要适当调低。

4.4 调度器与显示刷新:别在主循环里死等

编写应用代码时,最忌讳的是在一个 while 循环里用HAL_Delay()做同步等待。比如读取一次 ADC 等 10ms,刷一次屏又等 100ms,一旦某个环节耗时过长,整个系统就会卡住。推荐用“非阻塞任务调度”的思路,把周期性的任务拆开。

// 文件路径:App/app_main.c #include "app_main.h" #define TASK_ADC_PERIOD_MS 5 #define TASK_FREQ_PERIOD_MS 100 #define TASK_DISPLAY_PERIOD_MS 200 void App_Main_Loop(void) { uint32_t last_adc_time = 0; uint32_t last_freq_time = 0; uint32_t last_display_time = 0; while (1) { uint32_t now = HAL_GetTick(); if (now - last_adc_time >= TASK_ADC_PERIOD_MS) { last_adc_time = now; uint16_t adc_val = Adc_Get_Value(); Process_Adc_Data(adc_val); } if (now - last_freq_time >= TASK_FREQ_PERIOD_MS) { last_freq_time = now; float freq = Freq_Get_Hz(); Process_Freq_Data(freq); } if (now - last_display_time >= TASK_DISPLAY_PERIOD_MS) { last_display_time = now; Display_Refresh(); } // 让出 CPU,降低功耗 __WFI(); } }

这是典型的“时间片轮询”思路。三个任务各自的周期不同,互不阻塞。HAL_GetTick()返回系统启动以来的毫秒数,只要注意溢出问题即可。实际上 32 位可以扛 49 天,对一场比赛来说完全够用。

5. 完整示例:让一条信号测量链路跑起来

有了前面几个模块,我们就可以把它们串成一条完整链路。以一个典型的 H 题测量场景为例,系统要做的事情是:检测输入正弦波的频率和幅值,并把结果显示在 OLED 或串口屏上。

主循环的逻辑可以分为三步:周期读取 ADC 值,计算幅值;读取定时器捕获结果,计算频率;把所有数据格式化后送到屏幕。

为了方便调试,建议先做一步串口打印,而不是直接驱动显示。串口打印可以看到完整数据流,定位问题的速度比看屏幕快得多。把 printf 重定向到 UART,是嵌入式调试里最实用的操作。

// 文件路径:Bsp/uart_debug.c #include "uart_debug.h" #include "stdio.h" #include "main.h" extern UART_HandleTypeDef huart1; int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 50); return ch; } void Debug_Print_Measure(float freq, float volt) { printf("freq=%.2fHz, vpp=%.3fV\r\n", freq, volt); }

打印出来的数据,既可以看到频率是否稳定,也可以看到幅值是否已经处理成有效值。建议在主控和显示模块联调之前,先在串口里看到正确的数据。这一步能省下大量排查显示驱动的时间。

当你把这些模块都运行起来后,验证成功的标准很简单:用函数信号发生器输入一个已知的信号,比如 1kHz、1Vpp 的正弦波,串口打印出来的频率误差在 1% 以内,幅值误差在允许范围内,就说明整条链路是通的。接下来再优化精度,比一开始直接追求高精度要容易得多。

6. 调测顺序与效果判断:先电源,再时钟,最后才是算法

比赛期间,很多队伍在“调测”这个环节浪费了大量时间。一上来就测算法,结果算法每次输出都不对,绕了一圈才发现是某个模块的供电有问题。

这里推荐一个固定调测顺序,每一步都有明确的退出标准。

6.1 第一步:电源与硬件静态检查

上电前先测电源轨,确认电压没有明显跌落、电流没有异常偏大。用手摸一下主控、运放是否异常发烫。很多莫名其妙的问题,最后都出在电源纹波大或地线接触不良上。

6.2 第二步:时钟与基本外设

先用示波器确认主控晶振起振,然后看 GPIO 是否能正常翻转。如果这一步不通过,后面所有测试都没有意义。

6.3 第三步:模拟前端静态值

给一级运放输入一个固定直流电压,用示波器量运放输出是否符合预期。如果直流偏置都不对,交流信号进来之后只会更乱。

6.4 第四步:数字端数据链路

用信号发生器输出一个稳定的方波或正弦波,然后用串口打印 ADC 数据或频率值,确认主控读到的数据跟示波器观察到的波形一致。这一步是“软硬验证”的关键节点。

6.5 第五步:算法与显示

前四步都通过后,再进入算法调优和显示联调。此时你已经有把握:数据从输入到 MCU 是正确的,剩下的问题只会在算法逻辑或显示驱动里,排查范围一下子缩小了很多。

调测阶段的另一个建议是:每调通一个模块,就在共享表格里打一个勾。人的记忆在高压状态下不可靠,表格比脑子靠谱。

7. 常见问题与排查思路

四天三夜不可能不遇到问题,真正拉开差距的是遇到问题后的排查速度。下面这张表格,总结了 H 题调试中最常见的几类问题和处理思路。

问题现象可能原因排查方式解决方案
串口打印乱码晶振频率与代码配置不一致查看系统时钟配置和串口波特率用 CubeMX 重新配置系统时钟和 UART
ADC 读数始终偏高或偏低参考电压不稳或前端偏置错误用万用表量参考电压,对照原理图检查 REF 引脚,增加退耦电容
频率测量抖动大输入信号边沿抖动或滤波配置不当示波器观察波形毛刺调整输入捕获的 ICFilter,增加施密特整形
上电后程序不运行启动文件缺失或 BOOT 引脚错误检查下载器连接和 BOOT 状态重新配置启动文件,检查 BOOT0 电平
OLED 显示花屏I2C/SPI 时序不正确或电平不匹配用逻辑分析仪抓取通信波形降低通信速率,检查上拉电阻
部分数据偶尔丢失中断优先级冲突检查中断优先级分组为关键外设设置更高优先级
一插信号线就死机输入过压或静电损坏 IO测量输入端电压增加钳位二极管和限流电阻

这里最想强调的一行是“参考电压”。ADC 测量的精度,很大程度上取决于参考电压是否稳定。如果主控的 VREF 直接接到了电源,而电源本身纹波很大,那么 ADC 读数跳变几乎是必然的。解决方法是加一个高质量的参考电压源芯片,或者在 VREF 引脚附近并联一个低 ESR 的陶瓷电容。很多“明明硬件没问题,代码也没问题,但就是测不准”的情况,最后都栽在参考电压上。

8. 干队长伤身体:任务拆解、节奏管理与防翻车原则

说了这么多技术内容,终于要回到标题那句话:干队长伤身体。这句话并不是玩笑。队长在比赛里承担的角色,往往是一个“人形集成调试器”:队员遇到问题找他,硬件和软件对接找他,文档封箱还是找他,所有的信息都汇到一个人身上。如果这个状态持续四天,身体不出问题才怪。

但我观察到一个规律:优秀队长和普通队长之间的差别,并不在于谁更能熬夜,而在于谁更能把任务拆出去。

8.1 把“队长”拆成三个角色

  • 硬件负责人:负责模拟前端、电源、传感器接口,以及所有硬件指标的验证。
  • 软件负责人:负责主控驱动、信号处理算法、显示和通信。
  • 文档与测试负责人:负责写测试记录、保存每一次实验数据、整理封箱材料。

队长可以兼任其中一个角色,但绝不应该三头都管。核心原则是:每个队员手里都必须有一个“可以独立验证”的子任务。有了子任务,队员就有了明确的目标和成就感,队长就不需要处理所有细节。

8.2 用“里程碑”代替“全程盯守”

不要把比赛当成连续 96 小时的无差别冲刺。更合理的时间安排是:

  • 第一天中午前:完成需求拆解和整体方案;
  • 第一天晚上:每个模块都开始有可以跑通的最小代码;
  • 第二天晚上:完成信号链路打通,即“输入信号 → 串口打印”已经正确;
  • 第三天晚上:完成算法优化、显示和交互;
  • 最后一天上午:只做测试、记录、整理文档,不新增功能。

如果第四天还在改核心算法,说明前三天某个环节出了问题。这时候队长的第一反应不是继续加班,而是立即叫停新增需求,保住已经实现的成果。比赛比的是“完赛”,不是“炫技”,在最后半天改一个没有充分验证的代码,翻车概率远大于拿分概率。

8.3 给队长的四条“保命”建议

第一,每四小时强制休息十五分钟,别以为连续熬夜能换来更多产出,连续工作到三十小时之后,人的有效思考能力会明显下降。第二,每一次修改代码前先备份,不管是 Git 还是复制一份文件,都行。第三,遇到连续半小时解决不了的问题,立即换一个思路,比如把队员叫过来一起看,很多时候三个人盯着屏幕比一个人死磕有用。第四,睡前把明天要做的三件事写下来,哪怕只写一句话都行,第二天醒来就不会因为“不知道从哪开始”而焦虑。

9. 最后一夜该做什么:文档、封箱与现场应变

最后一天的状态,往往决定你能拿什么奖。很多队伍在最后一天还在焊板子、改代码,最后交上去的文档却只写了半页,这是非常可惜的事情。电赛的评审不仅看功能,还看文档和现场演示。功能一样的队伍,文档更完整、演示更流畅的那一支,分数往往会更高。

封箱前建议制作一个“自查清单”,至少包含以下几项:

  • 系统电源是否正常,多次重启后是否能复现同样的功能;
  • 所有关键测试数据是否已经截图保存;
  • 代码是否已经烧录到板载 Flash,且断电重启后可以直接运行;
  • 文档中是否描述了系统架构、模块划分、测试方法和测试结果;
  • 备用板和工具是否已经分类装好,方便现场快速取用。

现场演示环节,最容易翻车的不是功能实现,而是“演示的时候没反应”。原因多半是接线松动、电源没插紧,或者程序还停留在调试模式。建议在正式封箱之前,完整走一遍“断电 → 重新上电 → 自动运行 → 展示结果”的全流程。如果断电重启后系统能直接工作,现场基本就稳了。

10. 总结与后续学习方向

打完 2026 电赛 H 题,最值得记住的经验其实不是某个具体电路,而是一套“如何完成复杂系统”的方法:先拆需求,再定方案,然后让每个模块单独可测,最后按固定顺序完成系统集成。这个流程不只适用于电赛,以后做毕业设计、实习项目、甚至工作里的硬件开发,都是同一个套路。

如果还想继续深入,建议重点关注三个方向。第一,嵌入式实时系统的设计和调试方法,尤其是中断管理、DMA 和任务调度;第二,信号处理基础,包括有限冲激响应滤波、快速傅里叶变换,以及如何用 DSP 库完成运算;第三,模拟前端的设计能力,因为很多系统级问题最终都出在信号调理和抗干扰上。

下次比赛如果还要当队长,记得备好眼药水、耳塞和一个“强制休息提醒”的定时器。干队长确实伤身体,但把方法理顺了,至少能把伤害控制在一个可持续的范围内。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询