
简介面向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(time2000) sensor.set_auto_gain(False) # 关闭自动增益防止颜色漂移 sensor.set_auto_whitebal(False) # 关闭自动白平衡 uart UART(3, 115200, timeout_char1000) threshold_red (60, 100, 45, 80, 40, 80) # LAB阈值按现场微调 while True: img sensor.snapshot() blobs img.find_blobs([threshold_red], pixels_threshold5, area_threshold5) if blobs: b max(blobs, keylambda 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。当初用QVGA320x240试过识别精度更高但帧率会掉到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来抑制超调。我实测这组参数可以作为起点kp2.0ki0.02kd0.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是否持续发包。阈值红色阈值在比赛房间的灯光下是否依然稳定。标定摄像头位置是否有变化如果动过必须重新标定。电源电池或者电源适配器是否充满、供电是否稳定。备份备用舵机、备用激光笔、备用编程器是否带齐。最后一次测试时我们队伍在竞赛倒数第二天发现绿色云台的底座螺丝松了快一圈这就是抖动很难消除的根源。所以我现在对机械的重视程度远高于算法再好的控制遇到手能晃动的云台都是白搭。如果你准备做类似的自动追踪项目建议从始至终把“稳定”两个字放在第一位软件上多做限幅和保护硬件上把所有活动部件拧死。赛前别光顾着调程序把一个螺丝一个螺丝都过一遍比多跑十次测试都管用。本文还有配套的精品资源点击获取