简介:面向2023年全国大学生电子设计竞赛E题备赛者,这份压缩包围绕“基于STM32F1的自动追光云台”提供了完整工程与源码参考。包内包含STM32F10x系列外设驱动(如ADC、I2C、USART、定时器)的C语言源文件及头文件,附带Keil工程配置、编译中间文件、链接脚本与烧录文件,可直接用于评估或二次开发。资源共199个文件,以.c/.h源码、编译生成的.o/.d/.crf文件及Keil工程文件为主,整体体积仅6.74MB,结构清晰便于检索。已有7735人学习下载,说明内容具备较高参考价值。对于正在备赛或希望理解自动追光云台系统实现的读者,该包能提供从电机控制、传感器数据采集到主控逻辑的工程化范例,帮助快速上手实践。 2023年的全国大学生电子设计竞赛e题“运动目标控制与自动追踪系统”,在当年属于视觉和运动控制结合的重头戏。题目本身不复杂:搭建两套二自由度云台,一套让红色激光按指定轨迹移动作为运动目标,另一套让绿色激光自动跟踪红点。但实际做下来,大家发现“追得上”和“追得稳”完全是两个世界。很多队伍基础题能跑通,发挥部分一加速就露馅,绿激光不是原地画圈就是追到纸外。我陪队伍从校赛到国赛一路调下来,把视觉识别、坐标标定、PID控制这些核心环节都摸了一遍,这篇文章就把可复现的方案、关键参数、现场踩坑记录全部放出来,给后面参赛、做自动追踪类课设或者单纯想玩云台视觉的同学当参考底稿。
1. 题目解读与整体方案选择
1.1 基础题和发挥题到底考什么
先把题目要求拆清楚。2023年电赛e题的基础部分要求在两维云台上架设红、绿两个激光笔,红色激光模拟运动目标,能在规定时间内沿指定轨迹(比如白纸边界或给定图形)移动;绿色激光作为追踪器,需要在红点运动过程中保持跟踪,误差控制在一定范围内。发挥部分在此基础上加大难度,要么要求目标运动速度更高,要么要求轨迹更复杂(比如斜线、折线甚至任意手绘轨迹),有些赛区还加了“识别到追踪点后自动回到画面中心”的视觉伺服任务。
这个题目表面上是“做两个云台”,核心考察点其实是三块:第一是视觉识别,要稳定提取激光点位置,这是整个系统的眼睛;第二是坐标映射,把摄像头看到的像素坐标换算成云台的控制角度;第三是闭环控制,让绿色云台实时跟随红点,又快又稳。机械结构反而是最简单的一环,但恰恰是很多队伍在机械上翻车,云台松动、激光不垂直纸面,后边怎么调算法都白搭。
从拿分策略来讲,基础题只要保证“能走轨迹、能跟上”就能拿大部分分数,发挥部分拼的是动态响应和稳定性。所以方案设计时不要在奇怪的地方炫技,走成熟路线才是王道。
1.2 整体系统怎么搭:一个视觉伺服闭环
我最后定下来的整体方案是这样的:两套独立的二自由度云台并排放置在纸面两侧,云台分别由偏航舵机和俯仰舵机驱动,云台上固定红、绿激光笔。纸面上方固定一个摄像头,垂直俯瞰整个A4纸区域。主控分两级:视觉前端(摄像头加图像处理)负责识别红色激光点的像素坐标,通过串口发给主控;主控根据预先标定好的映射关系,把红点的像素坐标换算成绿色云台应该转到的角度,再用PID算法驱动绿色云台的舵机,让绿色激光始终压住红色激光。
这个架构本质上是一个典型的视觉反馈闭环:摄像头是传感器,主控是控制器,云台舵机是执行器,纸面上的光斑位置是被控量。红色部分是一个开环轨迹发生器,程序控制红色云台按规划好的轨迹移动;绿色部分是一个视觉闭环跟踪器。两个系统之间唯一的联系就是画面中的红点位置。
这样设计有三点好处。一是模块化,视觉、控制、机械分开调试,哪块出问题就单独排查;二是视觉前端独立,可以实现20ms左右一帧的识别速度,不给控制回路拖后腿;三是对硬件要求不高,不需要高性能嵌入式平台,用常见的STM32加摄像头模块就能跑起来。
1.3 方案选型:为什么是“OpenMV + STM32 + 双舵机云台”
最常见的三条实现路线分别是:用OpenMV这类带图像处理的单片机做视觉识别,再配STM32做主控;用树莓派这类Linux板卡做完整图像处理加控制;用K210等AI芯片跑目标检测。我实测下来,最适合电赛场景的是第一种。
树莓派方案开发效率高,Python代码写起来很顺手,但现场测试时经常会遇到两个问题:一是启动慢,开机电赛测试那几分钟尤其煎熬;二是供电稳定性差,配USB摄像头时画面会周期性掉帧,直接导致追踪滞后。K210跑目标检测没问题,但激光点这种小目标用传统颜色阈值法就够了,杀鸡用牛刀,而且部署SDK对新手不友好。
OpenMV的方案成熟,社区资料多,颜色识别、坐标输出这些都是现成的,而且OpenMV带LED补光和可调曝光,能在现场光线变化时做一定补偿。STM32则负责读坐标、跑PID、输出PWM驱动舵机,实时性好,现场调参也方便。OpenMV和STM32之间的通信用串口,波特率115200,一帧发9个字节,包括帧头、x坐标、y坐标、校验位,实测下来比较稳定,没出现过粘包问题。坐标频率尽量保持在50Hz以上,否则PID响应会明显迟钝。
2. 核心硬件选型与机械结构设计
2.1 主控、摄像头和舵机怎么选
这里直接给一个我实测过、可以照抄的硬件清单表格:
| 模块 | 型号 | 备注 |
|---|---|---|
| 视觉模块 | OpenMV Cam H7 Plus | 内存大,高分辨率不卡顿 |
| 主控 | STM32F103C8T6 | 跑PID完全够用 |
| 舵机 | DS3218数字舵机 | 扭矩大、响应快、线性度好 |
| 摄像头 | OpenMV内置/外接OV5640 | 外接可调焦距更灵活 |
| 供电 | 5V/5A开关电源 | 舵机单独一路,避免干扰 |
舵机这块多说两句。我用了两套DS3218数字舵机,每个云台各两个。DS3218扭矩大、响应快,关键是价格比较友好。MG996R模拟舵机也可以用,但有一个坑:模拟舵机的线性度差,在中位附近有死区,追踪时容易在小范围内来回抖动。后文会讲怎么通过软件死区解决,但硬件上还是建议数字舵机。
2.2 云台结构和激光安装要点
云台结构可以用成品两自由度云台支架,或者3D打印一个。需要注意两点:一是云台重心尽量低,舵机臂越短越好,否则高速摆动时整个云台会晃,直接影响激光指向;二是两个云台之间的距离和角度要固定,最好做一块底板把两个云台底座锁死。我用的是亚克力底板加螺丝固定,整个测试过程中几乎没位移。
激光笔的安装往往被忽略,但这里有个特别关键的细节:激光笔必须与云台转轴保持垂直,也就是让激光出射方向尽量垂直于云台的俯仰轴。如果激光笔斜着固定,云台转动角度和光斑移动距离之间就不是线性关系,标定会非常痛苦。固定方式我推荐用热熔胶加一个小的3D打印固定座,不要直接用胶带缠,胶带受热会松,比赛现场最怕激光歪。
2.3 供电系统设计:别让重启毁掉测试
这个板块不太起眼,但出问题特别多。两个DS3218舵机加一个OpenMV,如果全部从一个5V电源取电,启动瞬间电流会飙到2A以上,劣质稳压芯片会被拉垮,OpenMV会频繁重启。我最后用了两路独立供电:OpenMV用一个5V/1A的隔离模块,舵机用一个5V/5A的开关电源,并且舵机电源地线要单独拉回,不要跟信号线共用地线回路。
信号线上还有一个容易踩的坑:OpenMV的串口TXD、RXD和STM32连接时,电平都是3.3V,但如果STM32板子带5V电平的串口芯片,比如CH340,一定要先确认电平匹配。我曾经因为直接连到CH340的5V引脚把OpenMV的串口烧了,后来加了一个电平转换模块才解决。接线时记得所有模块共地,逻辑电平要一致。
3. 图像识别与坐标系标定
3.1 激光点识别:颜色阈值怎么调才稳定
激光点在摄像头画面里通常是一个很小的亮点,可能有几像素到几十像素。识别思路是:先用颜色阈值把红色区域分离出来,再找最大连通域的重心作为激光点坐标。OpenMV里最常用的是LAB颜色空间,因为LAB的亮度分量L跟色度分量A、B分开,在不同光照下比RGB稳定。
这里贴一段我在OpenMV上跑的核心识别代码,可以直接作为模板:
import sensor, image, time from machine import UART sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QQVGA) # 160x120,帧率优先 sensor.skip_frames(time=2000) sensor.set_auto_gain(False) # 关闭自动增益,防止颜色漂移 sensor.set_auto_whitebal(False) # 关闭自动白平衡 uart = UART(3, 115200, timeout_char=1000) threshold_red = (60, 100, 45, 80, 40, 80) # LAB阈值,按现场微调 while True: img = sensor.snapshot() blobs = img.find_blobs([threshold_red], pixels_threshold=5, area_threshold=5) if blobs: b = max(blobs, key=lambda x: x.pixels()) img.draw_cross(b.cx(), b.cy(), color=(0, 255, 0)) data = bytearray([0xAA, (b.cx() >> 8) & 0xFF, b.cx() & 0xFF, (b.cy() >> 8) & 0xFF, b.cy() & 0xFF, 0x55]) uart.write(data) else: uart.write(bytearray([0xAA, 0, 0, 0, 0, 0x55]))图像分辨率我设置成QQVGA,也就是160x120。当初用QVGA(320x240)试过,识别精度更高,但帧率会掉到30fps左右,对PID控制来说不够用。QQVGA下帧率能稳定在50fps以上,识别延迟小很多,配合后续的PID效果反而更好。像素分辨率低的误差可以通过标定环节弥补,但帧率低造成的滞后很难通过算法救回来。
阈值调节这块,我推荐的做法是现场对着目标环境反复测,把激光关闭和开启各截一张图,用OpenMV IDE的“阈值编辑器”工具直接看LAB直方图然后微调。另外一定要关掉摄像头的自动增益和自动白平衡,否则测光环境变化时画面会突然整体变色,颜色阈值直接失效。
3.2 像素坐标到云台角度的映射
这是整个e题里最核心也最容易翻车的地方。摄像头给出的红点坐标是像素值,而绿色云台需要知道自己应该转到什么角度,才能让绿色激光打到红点位置。直观思路是线性映射:比如图像宽度是160像素,对应云台左右各30度,那差一像素就是0.375度。但实际做出来误差很大,因为摄像头安装在纸面上方,有透视效应,图像边缘一个像素对应的实际距离比中心大;再加上镜头畸变,边界处偏差可能达到一两厘米。
我的解决方法是标定查表加双线性插值。具体操作分三步:
第一步,固定绿色云台和摄像头,让绿色激光正对纸面某个已知位置,记录此时云台的两个舵机角度和图像中绿色光斑的像素坐标。
第二步,在纸面上用网格线划分,比如每隔2cm打一个点,描出一张5x8的点阵。对每个点,手动控制绿色云台让绿光打到该点,记录对应的两个舵机角度和该点在图像中的像素坐标。这样得到一组标定数据:像素坐标和舵机角度的一一对应。
第三步,把这些数据存入STM32的查找表。运行时,红点像素坐标进入系统,先在查找表里找到最近的几个标定点,做双线性插值,算出绿色云台的目标角度。
用公式表示就是:假设标定数据是(u_i, v_i)对应角度(pan_i, tilt_i),对任意红点像素(up, vp),找到它周围的四个标定点,分别在u方向和v方向做线性插值。实际效果很好,整个A4纸范围内追踪误差能控制在1cm以内。标定数据一定要覆盖纸面的四个角和中间区域,边缘采样点不够会导致追踪到边界时误差明显变大。
我也试过用多项式拟合做映射,效果也不错,但有一个前提:摄像头的安装角度不能变。一旦测试时撞了一下摄像头,整个拟合函数就废了,必须重新标定。查表法虽然标定量大一点,但抗干扰性好,摄像头位置略有偏移时只要重新采几个点就能修正。
3.3 摄像头安装高度和视角怎么选
摄像头的安装对后续标定影响很大。我建议摄像头垂直正对纸面中心,高度根据纸面大小调整,让纸面四角都在画面内,最好留出一圈10%的余量。如果摄像头装歪了,有比较大的透视角度,虽然标定仍然能补偿一部分,但激光点识别时,纸面边缘的激光很容易被纸张厚度或者阴影遮挡。
一个小技巧:在纸面四角贴上黑色标记,程序识别时先检测这四个角,用来做透视矫正和动态补偿。如果测试过程中摄像头轻微移动,通过四个角的位置变化就能估计出变换矩阵并修正坐标。这个功能在正式比赛那种高压环境下很有用,因为现场难免会碰到摄像头。
4. PID控制与调参实战
4.1 控制回路怎么设计
绿色云台的追踪本质是一个位置伺服系统。给定目标角度(由红点像素坐标映射得到),需要让云台舵机快速到达并稳定在那个角度。由于舵机内部本身有位置闭环,给舵机的PWM信号其实就对应目标角度,整个外环就简化为:视觉检测红点像素坐标,查表得到目标角度,PID计算输出PWM增量,驱动云台。
我使用位置式PID,控制周期设在20ms左右。这里贴一段STM32上的核心实现:
typedef struct { float target_angle; float current_angle; float error; float integral; float prev_error; float kp, ki, kd; float out; } PID_t; void PID_Calc(PID_t *pid, float target, float current) { pid->target_angle = target; pid->current_angle = current; pid->error = target - current; pid->integral += pid->error; // 积分限幅,防止长时间跑偏产生大积分 if (pid->integral > 100) pid->integral = 100; if (pid->integral < -100) pid->integral = -100; float derivative = pid->error - pid->prev_error; pid->out = pid->kp * pid->error + pid->ki * pid->integral + pid->kd * derivative; pid->prev_error = pid->error; }输出最终被转换成PWM脉宽发给舵机。注意,位置式PID的输出如果直接作为绝对值给舵机,积分项可能会导致上电瞬间舵机猛转,实际应用时建议对输出做限幅,转换到PWM时再做平滑处理。
4.2 PID参数整定流程
整定PID参数是有套路的。先只用P,从小到大慢慢加,观察绿激光对红点的跟随情况。如果红点在某一位置轻微移动,绿点能跟上但不稳定地振荡,说明P偏高;如果反应迟钝、追不上,说明P偏低。P调好后加少量I来消除静态误差,最后加一点D来抑制超调。
我实测这组参数可以作为起点:kp=2.0,ki=0.02,kd=0.5。每个队伍机械结构不同,参数差别会很大,千万别照抄,一定要在自己的装置上重新整定。整定过程中,可以让红点以固定速度走直线或者画圆,观察绿点的最大偏差和相位滞后,这是最直观的评价方式。
有个细节很重要:D项在系统有噪声时容易放大抖动。激光点识别本身会有1-2像素的噪声,表现在角度上就是一个小量,D项对这个噪声特别敏感。我的做法是把误差做一阶低通滤波之后再算微分,能显著减小抖动,而且不影响对真实运动的响应。
4.3 追踪速度和精度怎么平衡
发挥部分要求目标运动更快,这时候单纯加大P值会导致系统振荡,必须在控制策略上想办法。我有两个经验。
第一,对视觉延迟做简单补偿。摄像头识别和串口通信会产生约20ms到40ms的延迟,这个延迟在高速运动下会引起明显的相位滞后。可以在PID之前加一个预测器:把最近几帧红点目标角度存下来,做线性预测,估算出当前时刻红点大概的角度,再把预测角度作为PID目标。公式大概是:预测角度 = 当前角度 + (当前角度 - 上一帧角度) / 帧间隔 * 延迟时间。实测对追踪效果提升明显,代码量也很小。
第二,对舵机输出做速度限制。舵机虽然允许大幅度PWM跳变,但机械机构不可能瞬间到达目标,如果输出跳变太猛,反而会造成“过冲-回摆-再过冲”的死循环。我在PID输出后加了一级限速,让目标角度每秒变化不超过某个上限。这个上限要根据云台实际响应能力来定,太高了没用,太低了跟不上,我最后设在每秒60度左右。
4.4 红色云台的轨迹控制
红色云台这边的控制相对简单,不需要视觉反馈,只要按规划好的轨迹给出目标角度,再用同样的PID让它平滑运动就行。轨迹可以用参数方程生成,比如画矩形时分成四段直线,每段按时间均匀插入角度点;画圆时按角度步进生成一系列点。这里同样建议做插值,不要让两个目标角度直接跳变,否则红激光会飞出纸面。
这部分的调参经验和前面类似,唯一要注意的是,红色云台的运动速度要保持均匀,不要忽快忽慢。因为绿色云台是在跟踪红点的,如果红点突然加速,绿点很容易产生较大超调。
5. 常见问题与排查实录
5.1 绿激光原地抖个不停
这是现场出现最多的故障。检查顺序是:先看颜色识别是否稳定,用OpenMV IDE观察红点坐标,如果坐标在固定位置来回跳,说明是识别噪声问题,可以通过增大pixels_threshold、对坐标做滑动平均滤波解决。然后再看PID,把D项暂时调成0,如果抖动消失,说明D项放大了噪声,需要加低通滤波或者减小D。
还有一个容易被忽略的点:舵机自身的死区。模拟舵机在目标角度附近存在一个不响应区域,PID输出在一个小范围内来回变化时,舵机就会在死区边界反复颤动,发出“嗞嗞”声。解决办法是设置软件死区:当误差绝对值小于某个阈值,比如0.5度时,PID输出保持上一拍不变。
5.2 红点不动时绿点缓慢漂移
出现缓慢漂移,大概率是积分项在作怪。舵机本身没有外部持续干扰,位置式PID的积分项主要用来消除静差,但如果不做限幅,长时间运行后积分会积累到很大,导致输出慢慢偏移。我处理的方法是给积分加限幅,并且当误差小于一定范围时停止积分累加。这个小改动之后,静态稳定性立即提升。
5.3 追踪到纸面边缘时误差突然变大
这个问题几乎每位同学都碰到过。原因有三层:镜头畸变、透视效应、激光倾斜。镜头畸变会让图像边缘的像素误差变大,这可以通过标定数据覆盖边缘来补偿;透视效应上文已经说过,通过双线性插值能兜住大部分;激光倾斜是指云台转到极限角度时,激光不是垂直入射纸面,光斑被拉长变形,颜色识别找到的重心会偏离真实落点。第三点没法完全消除,只能在机械上尽量让云台靠近纸面中心,减小极端角度。
5.4 现场灯光一暗红点就找不到了
电赛测试现场灯光情况很复杂,有时候评委把环境光调暗,激光点反而更清楚,但有时候红色激光被环境光淹没,OpenMV识别失败。我的经验是激光器选650nm波长、功率稍微大一点的,5mW的激光笔在现场环境下表现一般。同时OpenMV的曝光时间要手动固定,不要用自动曝光。如果还是不行,可以在摄像头前面加一个红色滤光片,能极大提高信噪比。滤光片用那种几十块钱的红色带通片就行,实测效果非常明显。
5.5 赛前检查清单
整理一份每次测试前必过的清单:
- 螺丝:所有云台和激光笔固定座螺丝是否松动。
- 通信:串口是否正常,开机后OpenMV是否持续发包。
- 阈值:红色阈值在比赛房间的灯光下是否依然稳定。
- 标定:摄像头位置是否有变化,如果动过必须重新标定。
- 电源:电池或者电源适配器是否充满、供电是否稳定。
- 备份:备用舵机、备用激光笔、备用编程器是否带齐。
最后一次测试时,我们队伍在竞赛倒数第二天发现绿色云台的底座螺丝松了快一圈,这就是抖动很难消除的根源。所以我现在对机械的重视程度远高于算法,再好的控制遇到手能晃动的云台都是白搭。如果你准备做类似的自动追踪项目,建议从始至终把“稳定”两个字放在第一位,软件上多做限幅和保护,硬件上把所有活动部件拧死。赛前别光顾着调程序,把一个螺丝一个螺丝都过一遍,比多跑十次测试都管用。
本文还有配套的精品资源,点击获取