2023电赛综合测评B题国一方案:STM32测频测幅与标定

发布时间:2026/10/26 19:44:24
2023电赛综合测评B题国一方案:STM32测频测幅与标定 简介2023年全国大学生电子设计竞赛综合测评B题的国一方案配套工程面向准备电赛综合测评、希望复现同轴电缆长度与终端负载检测装置的高校学生及电子爱好者。方案基于梁山派开发板与STM32采用CubeMXCLionOpenOCD开发环境核心实现包括TDR时域反射法测长度、分压法测电阻负载、NE555振荡电路测电容负载并给出与赛题基本要求、发挥部分相对应的误差指标和调试思路。压缩包共228个文件约22.64MB以h/c源码、obj编译产物、png截图、txt说明、pdf文档及xlsx数据表为主兼顾工程复现、参数记录与结果整理。该方案已有3442人学习可作为电赛综合测评备赛的完整参考帮助读者快速理解测量原理、搭建硬件电路、对照误差要求排查问题。1. 从现场四小时倒推2023 年电赛综合测评 B 题国一方案要解决什么2023 年电赛综合测评 B 题这类封闭题真正难的不是算法是四个小时里把一块半成品板子调到指标内。这份国一方案把 STM32 主控、模拟前端调理、测频测幅、直流输出、标定流程拆成可复现的模块每一步都对应现场能拿万用表和示波器验的判据。它适合两类人正在准备综合测评、需要一个能直接照抄的硬件骨架和代码骨架的参赛队以及做课程设计、想找一份带完整设计推导的 STM32 方案模板的读者。关键词里那些范文、模板、素材说到底要的是结构不是一段能跑的代码。2. 总体方案与技术选型三条支路的预算怎么切综合测评的题面通常长这样给定一块已经焊好运放和接口的板子要求对输入信号完成测量再按测量结果输出一个可控的直流量过程中用显示和按键完成人机交互。这类题看着是测量题实际考的是通道预算——你的增益怎么分、带宽怎么留、基准怎么稳。三条支路采集、运算、输出任何一条预算给错后面代码写得再漂亮也救不回来。所以方案的第一步不是选芯片是把指标拆成每一级电路的输入输出范围。2.1 主控与资源分配为什么主频不是第一优先级现场选主控第一优先级是外设够不够、能不能一次配对主频排在后面。综合测评里常见的运算是 256 点到 1024 点的定点平方和、一次开方、几次浮点除法72 MHz 的 Cortex-M3 完全跑得动真正卡住人的是定时器数量和 ADC 与 DMA 的联动能力。我的取舍是优先选带独立 DAC、双 ADC、至少 6 个通用定时器的型号例如 STM32F407 这一档如果现场只给最小系统板比如常见的 F103C8 类板卡就把直流输出改成 PWM 二阶 RC 滤波代价是建立时间从微秒级掉到几十毫秒题面若要求输出响应快这一条会直接扣分。这个退让必须在方案阶段想清楚不能等焊完了才发现没有 DAC。外设用途配置要点冲突风险ADC1 DMA2主采样通道定时器触发规则组单通道12 位与 DAC 共用 DMA 时注意流冲突TIM3采样触发源更新事件触发 ADC200 kHz不要用 TIM2留给捕获TIM2 CH1输入捕获测频预分频 1双边沿捕获高频时溢出计数要原子读DAC1直流输出12 位右对齐缓冲输出输出阻抗高必须加跟随I2C1OLED 显示400 kHz 快速模式与按键扫描共用主循环需节流GPIO4 按键 量程切换上拉输入 外部 100 nF模拟开关控制脚别和 JTAG 复用这张表的作用是提前把谁跟谁抢资源写在纸上。综合测评最典型的翻车是 TIM2 同时被拿去触发 ADC 又拿去捕获结果测频读数永远差一截查半小时才发现是同一个定时器。2.2 模拟通道的增益与带宽预算前端要解决三件事把信号抬到 ADC 量程中点、把幅度放大到用满量程、把高频挡在 ADC 前面。三件事对应三级电路顺序不能乱。第一级是输入缓冲用运放做电压跟随输入阻抗做到兆欧级避免前级信号源内阻分压。第二级是程控增益用模拟开关切反馈电阻做 1×、10×、100× 三挡挡位由主控 GPIO 控制。第三级是抗混叠低通用二阶 Sallen-Key截止频率设在采样率的 1/2.5 左右。若采样率是 200 kHz截止就放在 80 kHz 附近这样 100 kHz 以上衰减足够同时不让有用信号被削。增益预算要倒着算ADC 参考 3.3 V单端输入摆幅按 03.3 V 算去掉上下各 0.2 V 的线性区可用约 2.9 V 峰峰。若最小待测信号是 10 mV 有效值峰值约 14 mV需要约 200 倍总增益才勉强用满这时候三挡不够得加一挡 1000×。反过来最大输入若是 3 V 有效值峰值 4.2 V 已经超过电源轨必须在第一级前加分压或钳位。很多队伍在这一步偷懒结果小信号挡位信噪比极差大信号挡位直接削顶两头都拿不到分。提示增益挡位的切换判据不要写成小于某值就换挡要留 20% 回差否则信号在阈值附近抖动时继电器或模拟开关会来回跳读数一直不稳。2.3 供电与基准把漂移关进笼子模拟通道的精度上限由基准决定不由 ADC 位数决定。12 位 ADC 在 3.3 V 下的 1 LSB 是 0.8 mV如果基准本身随温度漂 5 mV后面所有标定都白做。稳妥做法是模拟部分单独用一颗低噪声 LDO 供电基准单独用一颗 3.0 V 串联基准数字部分和屏幕走另一路两路只在电源入口单点汇合。地线处理是最容易被忽略的一环。模拟地和数字地不要大面积混铺用 0 欧电阻或磁珠在 ADC 下方单点连接ADC 的模拟输入脚旁边放 100 nF 加 10 μF去耦电容回流路径越短越好。综合测评现场通常有开关电源和继电器干扰比实验室大这一条不做测出来的有效值会带着几十毫伏的随机跳动怎么算都进不了 1%。3. 关键模块实现五个模块的代码骨架与配置要点方案落到板上就是五个模块的初始化和回调。这里给出的都是可以对照 HAL 库直接改的骨架重点在参数为什么这么取、回调里为什么这么写而不是把整份工程贴出来。每个模块后面都写清了失败时先看哪个量现场排故按这个顺序走比盲目改代码快得多。3.1 采样链定时器触发加 DMA 双缓冲采样链的写法决定了后面所有计算的稳定性。用定时器更新事件触发 ADC可以让采样间隔严格等距RMS 计算才成立用 DMA 双缓冲是为了让正在采的半块和正在算的半块互不干扰避免算到一半数据被覆盖读出来的波形看着像被啃掉一块。/* TIM3 以 200 kHz 触发 ADC1DMA 循环搬运两个 256 点缓冲区 */ #define ADC_BLOCK 256 static uint16_t adc_buf[2][ADC_BLOCK]; volatile uint8_t adc_half_ready 0; /* 0前半块 1后半块 */ void Sample_Init(void) { /* DMA 配置为循环模式、半满与全满中断都开 */ HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buf, 2 * ADC_BLOCK); HAL_TIM_Base_Start(htim3); /* 此时才开始产生触发 */ } /* 半满回调前半块采完可以拿去算 */ void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) { adc_half_ready 0; } /* 全满回调后半块采完 */ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { adc_half_ready 1; }三个参数值得展开。采样率 200 kHz 是带宽和运算量的折中按每周期采 20 点算可测上限约 10 kHz 的波形细节高于这个频率就只能靠测频支路不靠采样还原。块长 256 点对应 1.28 ms 一帧50 Hz 信号能覆盖 1/16 个周期若题面要求测 10 Hz块长要加到 1024 点否则 RMS 会随相位跳动。双缓冲的标志位用 volatile 修饰是因为它在中断里改、在主循环里读不加编译器可能把它优化进寄存器出现标志永远不更新的假死。失败时先看两个量一是用示波器量 TIM3 的触发脚有没有 200 kHz 方波没有就是定时器没起来二是把 adc_buf 里前 10 个点打到屏幕上看是不是全 0 或全 4095全 0 多半是 DMA 通道映射错全 4095 是输入悬空顶到轨。3.2 测频输入捕获与测周法的切换点测频有两条路高频用固定闸门计脉冲数测频法低频用测两个上升沿之间的时间测周法。综合测评的输入范围往往跨三个数量级只用一种方法必然有一头精度崩掉所以要在中间设一个切换点我一般取 10 kHz。/* TIM2 CH1 输入捕获一次捕获记录边沿时刻配合溢出计数还原周期 */ static uint32_t last_cap 0, overflow_cnt 0; static float freq_hz 0.0f; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { uint32_t now HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); uint32_t period; if (now last_cap) period now - last_cap; else /* 计数器回绕补一个满周期 */ period (0xFFFF - last_cap) now 1; period overflow_cnt * 65536U; /* 加上期间溢出的次数 */ overflow_cnt 0; last_cap now; if (period 0) freq_hz (float)TIM_CLK_HZ / (float)period; } /* 更新中断里累加溢出超过 10 kHz 时改用固定闸门计数 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2 freq_hz 10000.0f) overflow_cnt; }这里的关键是溢出补偿。72 MHz 定时器在 16 位计数下约 0.9 ms 就回绕一次测 100 Hz 信号时两个边沿之间要溢出十几次不补溢出计数读出来的频率会在几十赫兹和几万赫兹之间乱跳这种玄学跳变十有八九是溢出没处理。切换点定在 10 kHz 是因为低于这个值测周法的相对误差随周期线性变好高于这个值闸门计数的分辨率更划算。失败时先量输入脚有没有信号再确认捕获极性设的是上升沿还是双边沿——设成双边沿会让周期算出来正好一半这个错很像信号源频率不对实际是配置问题。3.3 幅值RMS 与峰值检波的取舍有效值有两条实现路线。模拟峰值检波加二极管补偿速度快、代码少但对非正弦波误差大数字 RMS 对任意波形都准代价是要占用运算时间并要求采样等距。综合测评的题面若明确写正弦波两种都行若写了方波或含谐波只能走数字路线。/* 去直流后做平方累加定点累加防溢出最后开方还原 */ float Calc_Rms(const uint16_t *buf, uint16_t n, int32_t offset) { uint64_t sum 0; for (uint16_t i 0; i n; i) { int32_t v (int32_t)buf[i] - offset; /* 减掉偏置得到真实摆幅 */ sum (uint64_t)(v * v); /* 累加值最大约 256*4096^264 位够用 */ } /* adc_lsb 为每 LSB 对应的电压含前端增益与基准系数 */ return sqrtf((float)sum / (float)n) * adc_lsb; }偏置 offset 不能直接取 2048要用实测值输入端短接到地时采一帧求平均得到的数才是这一块板子这一路的真实零点它包含了运放失调和基准偏差。这一步做完小信号挡位的零点误差能从几十毫伏压到几毫伏是性价比最高的一处修正。失败时先看 offset 是不是每次上电都变如果变化超过 ±30 LSB多半是模拟地和数字地混铺导致 ADC 参考脚带进了开关噪声这时候改代码没用得回去处理地去耦。3.4 直流输出DAC 加跟随器的线性区输出支路最容易犯的错是把 DAC 引脚直接当电源用。DAC 输出阻抗在几十千欧量级接任何负载都会掉压必须后接一级运放跟随。同时要注意运放的输出摆幅到不了电源轨3.3 V 供电的轨到轨运放实际可用上限约 3.2 V下限约 0.1 V标定时要避开这两段。/* 按标定直线反解码值v k * code b */ void Out_SetVoltage(float v) { float code (v - cal_b) / cal_k; if (code 0.0f) code 0.0f; if (code 4095.0f) code 4095.0f; HAL_DAC_SetValue(hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, (uint32_t)(code 0.5f)); }这里的 cal_k 和 cal_b 来自标定流程不是理论值。理论斜率是 3.3/4096实际因为基准偏差和运放增益误差斜率常常差 1%2%把理论值写死输出精度就卡在 2% 上不去。加 0.5 再取整是四舍五入不加的话码值永远偏小多挡位叠加后误差会累积成可见的偏差。3.5 人机交互按键状态机与显示节流现场调试时按键和屏幕的稳定性直接影响你判断故障的速度。用延时消抖会把主循环卡住采样帧一断RMS 就跳变正确写法是状态机加时间戳主循环里非阻塞地走。typedef enum { KEY_IDLE, KEY_DEBOUNCE, KEY_DOWN, KEY_REPEAT } key_state_t; /* 每 10 ms 调一次返回值0 无动作 1 短按 2 长按 */ uint8_t Key_Scan(uint8_t raw, uint32_t now_ms) { static key_state_t st KEY_IDLE; static uint32_t t_press 0; switch (st) { case KEY_IDLE: if (raw) { st KEY_DEBOUNCE; t_press now_ms; } break; case KEY_DEBOUNCE: if (now_ms - t_press 20) break; /* 20 ms 消抖窗口 */ st raw ? KEY_DOWN : KEY_IDLE; if (raw) return 1; /* 短按只上报一次 */ break; case KEY_DOWN: if (!raw) st KEY_IDLE; else if (now_ms - t_press 800) { st KEY_REPEAT; return 2; } break; case KEY_REPEAT: if (!raw) st KEY_IDLE; break; } return 0; }显示刷新同理OLED 整屏刷新一次约 10 ms每帧采样都刷会把主循环拖慢。我的做法是 100 ms 刷一次且只在数值变化超过显示分辨率时才写屏这样按键响应和采样节拍都不受影响。综合测评现场最怕的就是屏幕在闪、按键不灵八成是刷新没节流。4. 标定与误差补偿把能跑推到指标内能跑通和能拿分之间隔着一整套标定。综合测评的评分表上功能分和精度分是分开的功能做出来只保证不难看精度要靠标定把系统误差一项项抠掉。标定的顺序不能颠倒先修零点再修增益最后修非线性因为后一步的残差会污染前一步的判据。4.1 两点标定与分段修正最省事的做法是两点标定用一条直线拟合整个量程。做法是输入一个接近满量程下限的标准值读一次码值再输入一个接近上限的标准值再读一次解出斜率和截距。这两点要选在量程的 10% 和 90% 附近不要选在两端因为两端正好是运放和 ADC 线性最差的地方。若题面要求在整个量程内都进 1%两点直线往往不够因为运放的失调和 ADC 的积分非线性会让中间段鼓出来。这时候用分段线性把量程分四段每段存一组 k、b查表时按输入范围选段。分段数不是越多越好四段之后残差已经接近噪声水平再加段只是把噪声拟合进去。标定参数要掉电保存写进 Flash 的最后一页。typedef struct { uint32_t magic; /* 0x5A5A0001用来判断是否已写过 */ float k[4]; float b[4]; int32_t offset_ch1; /* 第一路零点单位 LSB */ uint16_t crc; } cal_t; /* 上电读取magic 不对或 CRC 不过就回退到默认值 */ void Cal_Load(cal_t *c) { memcpy(c, (void *)CAL_ADDR, sizeof(cal_t)); if (c-magic ! 0x5A5A0001u || Cal_Crc(c) ! c-crc) { for (int i 0; i 4; i) { c-k[i] 3.3f / 4096.0f; c-b[i] 0.0f; } c-offset_ch1 2048; } }加 magic 和 CRC 两个字段是为了防止标定值写了一半掉电下次上电读到半截数据整台设备的读数全部错位——这种故障在现场极难查因为程序逻辑完全正常。这个习惯我后来在所有需要掉电保存参数的项目里都保留着。4.2 零漂与温漂的现场处置综合测评从进场到测评结束通常有几个小时板子会从室温升到明显发烫。运放的失调电压随温度变化几十微伏每度的漂移在 1000 倍增益下就是几十毫伏的输出偏移小信号挡位直接崩掉。处置办法有两个层次。硬件上是选低失调低温漂的运放并在反馈网络里让正负端对地阻抗尽量匹配抵消偏置电流引起的漂移。软件上是在每一挡增益切换后、正式测量前做一次短接校零把输入通过模拟开关短接到地采一帧求平均把这个数作为该挡位的零点存起来测量时减掉。这一步只需要 2 ms但能把温漂带来的偏移压掉大半。注意校零动作会占用采样通道必须在测量间隙做不能和正式采样并行否则会把短接期间的数据混进 RMS。4.3 指标对照表每一项误差对应哪一段电路调试时最耗时间的不是改代码是不知道误差来自哪里。把指标拆成表一项项对比反复重烧程序快得多。指标项目标主要误差来源补偿手段幅值测量误差≤1%前端增益误差、基准偏差两点/分段标定频率测量误差≤0.1%晶振偏差、捕获溢出未补溢出计数 闸门时间校正直流输出误差≤1%DAC 增益、运放失调反解标定直线 输出端实测校正零点漂移≤5 mV/小时运放温漂、地线干扰分挡校零 单点接地挡位切换误差≤2%模拟开关导通电阻每挡独立标定不共用系数这张表的价值在于建立现象—电路的映射。比如测出幅值误差随输入增大而变大问题多半在前端增益而非基准误差基本恒定不随输入变那就是零点没校好。有了这个映射现场改起来是有的放矢。5. 避坑与现场排查五个最容易翻车的点综合测评的四个小时里真正让人崩盘的不是难题是几个反复出现的小坑。下面五条都是我在不同场次里亲眼见过或者自己踩过的每条按现象、原因、解决写照着对号入座能省下大量时间。第一采样值满量程乱跳屏幕上数字一直顶到 4095。原因是输入悬空或者前端运放自激ADC 采到的是运放输出的高频振荡不是信号。解决是先断开前级把 ADC 输入直接接地看读数是否稳定在 0 附近稳定说明问题在前端用示波器看运放输出有没有几百千赫的等幅振荡有的话在反馈电阻上并 1022 pF 电容做相位补偿同时检查运放电源脚去耦是否漏焊。第二测频读数在高频段成倍跳10 kHz 信号有时显示 10 kHz有时显示 5 kHz。原因是捕获极性设成了双边沿或者定时器预分频被两处代码重复设置。解决是把捕获极性统一为上升沿并在初始化函数里用注释标死每个定时器由谁配置避免两个模块各写一遍。这类改了 A 处 B 处又覆盖回去的问题在多文件工程里特别常见。第三标定完了精度还是上不去输出和输入的线性度看着还行但整体偏 1%2%。原因是标定时用的标准表和设备实际读数是同一个表等于自己跟自己比。解决办法是引入一个独立的标准源或者在标定前先用高精度万用表把设备的实际输出量一遍确认标定链路本身没问题。血泪经验是标定工具本身也需要被标定。第四OLED 一刷新ADC 读数就跟着跳几十个 LSB。原因是屏幕的 I2C 走线和模拟输入走线并排或者共用同一路电源且没有隔离。解决的物理手段是走线分开、屏幕电源单独加 100 μF软件手段是在屏幕刷新期间暂停采样触发刷完再恢复。综合测评现场没法改板软件规避是最现实的做法。第五整机上电后功能全对一接上被测量的信号源就死机或者复位。原因是输入端的共模电压超出运放的输入范围或者信号源的地和板子的地之间存在电位差电流从输入脚灌进芯片。解决是在输入端串 100 Ω 电阻加双二极管钳位到电源和地并把信号源和板子接到同一个插排上。这个坑不做保护基本躲不掉而加保护只要两分钟。6. 验收与答辩把指标讲成一条能被追问的链综合测评的验收通常分两步先测功能再测指标最后有几分钟的问答。功能部分照题面逐条过不要抢时间做一条勾一条指标部分要主动出示你的测量方法而不是等评委问。我的习惯是提前准备一张单页正面写指标表反面写每一路的测试点和测试方法评委扫一眼就知道你在测什么、怎么测的。答辩环节的问题高度集中在几个方向提前把回答骨架想好现场就不会卡壳常见追问回答骨架你的误差怎么保证的先讲误差分解再讲标定方法和标定用的基准源最后给实测残差换一块板子还能不能达到指标哪些靠器件保证、哪些靠软件补偿软件补偿的部分可以复用为什么选这个采样率和块长带宽要求、单周期点数、运算耗时三者怎么折中高频段为什么不用测频法分辨率随频率下降的定量关系给切换点的计算依据温度变了怎么办校零机制 器件选型的温漂指标 实测的漂移数据这里有个技巧所有回答都落到数字上。精度大概 1%这种话在答辩里等于没说满量程 5 V 下最大偏差 38 mV折合 0.76%主要来自前端增益的 0.5%才是有效回答。评委追问的往往不是你的方案有多先进而是你有没有真正测过。还有一点是时间管理。进场后的前 30 分钟只做一件事确认电源正常、主控能下载、屏幕能点亮这三件事不通后面所有调试都无从谈起。中间两小时做功能和初步标定最后 90 分钟留给精度和稳定性测试包括至少半小时的连续通电观察漂移。我见过太多队伍把时间全花在功能上最后半小时才发现小信号挡位误差 5%已经来不及改。强调一下记录习惯。从那以后我每次做这类封闭测评都强制在草稿纸第一页写三行当前版本改了什么、上一次实测的指标是多少、下一步准备改什么。没有这三行两个小时后你一定会忘了上一版为什么被推翻然后重新踩一遍同样的坑。希望帮到你。本文还有配套的精品资源点击获取