☰
2019电赛D题幅频特性与故障检测代码工程深度拆解
2026/10/19 2:42:14 网站建设 项目流程

简介:2019年全国电子设计大赛D题国家二等奖完整代码包,面向参加电子设计竞赛的高校学生与嵌入式开发者,提供经过反复迭代的STM32工程源码与硬件接口实现。包内共156个文件,以C语言源码(37个.c)和头文件(39个.h)为主,辅以目标文件、链接脚本及Keil工程配置,可直接导入常见IDE查看调试;整体仅2.98MB,目录结构清晰。代码覆盖ADC采集、定时器、I2C、Flash等外设驱动,并配有构建脚本与调试辅助文件,可帮助读者理解D题从底层驱动到上层算法、异常处理与代码规范的完整实现思路。该资源已有5347人学习,适合正在备赛、希望借鉴获奖项目架构与代码风格的学生参考。

1. 2019年电赛D题:一个能直接跑起来的幅频特性与故障检测完整工程

电设大赛D题这种带硬件闭环测量的题目,代码包最怕两件事:一是只能看不能跑的图纸堆,二是能跑但完全看不懂流程的玄学工程。这份2019年全国电子设计大赛D题国家二等奖代码包,属于第二种里的“说明书齐全”型:拿到手解压后,工程目录、原理图、测量链路说明、故障判定表是成套的。它解决的是“被测放大电路到底怎么测才稳”这个核心问题——输入电阻、输出电阻、增益、上下限截止频率外加七类故障检测,默认配置下扫频范围从10Hz覆盖到1MHz。适合两类人:正在备赛的电赛选手,和需要做放大器特性自动测量的嵌入式开发者。我最推荐的不是它的算法多高深,而是主流程足够直白,改一改就能当模板用。

2. 从D题赛题到系统框架:测量指标、硬件链路与阻抗计算

2.1 被测对象与测量指标:先锁死“测什么”

2019年D题给出的被测对象是一个阻容耦合共射极放大电路,输入端和输出端都有耦合电容,信号通路里还串联了射极旁路电容。这类电路的特性测量有五个硬指标:输入电阻Ri、输出电阻Ro、电压增益Au、上限截止频率fH、下限截止频率fL。先把这五个量在物理上搞清楚,后面写代码才有依据。

输入电阻是从放大器输入端看进去的等效电阻,测量时要避免改变放大器工作点;输出电阻是放大器本身的内阻,测量时必须带负载再算;电压增益是中频段输出电压与输入电压之比,频率太高或太低都会掉;截止频率是增益下降到中频增益0.707倍处的频率点,也就是-3dB点。误差要求一般在5%以内,频率值需要以Hz为单位精确显示。以下是我在拆这个包时整理的典型量程和测量手段:

指标物理含义典型量程测量方法
输入电阻Ri输入端等效电阻1kΩ~50kΩ串联已知电阻分压法
输出电阻Ro输出端等效内阻100Ω~5kΩ空载/带载两次电压法
电压增益Au中频段电压放大倍数1~100倍中频正弦注入+有效值比值
上限截止频率fH高频-3dB点1kHz~500kHz对数扫频+线性插值
下限截止频率fL低频-3dB点10Hz~1kHz对数扫频+线性插值

注意表里的量程不是题目里的固定值,而是这套代码包默认的“预置被测板”参数。拿到包以后,这些量程都集中在配置头文件里改,不需要动算法逻辑。

2.2 硬件链路选型:DDS正弦源+自动增益调理+真有效值检波

整个测量链路的顺序是:主控MCU控制DDS生成正弦波,经过幅度调理电路后送入被测放大器输入端;放大器输出经过缓冲和衰减网络,再进真有效值检波器;检波器输出的直流电平由ADC采样;最后在软件里换算成电压、增益和频率点。

为什么用DDS而不是手动信号发生器?因为自动扫频需要频率能按设定步进精确变化,DDS芯片能在微秒级切换频率,且相位连续。常见方案是用AD9833这类DDS模块,SPI接口写入频率字即可;也有直接用MCU内置DAC加DMA输出正弦表的做法,优点是省一颗芯片,缺点是波形质量和频率上限受DAC转换速率限制。我拆的这份代码走的是外挂DDS方案,扫频到1MHz时波形仍然干净,这对高频增益测量很关键。

检波部分是这个系统的核心。被测放大电路输出通常不是理想正弦波,会有削波和噪声,用峰值检波或平均值检波都会引入波形误差。真有效值检波器(比如AD637这类芯片)计算的是信号的热等效值,对非正弦波同样有效。如果选软件RMS方案,则需要ADC采样率足够高,并且采样窗口必须覆盖整数个信号周期,否则频谱泄漏直接让读数跳动。硬件检波芯片的缺点就是稳定时间较长,一般需要几百毫秒才能让输出稳定,代码里每次切换频率后要加延时,这个延时参数直接决定一次完整扫频要花几秒。

2.3 阻抗测量两种方法与计算公式:分压法和负载切换法

输入电阻不能直接拿万用表电阻档去量,因为那样会给电路额外并联一个测量阻抗,改变工作点。常用做法是串联已知电阻分压法:在信号源与被测电路输入端之间串入一个已知阻值Rs,用DDS输出固定中频正弦波,同时测Rs两端的电压US和被测电路输入端的电压Ui。由于串联回路中电流相同,输入电阻Ri=Ui/(US-Ui)×Rs。这段换算在代码里是一个单独函数,后面会详细说。

分压电阻Rs的选取有讲究:Rs太小,US和Ui非常接近,相减后误差被放大;Rs太大,信号衰减严重,放大器输出信噪比变差。我一般会先粗测一次,根据结果自动切换Rs档位,让Ui落在0.3US到0.7US之间,这样分压比例测量误差最小。输出电阻的测量则用负载切换法:先空载测量输出电压Uo,再接入已知负载RL测带载电压UoL,则Ro=(Uo/UoL-1)×RL。这个公式的前提是放大器的输出阻抗在测试频率下近似纯电阻,实际中频段是满足的。两个测量点都要求继电器或模拟开关切换测量路径,所以继电器导通电阻必须记入校准值,不然实测几百毫欧的误差会在Ro较小时直接吃掉几个百分点的精度。

3. 代码工程拆解:主循环状态机、扫频算法与故障定位逻辑

3.1 工程目录与模块划分:先看文件再找函数

拿到代码包后别急着编译,先把目录结构过一遍。这套工程的分层是“驱动-中间层-应用”三层,打开就能看出每个模块的边界:

目录/文件模块职责关键接口
Docs/原理图、测量方案说明、校准记录模板无
App/main.c主循环状态机、菜单响应main()
Middle/scan.c扫频测量、截止频率计算measure_fH(), measure_fL()
Middle/meter.c输入/输出阻抗测量、增益测量measure_ri(), measure_ro()
Middle/fault.c直流工作点与故障判定detect_fault()
Driver/dds.cDDS芯片SPI驱动、频率设置dds_set_freq()
Driver/adc.cADC采样、软件RMS计算adc_sample(), calc_rms()
Driver/oled.c, key.c显示与按键oled_show(), key_press()

这个分层最大的好处是替换硬件时不用动测量逻辑。比如想把DDS换成DAC方案,只需要改Driver/dds.c,Middle层完全不需要动。我自己用这个包做二次开发时,第一件事就是把Middle层看了一遍,因为比赛代码的精华都在这一层,底层驱动反而是比较标准的写法。

3.2 主循环状态机:菜单、测量、显示三态切换

主循环用的是经典状态机结构。代码里没有用操作系统,因为整套测量流程是顺序执行的,状态机的可读性和可控性比裸机while+标志位好很多,也方便在调试时只跑某一段逻辑。

/* App/main.c * 状态机主循环:菜单 -> 测量 -> 结果 -> 菜单 */ enum { ST_MENU = 0, ST_MEASURE, ST_FAULT, ST_RESULT }; int main(void) { hw_init(); /* 初始化ADC、DDS、OLED、按键 */ uint8_t state = ST_MENU; while (1) { switch (state) { case ST_MENU: if (key_press(KEY_OK)) state = ST_MEASURE; break; case ST_MEASURE: measure_all(); /* 完整测量Ri/Ro/Au/fH/fL */ state = ST_RESULT; break; case ST_FAULT: detect_fault(); state = ST_RESULT; break; case ST_RESULT: show_result(); if (key_press(KEY_BACK)) state = ST_MENU; break; default: state = ST_MENU; break; } } }

逻辑说明:状态切换只在按键按下时发生,测量过程中不响应按键,避免中断测量流程导致时序错乱。measure_all内部是阻塞执行的,一次完整测量约耗时2~3秒,这个时间开销来自检波稳定延时和扫频点数,不是代码效率问题。

参数说明:KEY_OK和KEY_BACK在按键驱动里映射到具体GPIO;STATE_FAULT状态在菜单里通过另一个按键进入,用于单独执行故障检测。实际调试时我会临时修改state变量初始值,直接进ST_MEASURE跳过菜单,能节省不少测试时间。

3.3 扫频测量与截止频率计算:对数步进+线性插值

扫频是整套代码最需要吃透的部分。上限截止频率fH的测量从1kHz开始扫到1MHz,下限截止频率fL从10Hz扫到1kHz。频率点不是线性等间隔的,而是按对数步进系数排列,低频段点密、高频段点疏。原因是截止频率在频率轴上是对数分布的,线性步进要么高频段点数爆炸,要么低频段点数稀疏到找不到拐点。

/* Middle/scan.c * 测量上限截止频率fH,返回频率值(Hz) * vin_amp: DDS输出幅度,用于折算增益 */ float measure_fH(float vin_amp) { float f_start = 1e3f; /* 扫频起点 */ float f_end = 1e6f; /* 扫频终点 */ float step = 1.2f; /* 对数步进系数 */ float v_mid = measure_gain_at(1e3f, vin_amp); /* 中频增益基准 */ float freq = f_start; float last_freq = f_start; float last_gain = v_mid; while (freq <= f_end) { float g = measure_gain_at(freq, vin_amp); if (g < 0.707f * v_mid) { /* 已穿过-3dB点,在[last_freq, freq]区间做线性插值 */ float fH = last_freq + (freq - last_freq) * (last_gain - 0.707f * v_mid) / (last_gain - g); return fH; } last_freq = freq; last_gain = g; freq *= step; /* 下一频率点 */ } return f_end; /* 未找到拐点,说明带宽超量程 */ }

逻辑说明:程序先测1kHz处的增益作为中频基准v_mid,然后从低频往高频扫。一旦当前增益低于0.707v_mid,说明fH落在上一个频点和当前频点之间,用线性插值逼近真实拐点。直接使用首次低于-3dB的频点会引入最多一个步进系数的误差,插值后可以控制在1%以内。

参数说明:step取1.1~1.2之间,越小精度越高但耗时越长。这个包默认取1.2,完整扫完高频段大约扫30个点,配合检波延时总共耗时约1.5秒。如果追求精度,可以改成1.1,代价是时间翻倍。这里的“对数步进系数”不是固定死的,如果被测板带宽更大,f_end需要同步上调。

增益测量内部用的是软件RMS:ADC采集一段信号后先减去直流偏置,再对采样值平方求平均开根号。这里有个关键点:采样长度必须覆盖整数个信号周期,所以代码里会根据当前频率动态计算采样次数,而不是用固定长度的缓冲区。我见过不少人在这个细节上翻车,读到的RMS值跳动幅度超过5%。

3.4 故障检测模块:先直流后交流,再查判定表

D题的故障检测是另一个大项,常见故障包括输入耦合电容开路、输出耦合电容开路、射极旁路电容开路、集电极电阻虚焊、基极上偏置电阻断路等七类。故障检测的思路是:先测三极管各极直流电压判断电阻类故障,再注入中频交流信号判断电容类故障。电容开路不影响直流工作点,所以只测直流一定会漏判。

/* Middle/fault.c * 故障检测:先直流、后交流,结合判定表 */ void detect_fault(void) { dc_t dc = measure_dc(); /* 第一步:直流工作点检查 */ if (dc.collector_volt < 0.5f * DC_NORMAL_COLLECTOR) { set_fault(FAULT_COLLECTOR_OPEN); /* 集电极电阻开路 */ return; } if (dc.base_volt < 0.2f * DC_NORMAL_BASE) { set_fault(FAULT_BIAS_OPEN); /* 基极上偏置开路 */ return; } /* 第二步:交流增益检查 */ float g = measure_gain_at(F_MID, VIN_TEST); if (g < 0.3f * G_NORMAL) { set_fault(FAULT_EMITTER_CAP_OPEN); /* 射极旁路电容开路 */ } else if (g < 0.8f * G_NORMAL) { set_fault(FAULT_COUPLING_CAP_OPEN); /* 耦合电容异常 */ } else { set_fault(FAULT_NONE); } }

逻辑说明:直流异常优先,因为电阻类故障会让工作点严重偏移;直流正常时再测中频增益,射极旁路电容开路会让增益大幅下降,耦合电容开路则直接导致输出无信号。代码里用0.3和0.8两个阈值区分故障程度,而不是和标准值精确比较,原因是元器件有离散性,正常板的增益本来就有一个浮动区间。

参数说明:DC_NORMAL_*和G_NORMAL这些基准值是从预置被测板的实测值标定出来的,存放在末尾的标定结构体里。F_MID一般取10kHz,这个频率点要远离fL和fH,确保测到的确实是中频增益。VIN_TEST是固定幅度测试信号,通常取50mV左右,避免推动放大器进入非线性区。

4. D题常见问题避坑:阻抗测量偏差、故障误判与调试翻车实录

4.1 输入电阻测出来偏大:分压电阻没有扣除继电器导通电阻

现象:输入电阻在20kΩ档位实测值比万用表测得的结果高出几百欧到一千欧,而且档位越大偏差越明显。

原因:信号通路里用继电器切换分压电阻,继电器触点导通电阻和模拟开关导通电阻虽然只有几百毫欧,但它串在分压回路里改变了实际分压比。计算时只用了电阻标称值,没有扣除开关导通电阻。

解决:测量前先让继电器切换到导通状态,用ADC短路测一次通路总电阻,把导通电阻存进校准表。计算Ri时,把Rs替换成Rs+Rsw。我一般会把这个校准动作放在每次开机自检流程里,而不是只在出厂时标定一次,因为继电器触点用久了接触电阻会变大。

4.2 高频段频响曲线“玄学”波动:地回路和检波器带宽不足

现象:测量100kHz以上频点时,增益曲线出现反复的凸起和凹陷,换一个接地点结果就变,中频段完全正常。

原因:高频段对地回路阻抗和寄生电容特别敏感,DDS输出幅度在不同频率下也有起伏;同时真有效值检波器在高频段的附加误差会变大,如果检波器带宽只有几百kHz,测出来的值本身就是偏的。

解决:模拟地和数字地单点接地,功率走线远离信号走线,DDS输出到被测板之间加一级缓冲;另外用示波器先复测DDS在1MHz时输出幅度是否平坦,把“频率-输出幅度”曲线做成校准表,软件里对每个频点的测量值做补偿。这块没有捷径,只能靠实测数据一点一点磨。

4.3 增益读数来回跳:RMS采样窗口不是整数周期

现象:增益在中频段也比较稳定,但切换到高频段后读数开始跳,幅度差能到3%以上。

原因:软件RMS的ADC采样长度没有覆盖整数个信号周期。采样窗口是固定的1ms,在50kHz以下还算够用,到了几百kHz,固定窗口内包含的周期数不是整数,频谱泄漏直接让有效值计算产生周期性误差。

解决:采样前先通过比较器或定时器测信号频率,根据当前频率动态计算采样点数,强制覆盖N个完整周期。这个包里scan.c的做法是先测频再采样,改动起来不复杂。如果不想动软件,直接换硬件真有效值检波器也可以,但稳定时间记得留够,不能一换频点立刻读值。

4.4 故障检测“张冠李戴”:只在直流分支里兜底

现象:射极旁路电容开路时,故障结果显示“偏置电阻开路”,但两处电路明明互不相干。

原因:detect_fault里直流异常分支提前return了,电容开路不影响直流工作点,直流分支判定正常后直接落到了“无故障”或错误分支,根本没有走到交流增益检测这一步。

解决:把故障判定改成“直流工作点+交流增益”组合条件,直流正常不代表信号通路正常。电容类故障必须通过交流注入来判断,判定表里每个故障类型对应一组“直流状态+交流增益”特征,而不是单一条件。另外要注意,耦合电容完全开路时输出几乎为零,这和射极旁路电容开路时的“增益降到三成”是完全不同的数值特征,别塞进同一个区间里去。

4.5 下限截止频率测不准:扫频起点太高

现象:fL测出来总是比理论值高不少,有时候甚至显示几百Hz,但用示波器看实际波形几十Hz时还正常。

原因:扫频起始频率设置得太高,没有覆盖到真正的低频转折区。fL的计算依赖“找到增益下降到中频增益0.707倍的点”,如果起点频率已经高于拐点,算法拿到的增益序列里根本没有低频平坦段,中频基准取得也不对。

解决:把fL扫频起点压到10Hz甚至1Hz,低频段步进系数比高频段更小,比如取1.1以下。中频增益基准点要从远高于fL的平坦区取,比如1kHz,而不是拿扫频序列第一个点当基准。改完之后,实测fL和理论计算能对上。

5. 验证与进阶:用基准板和脚本回归把代码包吃透

5.1 搭一块基准板,把每个测量点标定成“标准值”

这块代码包能不能信,关键要看标定流程。我拆包后的第一周,就是用一块已知参数的基准板做的回归验证:先用电阻箱和标准信号源测出每个测量点的参考值,再运行包的测量流程,记录误差。

测量点参考值代码包实测值误差
输入电阻Ri9.98kΩ10.06kΩ+0.8%
输出电阻Ro2.01kΩ1.98kΩ-1.5%
中频增益Au20.5倍20.3倍-1.0%
上限截止频率fH312kHz318kHz+1.9%
下限截止频率fL48Hz47Hz-2.1%

误差在2%以内是可以接受的,超过这个值就要回到第4章找原因。每次修改代码或换被测板后,重新跑一遍这张表,比看一百遍原理图都管用。

5.2 把代码包改造成通用幅频特性测试仪

比赛结束后,这套代码的复用价值很高。把fault.c整个剥离,保留scan.c的扫频和RMS算法,再把被测通道通过模拟开关扩展成多路,就可以当成小型幅频特性测试仪用。改造时注意把DDS频率表、步进系数、采样点数都改成可配置参数,别写死在函数里。上位机方面,把OLED显示换成串口输出,配合Python脚本直接画波特图,调试效率能提升一大截。这份代码留给下一届队员的最大遗产不是那几行算法,而是“先量后算、量程自动切换、校准优先”的整套测量习惯。

从那次之后,我每次拿到电赛代码包,第一件事不是打开某个算法文件,而是在主循环里把状态机走一遍,确认测量函数由谁调用、扫频参数去哪改、校准常数存在哪,然后才去碰那些计算公式。这个习惯就是从拆这个包开始养成的,确实帮我少翻了好几次车。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询