全国大学生电子设计竞赛(电赛)是嵌入式方向学生接触真实工程问题的最快路径之一。两年电赛生涯结束后回看,最有价值的其实不是最后那张获奖证书,而是在备赛和参赛过程中被迫学会的一整套方法论:需求拆解、器件选型、电路连接、驱动编写、系统联调、现场排错和时间管理。
这篇文章不打算写成回忆录,而是用技术复盘的方式,把两年电赛沉淀下来的内容拆成可复用的经验。全程会围绕一条主线展开:从备赛时间规划,到作品通用架构,再到关键代码实现,最后落到故障排查和比赛现场操作清单。如果你正准备参加电赛,或者带学生参加电赛,这篇文章可以直接当作备赛参考。
1. 把两年电赛当成一个嵌入式系统工程来复盘
1.1 电赛到底在考核什么
外界很容易把电赛理解为“单片机编程比赛”,实际并不是。电赛要求参赛队伍在封闭场地、有限时间内,完成一个完整的电子系统设计。这个系统通常包含传感器检测、信号处理、主控逻辑、执行机构、人机交互和供电方案。作品做出来后,还要现场演示,并根据题目要求提交设计报告和测试数据。
所以电赛考核的不是单一技能,而是三件事的组合:
- 硬件能不能搭起来,并且稳定工作。
- 软件能不能跑通,并且满足性能指标。
- 临场遇到问题时,能不能在有限时间内定位并修复。
这非常接近一个小型嵌入式项目的完整流程。区别在于,平时的课程设计可以慢慢调,电赛现场没有宽裕时间。这种环境会把“系统工程能力”和“知识储备”之间的差距迅速放大。
1.2 两年生涯里真正沉淀下来的四个能力层
这两年的经历可以按能力层拆开,方便后续复盘:
| 能力层 | 具体内容 | 对应场景 |
|---|---|---|
| 硬件底层理解 | 传感器输出类型、信号调理、电源供电、共地 | 焊接、连线、示波器测试 |
| 嵌入式软件能力 | GPIO、中断、定时器、ADC、PWM、串口、I2C/SPI | 驱动编写、功能调试 |
| 系统联调能力 | 软硬件接口、协议约定、日志、仪表配合 | 整机联调、性能优化 |
| 工程化习惯 | 文档、版本备份、验收清单、时间管理 | 赛前准备、现场操作 |
这四个能力层不是并列关系,而是递进关系。第一年主要补前两层,第二年才真正进入后两层。很多队伍第一年成绩不理想,问题往往不是代码不会写,而是没有把作品当成一个系统去管理,导致硬件和软件之间反复推诿,最后现场崩掉。
2. 两年备赛的技术路线:按阶段推进,不能只靠赛前突击
2.1 第一年:从点亮 LED 到控制闭环
第一年备赛的核心目标不是拿奖,而是把嵌入式开发的基础外设全部过一遍,并且每个外设都要做最小验证。推荐的练习顺序如下:
- GPIO 输出,点亮 LED,理解引脚模式和电平。
- GPIO 输入,读取按键,做软件消抖。
- 串口 UART,通过打印日志确认程序运行状态。
- 外部中断,理解中断触发条件和回调机制。
- 定时器,实现延时、计数、PWM 输出。
- ADC 采集,读取电位器或分压电路电压。
- I2C 或 SPI,读取 MPU6050、OLED 等常见模块。
- 基础 PID,通过编码器或电位器反馈,控制电机转速或舵机位置。
这个阶段不需要做完整作品,只需要确保每个模块单独工作时是可靠的。大多数电赛现场故障,追根溯源都是某个基础模块在单独测试时就没真正跑通,赛时叠加在一起后问题被放大。
2.2 第二年:从模块堆叠到系统设计
第二年的重点开始从“模块能用”转向“系统能稳定”。这里有一个常见误区:很多队伍在正式比赛前,把一堆模块用杜邦线连在一起,写一个 main 函数从头到尾调用,就认为作品完成了。实际联调时就会出现各种奇怪问题,例如 ADC 数值漂移、电机互相干扰、OLED 刷新卡顿、按键失灵。
正确的做法是先不要写业务逻辑,而是把系统拆成几层:
- 传感层:负责采集数据,输出统一格式的物理量。
- 控制层:负责决策和算法。
- 执行层:负责把控制量转成电机、开关、显示等动作。
- 调试层:负责打印关键状态,方便现场观测。
层与层之间通过结构体或全局状态变量传递数据,而不是在中断里直接写显示、在显示里直接做延时。每年真正好用的作品,代码结构不一定复杂,但分层一定清晰。
2.3 不同赛题方向要重点准备的技术点
电赛题目会覆盖多个方向,备赛时不能每个方向都平均用力。建议先根据队伍成员的技术基础,选择一到两个主攻方向,再针对性地准备模块。
| 赛题方向 | 核心任务 | 需要重点准备的技术点 |
|---|---|---|
| 电源类 | 设计 DC-DC 变换、恒压恒流、效率测量 | Buck/Boost 拓扑、MOS 驱动、反馈环路、精密采样 |
| 控制类 | 电机控制、小车循迹、平衡控制 | 编码器、PID、PWM、姿态解算、滤波器 |
| 仪器仪表类 | 信号测量、波形分析、参数显示 | 高精度 ADC、信号调理、FFT、校准方法 |
| 通信类 | 无线数据传输、组网、协议解析 | 无线模块配置、数据封包、校验、重传机制 |
从备赛性价比看,控制类和仪器仪表类是新手最容易在两到三个月内做出成绩的方向,因为资料多、模块成熟、调试手段丰富。电源类对硬件设计和动手能力要求更高,如果队伍里没有人能熟练使用示波器和焊台,不建议作为首次参赛的主力方向。
3. 电赛作品的通用架构:采集、处理、控制、调试四层
3.1 主控选型:不是越贵越好,要看外设匹配度
很多准备电赛的同学容易陷入一个误区:主控芯片越高端越好。实际上电赛作品只需要满足题目要求即可,多出来的算力和外设反而会增加配置复杂度。选主控时建议按以下顺序确认:
- 需要几路 ADC,分辨率要求多高。
- 需要几路 PWM,频率范围是多少。
- 需要几个串口、I2C、SPI 接口。
- 是否需要 DMA 搬运数据。
- 工作电压和外设电平是否匹配。
以 STM32F103 系列为例,它拥有多个定时器、多路 ADC、多个通信接口,对于大多数电赛题目足够用。如果涉及图像处理、FFT 频谱分析等复杂计算,再考虑使用 STM32F4 系列或更高性能平台。
这里要区分平时练习环境和比赛现场环境。平时练习时,芯片型号、开发板品牌可以随意调整;比赛现场建议带至少两块相同型号的主控板、两个下载器、若干备用芯片,并且提前烧录好一个“最小系统测试固件”,用于确认主控板在上电后是否正常。
3.2 传感器和执行器接入时的通用处理流程
任何传感器接到主控之前,都要走一遍统一流程,不要直接写代码:
- 看数据手册,确认输出类型是模拟量、数字量还是通信协议。
- 确认传感器工作电压,判断是否需要逻辑电平转换。
- 确认接线方式和引脚定义,避免杜邦线接反。
- 先写一个最小读取程序,打印原始值。
- 对比原始值和实际物理量,判断是否需要标定。
- 做数据滤波或格式转换,再把数据交给上层业务逻辑。
执行器也一样。电机、舵机、继电器、加热丝等设备,通常不能由主控 GPIO 直接驱动,需要经过电机驱动芯片、MOS 管或光耦隔离。接入前要确认驱动板输入逻辑电平、输出能力和电源容量。
很多电赛现场故障都出在这一步:传感器输出的是 5V 电平,主控引脚不耐压;或者电机驱动板需要的逻辑高电平是 3.3V,结果输入信号不对导致电机不转。这类问题如果在备赛阶段统一处理,现场就不会浪费大量时间。
3.3 电源和通信:最容易在现场翻车的两个隐性模块
电源问题最隐蔽。电赛作品往往同时带主控、传感器、电机、显示屏,各部分对电压和电流要求不同。常见问题包括:
- 使用同一个电源给电机和主控供电,电机启动瞬间拉低电压,导致主控复位。
- 电源纹波过大,ADC 采样值跳动。
- 多个模块共地处理不当,通信数据出错。
- 电源线过细,长距离传输导致压降明显。
推荐做法是主控和传感器使用稳压后的电源,电机单独使用动力电源,两者只在电源输入处共地。如果空间允许,加入大容量电解电容来吸收启动瞬间的电流冲击。
通信问题则集中在串口、I2C、无线模块上。比赛现场最常见的串口通信故障是波特率不一致。其次是数据粘包和丢帧。自己做的协议里,建议固定使用帧头、长度、数据、校验的结构,不要使用裸字符判断。后续排查的时候,有协议边界和校验字段会轻松很多。
4. 核心代码模块的落地示例:以 STM32 平台为例
4.1 传感器数据采集:ADC 加均值滤波
传感器数据采集中,ADC 原始值不能直接当作物理量使用。以 STM32 的 HAL 库为例,先配置 ADC 通道,然后在代码里多次采样取平均值,减小噪声。
// 读取一次 ADC 原始值 uint32_t adc_read_raw(ADC_HandleTypeDef *hadc) { HAL_ADC_Start(hadc); if (HAL_ADC_PollForConversion(hadc, 10) == HAL_OK) { return HAL_ADC_GetValue(hadc); } return 0; } // 多次采样取平均,用于降低随机噪声 float adc_read_voltage(ADC_HandleTypeDef *hadc, float vref) { const int sample_count = 16; uint32_t sum = 0; for (int i = 0; i < sample_count; i++) { sum += adc_read_raw(hadc); } float avg_raw = (float)sum / sample_count; float voltage = avg_raw * vref / 4095.0f; return voltage; }这段代码的要点是把采样次数提高到 16 次,使用平均值代替单次采样值。对于慢变化的物理量,例如温度、电位器、电池电压,这样做能明显减少显示抖动。但要注意,如果传感器本身响应很快,例如要采集振动信号或电流波形,就不能大量取平均,否则会丢失信号细节。
ADC 的参考电压 vref 通常就是 3.3V,如果主控板使用外部基准电压芯片,需要把实际基准值传进来,否则计算出的电压会有偏差。
4.2 输出控制:PWM 调电压调速度
PWM 是电赛作品里最常用的输出控制方式。用定时器产生 PWM 时,需要明确两个参数:频率和占空比。
// 假设已经通过 CubeMX 配置好 TIM2 的 PWM 通道 void pwm_set_duty(TIM_HandleTypeDef *htim, uint32_t channel, float duty_percent) { if (duty_percent < 0.0f) duty_percent = 0.0f; if (duty_percent > 100.0f) duty_percent = 100.0f; uint32_t pulse = (uint32_t)(__HAL_TIM_GET_AUTORELOAD(htim) * duty_percent / 100.0f); __HAL_TIM_SET_COMPARE(htim, channel, pulse); }PWM 频率选择有一点讲究。频率太低时,电机或加热器会发出可听见的噪声,控制精度也差;频率太高时,功率管的开关损耗增加,驱动芯片发热。直流电机常用的 PWM 频率在 10kHz 到 20kHz 之间,舵机控制则一般是 50Hz 周期、1ms 到 2ms 高电平宽度。实际频率要结合驱动芯片的数据手册来确定,不要照抄别人的参数。
占空比的控制逻辑要在业务层实现,不要在主循环里反复修改定时器计数器值,而是通过统一的接口函数设置目标占空比,由定时器硬件自动保持输出。
4.3 控制算法:位置式 PID 的工程简化实现
电赛中最常见的闭环控制算法是 PID。位置式 PID 实现简单,适合初学者理解,但工程上要注意积分饱和问题。下面是一个适合嵌入式环境的简化版本。
typedef struct { float target; // 目标值 float feedback; // 反馈值 float kp; // 比例系数 float ki; // 积分系数 float kd; // 微分系数 float integral; // 积分累加值 float prev_error; // 上一次误差 float out_max; // 输出上限 float out_min; // 输出下限 } PidController; float pid_update(PidController *pid, float feedback, float dt) { float error = pid->target - feedback; pid->integral += error * dt; // 积分限幅,防止积分饱和 if (pid->integral > 300.0f) pid->integral = 300.0f; if (pid->integral < -300.0f) pid->integral = -300.0f; float derivative = (error - pid->prev_error) / dt; float output = pid->kp * error + pid->ki * pid->integral + pid->kd * derivative; pid->prev_error = error; // 输出限幅 if (output > pid->out_max) output = pid->out_max; if (output < pid->out_min) output = pid->out_min; return output; }这段代码有两个关键点值得展开。
第一,积分限幅。如果误差长期存在,例如电机堵转,积分项会一直累加,导致输出到达上限,恢复后要很久才能回到正常。通过限制积分累加值,可以避免系统反应迟钝。
第二,输出限幅。PWM 占空比不能超过 100%,所以任何 PID 输出最终都要限制在执行机构的物理范围内。否则一旦输出异常,整机控制效果就会被破坏。
调参顺序建议是:先只开 P 项,让系统稳定且不振荡;再加入 D 项,抑制超调;最后加 I 项,消除稳态误差。不要同时修改三个参数,否则很难判断哪个参数导致系统表现变化。
4.4 调试通道:串口日志和状态机打印
电赛现场没有 IDE 在线调试环境,或者说不方便反复连接调试器。这时候串口日志就是最重要的观测手段。建议在代码里预留一个轻量日志函数,统一输出当前状态、目标值和反馈值。
// 日志格式建议固定,方便使用串口上位机或文本工具解析 void log_status(int state, float target, float feedback, float output) { char buf[64]; snprintf(buf, sizeof(buf), "state=%d,target=%.2f,fb=%.2f,out=%.2f\r\n", state, target, feedback, output); // 调用串口发送函数,例如 uart_send_string uart_send_string(buf); }状态机的每个状态都打印一条日志,定位问题时会非常方便。例如小车走直线跑偏,如果日志显示反馈值一直在目标值附近波动,说明控制参数有问题;如果反馈值根本没更新,说明传感器或通信链路有问题。日志能快速把问题范围缩小到“硬件采集”还是“控制算法”。
5. 两年里踩过的坑:现象、原因和排查链路
5.1 硬件正常但软件读数为 0
现象:用万用表量传感器输出,电压正常,但单片机读取的 ADC 数值始终为 0。
可能原因:
- 引脚配置错误,把不是 ADC 功能的引脚配成了模拟输入。
- 开启了复用功能,但没有正确配置 ADC 通道。
- 采样时间过短,信号源阻抗偏高。
- ADC 没有执行校准。
检查方式:
- 对照芯片数据手册,确认引脚是否支持 ADC 输入。
- 查看 CubeMX 或寄存器配置,确认 ADC 通道号与引脚对应。
- 用手触摸传感器输出点,观察读取值是否变化。
- 打印 ADC 原始值,不要直接打印转换后的电压。
处理建议:先把 ADC 配置简化成单通道连续转换,确认能读到有效值后,再调整采样时间或加入 DMA。不要一上来就多通道扫描。
5.2 电机抖动、声音尖锐且发热
现象:电机低速运行时抖动明显,高速时声音尖锐,驱动芯片和电机外壳发热严重。
可能原因:
- PWM 频率与驱动芯片匹配不佳。
- PID 参数过激,输出振荡。
- 电源供电能力不足,电机启动时电压跌落。
- 电机或驱动板共地不良。
检查方式:
- 用示波器观察 PWM 输出波形,确认频率和占空比稳定。
- 把 PID 的 Ki、Kd 先置零,只留 Kp,观察系统是否还会抖动。
- 用电流表测量电机启动时的工作电流,对比电源最大输出电流。
- 检查电机电源地和主控地是否连接在同一电位。
处理建议:先用固定占空比测试电机的开环运行,确认电源和驱动没有问题;再加入闭环控制。如果抖动仍然存在,优先排查 P 项过大,不要先调积分。
5.3 系统偶尔复位,找不到稳定复现路径
现象:整机运行过程中偶尔重启,故障出现频率不高,难以稳定复现。
可能原因:
- 电源电压跌落。
- 外部干扰导致复位引脚误触发。
- 独立看门狗超时。
- 中断函数执行时间过长,导致主循环卡死。
检查方式:
- 用示波器长时间观察主控 VDD 引脚,看电机启动时是否出现跌落尖峰。
- 查看复位标志寄存器,区分是上电复位、外部复位还是看门狗复位。
- 在中断函数和主循环里都加入日志,确认程序最后执行到哪里。
- 逐个失能外设,缩小干扰来源。
处理建议:优先在电源端加大电容,并参考芯片手册检查复位引脚的上拉电阻。不要直接修改看门狗超时时间来掩盖问题,因为往往真正的原因不是看门狗,而是程序卡死。
5.4 比赛现场改参数改崩了
现象:现场演示前,为了优化性能修改参数,结果系统行为异常,无法快速恢复到之前可用的状态。
可能原因:
- 没有保留上一版的固件备份。
- 同一时间修改了多个参数,无法确定是哪个参数导致问题。
- 没有在源码里注释原参数值。
预防措施:
- 每次烧录前,把当前固件导出为 .bin 文件,按日期和版本号命名。
- 现场只允许修改一个参数,如果无效立即改回。
- 关键参数用宏定义或配置文件统一管理,不要在代码里散落多个魔法数字。
6. 两年电赛沉淀出来的工程素养
6.1 文档先行:接口约定比写代码更早
电赛作品往往需要两个人或三个人协作。如果软硬件分工不明确,联调时就会反复改代码、改接线。建议在动手写代码之前,先定一份接口文档,内容至少包括:
- 主控引脚分配表。
- 每个传感器和执行器的供电电压、通信接口、数据格式。
- 数据结构体的定义,例如状态枚举、控制量、反馈量。
- 串口协议格式。
下面是一个简单的引脚分配表示例:
| 功能 | 引脚 | 电平 | 说明 |
|---|---|---|---|
| 编码器 A 相 | PA0 | 3.3V | 外部中断输入 |
| 编码器 B 相 | PA1 | 3.3V | 外部中断输入 |
| 电机 PWM | PA8 | 3.3V | TIM1_CH1 |
| 电机方向 | PB3 | 3.3V | GPIO 输出 |
| 电压采样 | PC0 | 3.3V | ADC1_IN10 |
这份文档在比赛前写好,比赛现场就能减少大量沟通成本。
6.2 版本管理和备份策略
电赛备赛周期通常有两三个月,代码会经过无数次修改。可靠的做法是至少建立三层备份:
- 本地开发目录,按日期建目录副本。
- 使用 Git 做代码版本管理,每个功能完成就提交一次。
- 比赛现场携带 U 盘,保存所有源码和固件。
即使不熟悉 Git,也要养成“改大功能前复制一份”的习惯。现场最怕的不是没有新功能,而是改到一半想回退却发现回不去。
6.3 测试与验收清单
电赛作品不要只测“能跑”。建议每个子模块都设置一个验收标准,避免联调时把基础模块问题带进系统。下面是一份常用测试清单:
| 检查项 | 验收标准 |
|---|---|
| 主控上电 | 电源指示灯亮,最小系统固件串口输出正常 |
| 传感器读取 | 串口打印值随物理量变化,响应方向和量程正确 |
| PWM 输出 | 示波器测量频率和占空比与设定值一致 |
| 闭环控制 | 施加扰动后,系统能在规定时间内回到目标值 |
| 连续运行 | 整机连续运行 2 小时,无复位、无数据异常 |
比赛前一周,至少完整跑一遍整机连续运行测试。很多队伍只测功能不测稳定性,现场演示时系统运行十分钟后才开始出现异常,这种问题最难处理。
6.4 时间管理:备赛不同阶段怎么分配精力
备赛周期一般可以分成四个阶段:
- 基础练习阶段:约前 6 周,集中解决单片机外设和模块驱动问题。
- 模块准备阶段:约第 7 到第 10 周,把常用传感器、执行器、屏幕、无线模块全部备齐并单独验证。
- 系统整合阶段:约第 11 到第 14 周,做一两次完整模拟题,从选题、方案设计到整机联调。
- 现场冲刺阶段:最后一周,只做稳定性测试和文档整理,不要再引入新功能。
最容易出问题的是第三阶段。很多队伍到系统整合阶段才发现自己缺少某个模块,或者某个模块单独能用,但多个模块同时工作后互相干扰。所以模拟题做的时间要尽量提前,越早暴露问题,越有时间补齐。
7. 给后来者的可复用备赛清单
7.1 赛前三个月、一个月、一周的检查项
赛前三个月:
- 确定主控平台,完成基础外设练习。
- 准备常用模块:编码器电机、舵机、OLED、MPU6050、蓝牙/Wi-Fi 模块、各类传感器。
- 编写每个模块的最小测试代码,保存为独立工程。
- 建立接口文档模板和代码版本管理习惯。
赛前一个月:
- 至少完成两次模拟题。
- 每次模拟题后记录用时、卡点、缺失模块。
- 根据模拟题暴露的问题补充器件和代码库。
- 准备备用主控、下载器、杜邦线、常用芯片、电源模块。
赛前一周:
- 整理现场工具箱:焊台、万用表、示波器、电源、热风枪、剥线钳、杜邦线、扎带。
- 对每个模块做上电检查,确认没有损坏。
- 打印接口文档、引脚分配表、测试清单。
- 准备 U 盘,备份所有源码和固件。
7.2 现场比赛时的操作顺序
拿到赛题后,按以下顺序推进:
- 先通读题目,圈定基本要求和发挥部分。
- 评估手上模块是否能覆盖题目要求,区分“必须件”和“已有件”。
- 确定技术方案,把系统拆成硬件、软件、算法三个子任务。
- 先跑通最小闭环:主控、一个传感器、一个执行器、串口日志。
- 在最小闭环的基础上,逐步加入显示、通信、备用功能。
- 完成基本功能后,再优化指标,例如提高控制精度、降低功耗。
- 最后写设计报告和准备演示脚本。
不要在拿到题目当天就急着写全部代码。先把架构定清楚,把核心风险提前识别出来。现场四天里的节奏是:第一天定方案和搭硬件,第二天写核心代码,第三天联调和优化,第四天准备演示材料和备份。
7.3 赛后复盘记录什么
比赛结束后,不要急着休息或准备下一场,先花两个小时做一次复盘。记录内容:
- 题目和最终方案。
- 时间分配是否合理,哪个阶段超出预期。
- 遇到的所有问题,包括问题现象、根因、解决方式。
- 哪些准备不足,哪些判断准确。
- 如果再做一次,会在哪个环节投入更多时间。
这份复盘的价值会在下一场比赛,或者后续的工程项目中体现。两年电赛生涯最宝贵的东西,其实是逐步建立起来的这套做事方式:先想清楚再动手,每一步可验证,每个问题可追溯。
如果你准备参赛,建议从今天开始,先给自己建一个“模块测试库”目录,把你手里的每一块传感器、每一种电机、每一个无线模块,都写一个最小测试工程并保留结果记录。真正拉开差距的不是最后几天的熬夜,而是每一次实验之后,你有没有把现象、原因和下一步行动写下来。有了这套基础,到比赛现场时,你手里握着的就不是零散模块,而是一个经过反复验证的工程工具箱。