电赛复盘:从零搭建云台瞄准系统,与赛场上的电烙铁应急决策
记录全国大学生电子设计竞赛 E 题"简易自行瞄准装置"的全过程:赛前训练、PID 精度攻关、UART 三保险协议设计、四天三夜极限交付,以及赛场上用电烙铁开出中空走线通路解决线缆缠绕危机的应急决策。
关联荣誉
项目概述#
| 项目属性 | 内容 |
|---|---|
| 竞赛名称 | 全国大学生电子设计竞赛 |
| 赛题 | E 题:简易自行瞄准装置 |
| 项目时间 | 2025.07 – 2025.08 |
| 获奖情况 | 顺利完赛 |
| 我的角色 | 主力成员,独立负责云台瞄准系统全部软硬件设计 |
| 技术栈 | STM32 HAL 库、位置式 PID、UART 协议、状态机、SolidWorks、3D 打印 |
赛前:那些”笨拙”的准备工作#
电赛的成果看起来是四天限时做出来的。但实际上,比赛开始前的那个 7 月上旬,我就在做准备了。
我在跑一个旧的底盘,写 BSP 功能包,用状态机调多模块联调。很多事其实是”笨拙”的——串口 DMA 在 STM32 上跑得很顺畅,但换到 TI 的 MSPM0 上连最简单的阻塞式串口都跑不顺,花了好多时间才搞清楚原因是芯片底层的中断控制器架构不同。用无线串口模块调 PID 的时候才发现,有一个能实时看到数据的工具,效率完全是两个世界。在设计云台结构时,纠结了很久到底是用齿轮传动还是电机直连,最后发现齿轮的啮合精度很难保证,效果反而不如直连。
这些思考当时看起来都不算”成果”。但等到赛题正式发布,面对硬性指标时,那些踩过的坑变成了设计决策的依据。我知道哪种通信方式更可靠,知道 PID 该怎么调,知道结构该往哪个方向优化。
这段经历让我相信:那些看起来笨拙的、没有即时成果的摸索,才是真正让人成长的底色。
正式参赛:从零搭建一套”指哪打哪”的系统#
赛题发布后,作为主力成员,我独立负责整个云台瞄准模块的软硬件设计——从驱动层到调度层,从通信协议到机械结构。
电赛 E 题有一个硬性指标:云台必须在 4 秒内完成自动瞄准,光斑落点误差不超过 2 厘米。这听起来不算苛刻,但对一个从零搭建的系统来说,每个环节都是变量。
系统骨架:6 状态有限状态机#
先搭骨架。我设计了包含空闲、寻迹、瞄准等 6 种状态的有限状态机,用一个全局标志位来解耦各模块,确保状态之间不会互相冲突。
按键交互上,我加了 200ms 的软件消抖。这件事看起来很小,但在实际比赛里非常关键——人手一按机械按键,触点会弹跳几十毫秒,如果不做消抖,状态机就会在这几十毫秒内来回跳好几次,整个系统逻辑就乱了。
stateDiagram-v2
[*] --> IDLE
IDLE --> TRACE: 按键1 / 寻迹指令
IDLE --> AIM: 按键2 / 瞄准指令
IDLE --> CIRCLE: 按键3 / 画圆指令
IDLE --> CALIBRATE: 校准触发
TRACE --> IDLE: 任务完成 / 停止
TRACE --> ERROR: 巡线丢失 / 超时
AIM --> IDLE: 瞄准完成
AIM --> ERROR: 视觉通信超时 / 云台卡死
CIRCLE --> IDLE: 画圆完成
CIRCLE --> ERROR: 电机丢步 / 位置超限
CALIBRATE --> IDLE: 校准完成
ERROR --> IDLE: 错误恢复 / 手动复位
note right of IDLE: 全局标志位解耦各模块
note right of ERROR: 异常自动切换,防止逻辑死锁
// 任务状态枚举
typedef enum {
TASK_IDLE = 0, // 空闲状态
TASK_TRACE, // 寻迹模式
TASK_AIM, // 瞄准模式
TASK_CIRCLE, // 画圆模式
TASK_CALIBRATE, // 校准状态
TASK_ERROR // 错误状态
} Task_State_t;
// 按键消抖:200ms 延时确认
uint8_t Key_Debounce(GPIO_TypeDef* port, uint16_t pin) {
if (HAL_GPIO_ReadPin(port, pin) == GPIO_PIN_SET) {
HAL_Delay(200); // 等待触点稳定
if (HAL_GPIO_ReadPin(port, pin) == GPIO_PIN_SET) {
return 1; // 确认有效按下
}
}
return 0;
}c精度攻关:51200 脉冲/转 + 位置式 PID#
然后攻精度。步进电机我配置到 51200 脉冲/转的高细分模式——这意味着每个脉冲只转 0.007 度。这样做是为了抑制低频振动:步进电机在低速运行时会产生明显的步进感,细分越高,单步角度越小,运动越平滑。代价是相同转速下需要的脉冲频率更高,但 STM32 的定时器完全能应付。
控制算法用的是位置式 PID,角度闭环。
// PID 控制器
typedef struct {
float Kp, Ki, Kd;
float integral;
float prev_error;
} PID_Controller_t;
float PID_Calculate(PID_Controller_t* pid, float setpoint, float measured) {
float error = setpoint - measured;
pid->integral += error;
float derivative = error - pid->prev_error;
pid->prev_error = error;
return pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative;
}c标定那天,我把激光指示器固定在云台上,反复测。第一次跑完,光斑落在靶心外 3 厘米多,不符合赛题 2 厘米的要求。回去检查,发现 PID 参数偏保守——Kp 太小,响应不够快,云台还没转到目标位置就”以为”自己到了。调参、再测、再调。最后一次测试,光斑落在距靶心 1.6 厘米的位置,2 秒内完成。我盯着那个红色光点看了十几秒,觉得这两个月的所有代码、所有调试、所有深夜,都值了。
通信协议:三道保险,零误码#
云台和视觉模块之间用 UART 通信——115200bps 波特率。视觉模块负责识别靶心位置,把坐标通过串口发给云台,云台再执行瞄准动作。
但联调初期,遇到了一个让人抓狂的问题:数据包时灵时不灵。有时坐标过来了,云台秒到位;有时云台像瞎了一样,一动不动。排查后发现是丢包——不是偶尔丢,是随机、无规律地丢。
115200bps 在 UART 里算是比较高的速率了。速度快了,数据线上的噪声就多,如果接收端没有严格的校验机制,一个字节错了,后面的数据就全乱。
我给通信协议加了三道保险:
- 帧定界:帧头 0xAA、帧尾 0xBB,用两个固定的字节把一包数据框起来。任何一上来就对不上帧头的数据,直接扔掉。
- 累加和校验:把数据域里每个字节加在一起,最后补一个校验字节,发给接收端对照。和值对不上,这一包也丢掉。
- 解析状态机:逐字节检查数据流,确保每一包数据都是完整、合法的。不是在主循环里随手读几个字节了事,而是一个状态驱动的解析流程。
// 数据包格式
typedef struct {
uint8_t header; // 帧头 0xAA
uint8_t cmd_type; // 命令类型
int16_t target_x; // 靶心 X 坐标
int16_t target_y; // 靶心 Y 坐标
uint8_t status; // 云台状态
uint8_t checksum; // 累加和校验
uint8_t tail; // 帧尾 0xBB
} Vision_Packet_t;csequenceDiagram
participant V as 视觉模块 (OpenMV)
participant U as UART 总线
participant G as 云台模块 (STM32)
V->>V: 识别靶心,计算坐标 (x, y)
V->>U: 发送 [0xAA][cmd][x][y][status][chk][0xBB]
Note over U: 115200bps 传输中
U->>G: 接收字节流
alt 帧头校验失败
G->>G: 首字节 ≠ 0xAA → 丢弃,等待下一帧
else 帧尾校验失败
G->>G: 末字节 ≠ 0xBB → 丢弃,等待下一帧
else 累加和校验失败
G->>G: 数据域和 ≠ chk → 丢弃,请求重传
else 三道保险全过
G->>G: 解析状态机提取坐标
G->>G: 位置式 PID 计算云台转角
G->>G: 步进电机执行瞄准
G-->>U: ACK [0xAA][0x02][ack][chk][0xBB]
U-->>V: 确认收到
end
加上这三道之后,丢包现象完全消失。后来跑整个比赛流程的时候,通信链路始终是零误码。
机械结构:SolidWorks 建模 + 3D 打印迭代#
云台支架和视觉连接板全部基于 SolidWorks 设计,3D 打印(PETG 材料)快速迭代。优化了重心分布以减小转动惯量——让质量尽量靠近旋转轴,这样电机就不需要克服过大的惯性力矩,瞄起来更快更稳。
转折:电烙铁开中空走线通路#
电赛这道题有一个容易被忽视的坑:云台的 Z 轴——垂直于地面的那个轴——需要做大角度旋转来瞄准。传统的线缆外走方式,转几圈之后电源线和信号线就会绞在一起,越拧越紧,最后要么接触不良,要么直接拉断。
这个问题在调试阶段暴露了出来。当时我们的机械结构已经接近成型,没有时间推倒重来。学长建议加装电滑环——但电滑环意味着重新设计旋转关节、重新建模、重新打印、重新装配。在倒计时以小时为单位的赛场上,这条路基本等于放弃。
摆在面前的是一个两难:要么接受风险,祈祷正式评测时线缆别断;要么给结构动一个”外科手术”——在已经打印好的旋转轴中心,用电烙铁烫出一条中空走线通路。
我选了后者。
这个决定的底气在于,我对 PETG 材料在大二做收纳管家时就已经很熟悉了——它的热熔温度区间、冷却后的强度、容不容易在局部受热时产生裂纹。我在脑子里快速过了一遍:走线孔的直径、位置、距离轴承安装面的安全余量。稍有偏差,这个截面的机械强度就会大幅削弱,高速旋转下可能直接崩解。但如果不做,线缆缠绕问题在评委面前爆发,之前所有精度调试就全白费。
电烙铁下去的时候,我的手很稳。
通路烫出来,走线,上电,跑流程。云台连续转了 3 到 4 圈——线缆纹丝不动,通信链路零误码。最终光斑误差锁定在 1.6 厘米以内。
赛后有人问我,万一烫坏了怎么办。我说:我也想过它会坏。但比起线缆断在评委面前,我宁愿承担这个风险。因为这是我判断过的、可控的、我能兜底的风险。
赛场压力:慌是没用的,方法才是有用的#
电赛的现场和实验室预演完全是两回事。
我们在实验室把流程跑过几十遍了。到了赛场,光线变了,地面材质变了,连靶标的光照条件也变了。本来在实验室能打得很准的光斑,现场连续两次偏了。
计时器在走,比赛不会停下来等你调。
那一刻压力非常大。但我知道,在这种时候最不能做的就是乱动代码。一着急乱改,反而会把原本正确的参数改坏了。
我深吸了一口气,让自己冷静下来。花了大概 15 分钟,先排除机械结构的问题——云台是否能正常响应指令,步进电机有无丢步——再逐项排查视觉通信有没有出现延迟或丢帧,最后确认是不是环境光干扰了激光光斑的识别。排查的过程本身就是在找回节奏,最终锁定了问题,快速调整了几组参数,重新校准,把系统拉回了正常状态。
比赛最终顺利完成。这 15 分钟让我学到的,不是在压力下不紧张,而是即使紧张,也要按部就班地排查。慌是没用的,方法才是有用的。
复盘:做对了什么,做错了什么#
做对了的:
- 赛前训练的价值远超预期。跑 BSP、调串口、纠结齿轮还是直连——这些当时看起来没有”产出”的事,到了赛场上变成了决策的底气和速度。如果赛题发布后才从零开始学,四天根本来不及。
- 通信协议的三道保险。帧定界 + 累加和校验 + 解析状态机,这套组合不复杂,但在赛场上保证了零误码通信。通信是云台和视觉之间的”神经”,它不出问题,上层逻辑才能安心工作。
- 电烙铁决策。对 PETG 材料的熟悉让我有底气在极限压力下做判断。这个决定的正确性可能是有运气的成分,但”敢做判断并为此兜底”的心态,是我在这个项目里最大的成长。
做错了的:
- 没有提前考虑线缆走线问题。Z 轴旋转的线缆缠绕,其实是一个在结构设计初期就应该预见并解决的问题。如果我在设计云台支架时就把走线孔留好,赛场上就不需要用电烙铁冒险。
- PID 参数没有做系统化的标定记录。我在标定过程中调了很多轮参数,但没有认真做好每一轮的参数-误差对应记录。如果能把调参过程数据化,建一个简单的参数响应表,上场后遇到环境变化时可以更快地做出有依据的调整。
- 结构迭代的版本管理太粗糙。3D 打印了好几版支架,但没有给每版编号和记录改动点。到后来已经搞不清哪个版本的哪个零件是最新的——这在赛场上是非常低级的浪费。
可迁移收获#
第一,嵌入式 + 机械的全栈闭环。电赛云台是我把”软”和”硬”同时做到一个系统里的第一次完整实践。从 STM32 底层驱动、通信协议、PID 控制算法,到 SolidWorks 建模、3D 打印结构件、赛场应急改装——我一个人跑通了整个链条。这种跨域能力让我在后续的 AGV 项目中,能同时理解机械层的约束和软件层的需求。
第二,高压下的冷静判断是可以训练的。那 15 分钟的赛场排查和电烙铁决策,让我意识到:紧急状态下的冷静不是因为天生冷静,而是因为平时踩过足够多的坑,知道”出问题后第一步应该做什么”。排查的流程化、版本的可追溯性、参数的文档化——这些平时看起来不重要的事,在高压下就是救命的东西。
第三,兜底意识的觉醒。电烙铁那个决策让我第一次清晰地意识到:我最珍视的自己的一个品质是,在关键时刻愿意做判断并为此负责。我也了解风险,我也计算过代价,但当我觉得这是一个”可控的风险”并且是当前情况下的最优解时,我会选择行动,而不是等待别人来做决定。
初稿写于 2025 年 8 月,2026 年 4 月根据项目复盘更新。