简介:这份资料面向备战全国大学生电子设计竞赛的参赛学生与指导教师,聚焦2023年E题「基于STM32F1的自动追光云台」的完整实现方案。资源以STM32F1系列微控制器为核心,涉及光敏传感器检测、步进/伺服电机控制、PID算法调节、电源管理与机械结构设计等关键环节,适合具备一定嵌入式基础、希望系统梳理赛题思路的读者参考。压缩包共199个文件,约6.74MB,以C源码与头文件为主体,辅以工程配置、编译中间文件及少量说明文档,完整保留了Keil工程结构,便于直接打开编译与二次修改。目前已有7797人学习下载,热度较高。读者可从中获取传感器数据采集、电机驱动、控制算法与工程调试的完整代码框架,对照赛题要求理解自动追光云台的设计逻辑,并借鉴其模块划分与排错思路,提升备赛效率与工程实践能力。
1. 电赛E题到底考什么:从“运动目标控制与自动追踪”看拿分逻辑
很多人第一次看到电赛E题,以为又是考小车、考云台、考PID,结果一上手才发现,这题真正卡人的地方是“两个系统怎么配合”。2023年E题的核心是运动目标控制与自动追踪,红色激光笔负责打目标,绿色激光笔负责追目标,两个云台各管各的,但任务要求它们在同一块屏幕上完成闭环。换句话说,这不是单考控制算法,而是考你对“视觉反馈+舵机控制+系统协同”的整体把握。拿分的关键不在某一项技术多深,而在基础部分能不能稳、发挥部分能不能快。适合已经做过至少一轮控制类题目的同学,也适合想靠这道题补足“系统联调”经验的备赛者。
这道题的分值分布很典型:基础部分占大头,主要考激光笔能不能按指令打到指定位置、能不能画简单图形;发挥部分才是真正的分水岭,要求绿色激光笔实时追踪红色激光笔的移动轨迹。很多队伍基础部分拿满,发挥部分直接翻车,原因不是算法不会,而是没搞清楚“追踪”和“跟随”的区别。追踪要求的是实时性,跟随只要最终到位就行。面试或答辩时,评委最想听的是你怎么定义误差、怎么处理延迟、怎么保证两个云台不打架。这些点,下面几章会拆开讲。
2. 高频考点:视觉识别与坐标映射的四个必考环节
2.1 红色激光点识别:阈值分割不是万能药
E题第一个坑就是红色激光点的识别。很多队伍上来就用HSV阈值分割,结果发现环境光一变,阈值就废了。正确的做法是先把摄像头曝光调低,让激光点成为画面里最亮的区域,再用亮度+颜色双条件筛选。下面这段伪代码展示的是典型处理流程:
# 假设已经读取到一帧图像 frame hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # 红色在HSV里跨两个区间,别只写一个 mask1 = cv2.inRange(hsv, (0, 100, 100), (10, 255, 255)) mask2 = cv2.inRange(hsv, (170, 100, 100), (180, 255, 255)) mask = mask1 + mask2 # 形态学去噪,激光点小,核别开太大 kernel = np.ones((3, 3), np.uint8) mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) # 找轮廓,取最大轮廓的质心 contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: c = max(contours, key=cv2.contourArea) M = cv2.moments(c) cx = int(M['m10'] / M['m00']) cy = int(M['m01'] / M['m00'])这段代码的关键参数是HSV的下界。饱和度低于100的基本可以滤掉,亮度低于100的也滤掉。形态学开运算的核用3×3就够了,用5×5会把小激光点直接吃掉。质心计算前要判断M['m00']不为零,否则会除零报错。面试官如果问你“为什么不用帧差法”,你要能答出来:帧差法适合运动目标,但激光点是静止或慢速移动的,帧差反而会引入拖影。
2.2 坐标映射:从像素到舵机角度的非线性问题
识别到激光点坐标后,下一步是把它映射成舵机角度。很多队伍直接写线性映射,结果发现边缘位置总是打不准。原因是摄像头有畸变,云台也有机械安装误差。正确的做法是至少做一次四点标定,用双线性插值代替线性映射。下面是一个简化的标定表:
| 像素坐标 | 舵机X角度 | 舵机Y角度 |
|---|---|---|
| (100,100) | 30 | 45 |
| (540,100) | 150 | 45 |
| (100,380) | 30 | 135 |
| (540,380) | 150 | 135 |
标定的时候注意:四个点要覆盖屏幕的四个角,不要只标中间。标完之后用双线性插值算任意点的角度,误差能控制在1度以内。如果评委问“为什么不用透视变换”,你可以说透视变换适合平面到平面的映射,但舵机角度和像素坐标不是严格的平面关系,双线性插值更稳。
2.3 真题演练:基础部分“打点画图”怎么答
基础部分常考的一道题是:让红色激光笔依次打到屏幕上的A、B、C三点,然后画一个边长10厘米的正方形。答题思路分三步:第一步,先做坐标标定,把屏幕上的物理位置换算成像素位置;第二步,用PID控制舵机走到目标点,注意积分限幅,否则超调会很大;第三步,画图的时候用插值生成中间点,不要直接跳过去。面试官最想听的是你怎么处理“到位判断”——是等误差小于阈值再发下一个点,还是用时间延时。建议用误差阈值,延时不可靠。
2.4 真题演练:发挥部分“追踪”怎么答
发挥部分要求绿色激光笔追踪红色激光笔。这道题的答题模板是:先识别红点,再识别绿点,然后计算两者偏差,最后用增量式PID控制绿点云台。注意两个细节:一是红点移动快的时候,摄像头帧率要跟上,建议用全局快门摄像头;二是绿点云台的运动要加前馈,否则永远滞后。面试官如果问“追踪和跟随的区别”,你要能说清楚:追踪是实时闭环,跟随是开环或半闭环。这道题拿分的关键是延迟控制,不是算法复杂度。
3. 易错点:五个让评委直接扣分的操作
3.1 错在哪:用串口打印调试信息
很多队伍在联调的时候习惯用串口打印激光点坐标,结果发现追踪延迟特别大。为什么错?因为串口打印是阻塞操作,每打印一次就要等几毫秒,帧率直接掉到10帧以下。怎么答对:用OLED小屏显示关键数据,或者用无线模块把数据发到上位机,主控只负责控制。如果评委问“你怎么保证实时性”,你要能说出“控制周期小于5毫秒,视觉处理周期小于20毫秒”。
3.2 错在哪:PID参数一套用到底
有的队伍调好一组PID参数后,不管打点还是追踪都用同一套。为什么错?打点要求无超调,追踪要求响应快,两者的参数需求是矛盾的。怎么答对:打点用位置式PID,积分分离;追踪用增量式PID,加大微分项。面试的时候主动说“我准备了两套参数,根据任务切换”,评委立刻知道你有实战经验。
3.3 错在哪:忽略舵机死区
舵机都有死区,一般1到2度。有的队伍发现激光点总是在目标附近晃,就是死区导致的。为什么错?因为PID输出小于死区时舵机不动,积分项会一直累积,最后突然跳一下。怎么答对:在PID输出后加死区补偿,或者用积分限幅。面试官如果问“怎么消除稳态误差”,你要能答出“死区补偿+积分分离”。
3.4 错在哪:摄像头和云台不同步
有的队伍摄像头采样和云台控制放在同一个循环里,结果视觉处理一慢,云台就卡顿。为什么错?因为视觉处理耗时不稳定,会拖累控制周期。怎么答对:用双线程或定时器中断,视觉处理和控制分开跑,中间用共享内存或队列传数据。评委最想听的是“你怎么保证控制周期的确定性”。
3.5 错在哪:不做异常处理
激光点丢失、屏幕反光、舵机堵转,这些异常不处理,程序直接跑飞。为什么错?因为比赛现场环境不可控,异常是常态。怎么答对:加超时检测,激光点丢失超过100毫秒就进入搜索模式,舵机堵转就切断输出。面试的时候主动提“我加了看门狗和状态机”,评委就知道你考虑周全了。
4. 答题思路:从审题到答辩的完整推演
4.1 审题阶段:先分清基础部分和发挥部分的边界
拿到题目后,不要急着写代码。先花20分钟把任务书拆成三张表:第一张是输入输出表,写清楚每个任务需要什么信号、输出什么动作;第二张是时间表,写清楚每个任务的时限;第三张是分值表,写清楚每项多少分。这样你就能判断哪些任务是必须拿满的,哪些是可以放弃的。比如基础部分的打点画图,分值高且确定性好,必须拿满;发挥部分的追踪,分值高但不确定性大,要留足调试时间。
4.2 方案设计:用状态机代替流水账
很多队伍的代码是流水账,从上到下写,结果一改需求就全乱。正确的做法是用状态机。下面是一个简化的状态机框架:
typedef enum { STATE_IDLE, STATE_CALIBRATE, STATE_DRAW_POINT, STATE_DRAW_SHAPE, STATE_TRACK, STATE_ERROR } SystemState; SystemState current_state = STATE_IDLE; void state_machine() { switch (current_state) { case STATE_IDLE: if (start_button_pressed()) { current_state = STATE_CALIBRATE; } break; case STATE_CALIBRATE: if (calibration_done()) { current_state = STATE_DRAW_POINT; } break; // 其他状态类似 } }这个框架的好处是每个状态只做一件事,调试的时候可以单独测试。面试官如果问你“怎么保证代码可维护”,你就把状态机图讲一遍。
4.3 调试阶段:先开环再闭环
调试的时候不要一上来就闭环。先把舵机开环控制,手动给角度,看激光点能不能打到预期位置。开环调好了,再加视觉反馈。这样出问题的时候你能快速定位是机械问题还是算法问题。很多队伍一上来就闭环,结果激光点乱飞,根本不知道是识别错了还是PID参数不对。
4.4 答辩阶段:用数据说话
答辩的时候不要只说“我用了PID”,要说“我用了增量式PID,控制周期5毫秒,超调小于5%”。评委最喜欢听具体数字。如果问你“为什么选这个方案”,你要能说出对比过程:比如“我试过线性映射和双线性插值,后者误差从3度降到1度”。这样评委就知道你是真的做过实验,不是背答案。
5. 押题思路:2023年E题最可能被问到的三个问题
5.1 “你的追踪延迟是多少,怎么测的?”
这个问题几乎必问。答题模板:先说延迟定义,从红点移动到绿点跟上的时间差;再说测量方法,用高速相机拍或者用示波器测信号;最后说优化手段,比如提高帧率、加前馈、用预测算法。如果你能说出“延迟控制在50毫秒以内”,评委基本就满意了。
5.2 “如果红色激光笔突然消失,你的系统怎么处理?”
这是考异常处理。答题模板:先检测丢失,用连续N帧找不到红点作为条件;再进入搜索模式,让绿点云台按螺旋轨迹扫描;最后如果超时还没找到,就回到原点并报警。面试官最想听的是“我有状态机,不会跑飞”。
5.3 “两个云台会不会互相干扰?”
这是考系统协同。答题模板:先说机械上怎么隔离,比如两个云台分开供电、分开固定;再说软件上怎么分时,比如红点识别和绿点识别交替进行,避免同时占用摄像头;最后说如果干扰了怎么检测,比如看电流波动。评委如果追问“你怎么保证同步”,你就说“我用同一个定时器触发两个云台的控制周期”。
5.4 一个具体方法:用“误差带”代替“精确到位”
追踪的时候不要追求零误差,那样会震荡。正确做法是设一个误差带,比如±2度,进入误差带就保持当前角度,出了误差带再调。这样既稳又省时间。下面是一个简单的实现:
#define ERROR_BAND 2.0 // 度 void track_control(float target_angle, float current_angle) { float error = target_angle - current_angle; if (fabs(error) < ERROR_BAND) { // 在误差带内,不调整 return; } // 否则用增量式PID调整 float output = incremental_pid(error); set_servo_angle(current_angle + output); }这个方法的参数是误差带宽度,一般取舵机死区的2到3倍。太窄会震荡,太宽会跟不上。调试的时候先用宽误差带,再慢慢收窄。
从那以后我每次做控制类题目,都强制先画状态机、再标定、最后调PID,顺序错一步就翻车。希望帮到你。
本文还有配套的精品资源,点击获取