简介:2019年全国电子设计大赛D题国家二等奖代码包,面向准备电赛的本科生与嵌入式开发者,提供一套经过严格测试、无bug的完整工程方案。代码以STM32为平台,涉及ADC、定时器、LCD显示、I2C、CAN等外设驱动与算法实现,适合用于信号处理、数据通信和控制策略的实战参考。压缩包共156个文件,以C源码和头文件为主(h/c文件约76个),辅以工程配置文件、编译生成的HEX/axf以及文档说明,整体仅2.98MB,结构清晰、便于快速导入Keil工程进行学习与二次开发。目前已有5337人学习下载。该版本标注为“v3”,体现多次迭代优化后的稳定成果;通过阅读代码,可重点学习系统模块化划分、硬件接口调用、异常处理机制及代码规范,还可借助调试相关文件理解排错思路,对提升编程能力和团队项目协作大有帮助。 最近翻网盘,看到当年存下来的“2019年全国电子设计大赛D题国家二等奖代码.rar”,顺手解压看了看,发现里面除了代码,还有不少调试记录、波形截图和笔记。这套料挺完整的,正好拿出来聊聊——不是复刻一个获奖作品,而是分享一套从题目拆解、方案选型、代码实现到调试排错的完整思路,给准备打电赛或者正在做电路测量类项目的朋友做一个参考。
这套代码本身解决的是D题“简易电路特性测试仪”的核心测量问题:在不知道被测放大器内部参数的前提下,测出输入电阻、输出电阻、电压增益,以及幅频特性和上限频率。如果你今年或者明年要参加电赛,或者你正在做信号采集、阻抗测量、频率响应分析这类项目,这个代码包里的嵌入式程序框架、测量算法和校准思路,都有直接可抄的价值。哪怕是纯软件方向的同学,扫频算法的实现和状态机设计也值得看一遍。
1. 赛题拆解与方案选型
1.1 题目的真实需求
2019年国赛D题,表面上是做一个“测试仪”,但把题目要求拆开看,其实是在考三个层面:信号发生、信号测量、数据处理与显示。
被测对象是一个阻容耦合多级放大器,设计指标大概在电压增益20dB上下、带宽100kHz左右,输入电阻几十千欧、输出电阻几百欧这个量级。测试仪需要做的事情包括:
- 输出一个幅度和频率都可控的正弦激励信号,加到被测放大器输入端;
- 测量放大器输入端的电压幅度(用来算输入电阻);
- 测量放大器输出端的电压幅度(用来算增益和幅频特性);
- 输出端接不同负载,通过两次测量算出输出电阻;
- 扫频测量,绘制幅频特性曲线并计算上限频率。
这里有个关键点:题目考的是测量仪器,不是放大器本身。所有测量的前提是——你不能改变放大器的工作状态,不能拆开它,也不能用一个已知电阻去并联它的输入端影响工作点。这意味着所有的测量都必须在“轻接触”的前提下完成,对测量电路的前级阻抗匹配、信号幅度控制、抗干扰能力都有要求。
1.2 方案选型:为什么是STM32+DDS+真有效值检测
当年我们选型考虑过几套方案,最终敲定的是:STM32F103ZET6做主控,AD9833做DDS扫频信号源,AD637做真有效值检测,配合CD4052模拟开关实现自动量程切换。
选STM32没有悬念,国赛大部分队伍都用意法半导体,库函数成熟、资料多、出问题好查。DDS选AD9833的原因是它支持SPI接口,频率寄存器32位,在20Hz到2MHz范围内可以做到0.1Hz级别的分辨率,完全覆盖题目要求的频段。
测量方案上,我们对比过“ADC直接采样波形+FFT算幅度”和“真有效值芯片检测”两条路。FFT方案在低频段需要采集较长时间的波形才能保证频率分辨率,而且对ADC的采样率和触发时序要求较高;AD637是硬件真有效值芯片,直流输出与输入波形有效值成正比,MCU只需要采集一个直流电压,程序实现简单,实时性也好。代价是AD637的响应时间大约在几毫秒量级,扫频时每个频点需要等待输出稳定再采样,但电赛对测量速度的要求不高,这个延时完全能接受。
最终系统的核心测量链是:STM32设置AD9833输出指定频率正弦波 → 幅度调整电路把信号幅度控制在校准到1Vpp/100mVpp等档位 → 通过模拟开关选择测量点 → AD637转换有效值 → STM32的ADC采集 → 查校准表修正 → LCD显示结果。
2. 系统架构与代码模块设计
2.1 代码整体框架
这套代码的工程结构不是写到哪算哪的“流水账”,而是按功能拆成驱动、算法、界面三层。打开rar解压后,核心文件大概是这样组织的:
- main.c:系统初始化、主状态机调度
- ad9833.c / ad9833.h:DDS信号源驱动,SPI通信,频率/相位寄存器设置
- ad637_adc.c:ADC采样与均值滤波,真有效值读数处理
- switch_mux.c:CD4052模拟开关通道控制,量程切换逻辑
- amplifier_measure.c:输入电阻、输出电阻、增益测量的核心算法
- sweep.c:对数扫频实现,幅频特性数据采集
- lcd_display.c:界面绘制,波形图和表格显示
- calibrate.c:校准表管理和线性插值修正
- key_input.c:按键扫描和菜单交互
分层的好处到后期调试非常明显。比如发现AD9833在1MHz以上输出幅度衰减严重,只需要改ad9833.c里的幅度补偿函数,完全不影响测量算法和界面代码。如果后期换用别的DDS芯片(比如AD9850),只要保持接口函数不变,上层代码一行都不用动。
2.2 主状态机设计
电赛代码最容易烂掉的地方,就是把所有逻辑堆在while(1)里用延时轮询。我们一开始就定了状态机思路,每个状态做了什么事、什么时候跳转,全部画了流程再写代码。
整个系统有四个主状态:待机测量、参数设置、扫频测试、结果查看。按键事件和测量完成事件作为状态转移的触发条件,所有需要等待测量的操作都以“启动测量→置忙标志→测量完成回调”的方式实现,避免了死等延时。
举个具体的例子,测量输入电阻时,过程是这样的:切换到输入测量通道 → 改变激励源输出阻抗(切换前置串联电阻)→ 延时等待DDS输出稳定和AD637建立时间 → 采样5次取平均 → 计算电阻值 → 跳回待机状态。这个过程在代码里看是一个完整的“测量函数”,但中间所有延时都用定时器时间戳实现,不会阻塞按键扫描。
2.3 为什么不用RTOS
有人可能会问,为什么不用FreeRTOS或者ucos?选拔赛的时候我们确实试过FreeRTOS,但后来发现,对于这样一个测量流程固定的设备,裸机状态机完全够用,而且更可控。
测量系统的实时性需求并不是很高,关键是有序性和确定性:先设频率、再等稳定、再采数据。RTOS的任务调度会引入不确定的任务切换时间,反而不利于保证测量时序的一致性。代码层面只需要在测量过程中关闭按键扫描中断,或者在状态机里做简单的互斥,就不会有资源竞争的问题。这一点对竞赛代码来说更重要——评审老师不会关心你用了什么系统,但会关心你在连续测量100个点时数据是否稳定一致。
3. 核心算法与关键实现
3.1 输入电阻与输出电阻的测量原理
阻抗测量的本质是在不改变被测电路工作点的前提下,通过改变源阻抗或负载阻抗,观察端电压的变化,反推阻抗值。
测量输入电阻用的是串联分压法。在DDS输出和被测放大器输入端之间串联一个已知电阻R_s,分别测量不加串联电阻时的输入电压(空载参考)和加上串联电阻后的输入电压,再根据分压公式计算输入电阻。实际处理时考虑了DDS输出阻抗的非理想因素,先用纯电阻负载做了标定,把DDS的内阻等效值算出来并在公式里补偿掉。
输出电阻测法是戴维南定理的直接应用:先测量空载输出电压,再在输出端接入标准负载电阻R_L,测量带载输出电压,根据两次电压比求出输出电阻。这个方案在电路原理上很简单,但实际做的时候有个大坑:被测放大器的输出级可能带有一定直流偏置,AC耦合后的波形有零点漂移,直接用交流有效值计算会引入误差。我们的处理方式是先做隔直处理(用内置隔直电容的测量通道),并在代码里做了直流偏置检测,如果偏置过大就提示用户检查连接。
3.2 扫频与幅频特性计算
扫频部分的算法直接决定幅频特性曲线的质量和测试速度。我们用了对数扫频:从20Hz到2MHz,按对数坐标均匀分布,总共测41个频点,每个频点测完立即更新显示。这个频点分布和人眼对频率轴的感知一致,低频段点多一些,高频段点疏一些,画出来的曲线看起来自然。
每个频点的处理流程都是标准的“三步走”:设置频率 → 等待稳定 → 幅值采样。稳定时间由两部分组成:DDS和滤波电路的建立时间,以及AD637的建立时间。实测下来20Hz低频段DDS建立时间较短,主要是AD637的响应慢,我们设置150ms的稳定延时;1MHz以上DDS内部的低通滤波器响应变慢,稳定延时加到200ms。整个扫频测试总耗时大约6到8秒,虽然不算快,但换来了稳定可靠的曲线,决赛现场实测也验证了这一点。
上限频率的计算采用“找-3dB点”的思路:先取中频段增益的平均值作为基准增益,然后从最高频点往回搜索,找到第一个增益低于基准增益0.707倍(即-3dB)的频点,再在两个相邻频点之间做对数线性插值,得到最终的上限频率估计值。这套算法在连续扫频结果上很稳,重复测量同一台放大器的结果波动能控制在2%以内。
3.3 自动量程与校准补偿
AD637的有效值输出是直流电压,线性度不错,但不同的输入幅度档位对应不同的通道增益,而且模拟开关在不同通道上的导通电阻也不一样。如果直接拿ADC读数反算电压,误差在某些档位会超过5%,这对测量仪器来说是不可接受的。
我们做了两层处理。第一层是自动量程:先用一个固定中等增益档位快速测一次,估出信号大概幅度,再切换到最适合的档位进行精确测量。第二层是校准表:用高精度信号源(实验室的Agilent 33220A)输出标准幅度信号,对每个量程档位记录5到6个校准点,代码里用线性插值进行修正。校准完成后,整个测量链路的误差控制在1%以内。
这里说一个很值得注意的细节:校准不是一次性的,DDS的输出幅度会随环境温度和工作时长漂移。我们的代码里加了“开机自校准”流程,每次上电后用内部基准电压源对ADC零点和增益进行修正。这个功能在竞赛现场特别重要,因为场地温度、供电电压都可能和实验室不同,不做这个修正,可能上午测得好好的参数,下午就不准了。
4. 调试现场与问题排查实录
4.1 高频段增益测量值跳变的“元凶”
第一次全系统联调时,发现1MHz以上频点测量值随机跳变,而且没有规律,有时候连续测三次三次都不一样。排查过程是这样的:先用示波器看DDS的输出波形,发现1MHz以上幅度本来就有点波动,但示波器读数本身不稳定,说明问题不在DDS的确定性漂移上。
后来用频谱仪看输出信号,发现1MHz附近出现了明显的谐波分量,频率刚好是基频的2到3倍。问题找到了一半——DDS的片内DAC输出是阶梯波,谐波本来就是存在的,之前的低通滤波器需要滤掉这些谐波。我们原来的设计里滤波是放在DDS内部配置的,但AD9833内部的滤波能力有限,高频段谐波抑制不够,导致谐波分量被AD637检测进来,读数自然偏高且不稳定。
解决方案是在DDS输出后加了一级有源二阶低通滤波器,截止频率设计在2.5MHz,既不影响高频段的基波测量,又把谐波压到-40dB以下。代码层面不需要改动,但这个硬件改动让高频段的测量重复性从5%提升到了1%以内。
4.2 输入电阻测量偏低:隔直电容与偏置电流的博弈
另一个花了一个下午才定位的问题:测量输入电阻时,读数总是比标称值低约15%。用万用表直接测电阻是准的,但接上放大器测就不对。
查了很久发现是“隔直电容+放大器输入偏置电流”的耦合问题。DDS输出经过测量通道时是AC耦合的,但测量通道和放大器之间如果地线连接不理想,会形成额外的地回路电流,这个电流会在串联电阻上产生压降,导致计算出的输入电阻偏低。
解决方式有两步:第一步是硬件上把整个测量链路做成“单点接地”,所有信号地汇聚到一点再连放大器;第二步是代码上用两次测量求差值的方式——先测不加串联电阻时的输入电压,再测加电阻时的输入电压,用差值比例计算,这样能抵消一部分地回路带来的共模误差。
这个经验后来也写进了我们的调试文档:任何高阻抗测量都要检查地回路,示波器探头的地夹子拿掉,用弹簧地针,也会有明显改善。
4.3 按键抖动导致参数设置错乱
初版代码里按键用的是最简单的扫描+延时消抖,正常按没毛病,但快速连续按的时候会偶尔触发两次,导致光标跳格、参数乱跳。解决方式笨但有效:检测到按键按下后,先进入一个“等待释放”的状态,在这个状态下持续扫描,只有检测到电平拉高超过50ms才认为这次按键结束,再开始响应下一次按键。
这个改进看起来很简单,但对操作体验的改善非常大。现场调试时经常需要快速切换频率范围和步进,按键不抖动之后,整个交互速度快了一倍。
4.4 常见问题速查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 低频段测量值偏大 | DDS在低频段输出幅度不受控,或隔直电容容量不足 | 用示波器看DDS输出端,检查波形是否失真;检查隔直电容是否低于10uF |
| 高频段读数跳变 | 谐波未被抑制,或AD637前级饱和 | 频谱仪看谐波分量;检查测量通道各级增益,确保末级不饱和 |
| 显示值整体偏小 | 校准表失效,或ADC参考电压不准 | 用标准信号源重新校准;测量VREF引脚电压是否稳定在3.3V |
| 扫频曲线低频段有凹陷 | 隔直电容和输入电阻组成的高通滤波器截止频率过高 | 计算高通截止频率,确认在10Hz以下 |
| 开机后界面卡死 | 状态机进入了未定义状态,或LCD初始化失败 | 检查硬件连接,增加状态机default分支 |
5. 代码里的“工程化”细节
很多同学写电赛代码,功能能跑就够了,但等到现场调试、临时改需求、甚至是在评审面前演示的时候,代码的可维护性直接影响最终成绩。这套代码里有一些“工程化”的细节,特别值得借鉴。
第一个是“所有参数集中管理”。信号频率、采样延时、增益补偿这些常数全部用宏定义集中在config.h文件里,现场调试时改参数只需要改一个地方,不用满工程搜索。比如我们现场微调AD637稳定延时,改一个宏就重新编译,一分钟搞定。
第二个是“日志输出”。代码里保留了一套通过串口打印的调试日志函数,可以在关键状态切换、测量完成时输出参考值和计算中间量。现场调试时接一个USB转串口模块,就能在电脑上实时看到测量数据流和状态转移过程,比瞎猜效率高太多。在决赛评审环节,评委问“你们是怎么判断测量是否可靠的”,我们的回答就是“每个测量点都有参考值和中间计算结果,可以随时拉出来验证”。
第三个是“模块自检函数”。每个外设驱动文件里都附了一个xx_self_test()函数,上电时可以快速检查SPI通信是否正常、ADC读数是否在合理范围、LCD能否正常显示。现场设备出问题的时候,先跑一遍自检,很多时候就能定位到到底是硬件故障还是代码问题。
6. 最后再说点题外话
翻到这套代码的时候我其实挺有感触的。当时备赛通宵调DDS波形,边焊板子边改代码,最后看着屏幕上一组组数据稳定下来,那种成就感是很真实的。这个项目沉淀下来的不只是代码文件,还有一套“遇到问题怎么定位、怎么拆解、怎么解决”的思维习惯。
如果你正在准备类似的比赛,或者在做测量类项目,我的建议是:不要只追求功能能跑通,多花点时间在代码结构、调试手段、异常处理这些“看不见的地方”。很多时候国赛评的不是谁的功能更花哨,而是谁的系统更稳、更有说服力。这套代码里也许没有惊人的算法,但每一步都是踩过坑之后沉淀下来的,这也是它能拿到国二的原因。
本文还有配套的精品资源,点击获取