1. 项目概述与赛题核心解读2023年全国大学生电子设计竞赛电赛的E题题目是“运动目标控制与自动追踪系统”。这个题目一出来当时就在我们参赛圈子里炸开了锅因为它完美地踩在了当前几个技术热点的交叉口上机器视觉、运动控制和嵌入式系统。简单来说就是让你做一个能自己“看”到移动目标并且能控制一个云台上面装着激光笔去实时瞄准、追踪这个目标的装置。听起来是不是有点像电影里的自动瞄准没错其核心逻辑就是一套简易的、实时的闭环反馈系统。这道题的魅力在于它把一个复杂的工程问题拆解成了几个非常具体、可实现的模块但又留足了发挥空间。它不要求你用多高端的硬件核心就是一块常见的微控制器比如STM32、一个摄像头模块如OpenMV、K210或者树莓派搭配摄像头、一个二维舵机云台再加上一个激光笔。硬件门槛相对亲民但软件和算法上的挑战直接拉满。你需要解决图像采集、目标识别、坐标转换、PID控制算法、系统延时补偿等一系列问题任何一个环节没处理好整个系统的追踪效果就会大打折扣出现“跟不上”、“抖得厉害”或者“跑飞了”的情况。这道题非常适合有一定嵌入式基础和编程能力并且对机器视觉感兴趣的同学来挑战。它不仅能让你把单片机、传感器、电机驱动这些硬件知识串起来更能让你亲身体验到从“感知”到“决策”再到“执行”的完整自动控制流程。接下来我就结合我们团队的实战经历把这个题从设计思路到代码实现再到调试避坑给你掰开揉碎了讲清楚。2. 系统整体设计与核心思路拆解做电赛题目最忌讳的就是拿到题目就埋头焊板子、写代码。我们花了将近半天的时间来做方案论证和整体设计事实证明这半天的时间在后面省下了无数个通宵。2.1 核心需求与指标分析题目要求主要有几个关键点1系统能自动识别并锁定指定的运动目标比如一个画有特定图案的小车2控制云台转动使激光点持续照射在目标中心3目标做匀速直线、S形等规定运动时追踪误差要小于一定范围比如2厘米4有手动模式可以介入。这些要求翻译成技术语言就是感知层需要一套视觉系统能稳定、快速地从摄像头画面中提取出目标的位置坐标X, Y。控制层需要一套算法能将目标坐标转换为云台两个舵机俯仰和偏航的转动角度并且这个转换要能补偿摄像头安装位置带来的几何偏差。执行层需要能精确驱动舵机转到指定角度的硬件电路和驱动程序。决策层需要一套控制律我们用的是PID根据目标位置的实时变化计算出舵机应该调整的速度或角度让整个系统形成一个平滑、快速的闭环。我们的设计思路也就此明确以微控制器为大脑摄像头为眼睛舵机云台为手脚构建一个视觉伺服系统。核心闭环流程是摄像头采集图像 - 图像处理识别目标 - 计算目标在图像中的像素坐标 - 通过坐标转换模型得到目标相对于云台的真实角度偏差 - PID控制器根据角度偏差计算舵机调整量 - 驱动舵机转动 - 激光点移动 - 下一帧图像采集……如此循环。2.2 硬件方案选型与考量硬件选型直接决定了系统的性能上限和开发难度。主控MCU我们选择了STM32F407。理由很充分首先F4系列带有FPU浮点运算单元在做坐标转换、PID运算等大量浮点计算时速度比F1系列快一个数量级这对保证控制周期至关重要。其次它资源丰富有足够的定时器来产生精准的PWM波控制舵机也有足够的串口、SPI等接口与摄像头模块通信。视觉模块这是选型的重中之重。我们评估了三个主流方案OpenMV优点是开发简单内置了丰富的图像处理函数找色块、找直线、AprilTag识别等直接用MicroPython编程上手快。缺点是性能有限处理复杂图像或高帧率时比较吃力且与STM32通信通常用串口传输处理后的结果数据有延时。Kendryte K210这是一颗带AI加速的芯片性能强悍可以跑一些简单的神经网络模型进行目标检测。但如果只是识别颜色块或特定图案有点杀鸡用牛刀且双核开发、内存管理对新手不太友好。树莓派摄像头性能最强可以用OpenCV做非常复杂的处理。但问题是树莓派是Linux系统实时性是个挑战控制舵机的PWM精度可能不如单片机而且系统复杂功耗也高。综合权衡我们选择了OpenMV H7 Plus。它的性能比老款强不少足够处理这个题目。最关键的是开发效率。电赛时间紧OpenMV的快速原型开发能力能让我们把精力集中在控制算法上而不是折腾摄像头驱动和底层图像处理。我们让OpenMV只负责一件事以最高帧率找到目标并输出其中心点的像素坐标x, y。这个坐标通过串口实时发送给STM32。执行机构云台我们用了两个数字舵机MG996R这类分别控制Yaw偏航水平转动和Pitch俯仰垂直转动。数字舵机比模拟舵机控制精度高响应快。舵机由STM32的定时器产生PWM信号直接控制注意要单独供电电流要足否则会出现抖动甚至重启。其他激光笔直接用个小功率的固定在云台上。整个系统用一块12V锂电池供电通过降压模块给各个部分提供合适的电压STM32和OpenMV用5V或3.3V舵机用6V或7.4V单独供电。注意供电一定要隔离电机舵机的电源必须和核心控制板STM32 OpenMV的电源分开至少要用磁珠或二极管隔离并用大电容稳压。否则舵机一动作电压瞬间被拉低会导致单片机复位摄像头重启这是新手最容易踩的坑。3. 核心算法解析与实现要点硬件是骨架算法才是灵魂。这个题目的算法部分主要集中在坐标转换和PID控制上。3.1 视觉识别与坐标获取在OpenMV上我们的脚本非常简单高效。假设目标是红色小球。import sensor, image, time, pyb from pyb import UART # 初始化摄像头 sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) # 320x240 sensor.skip_frames(time 2000) sensor.set_auto_gain(False) # 必须关闭自动增益和白平衡否则颜色阈值会变 sensor.set_auto_whitebal(False) # 定义红色阈值 (在RGB色彩空间下调整) red_threshold (30, 100, 15, 127, -128, 127) # 根据实际环境调整 uart UART(3, 115200) # 初始化串口3波特率115200 while(True): img sensor.snapshot() # 抓取一帧图像 # 寻找色块 blobs img.find_blobs([red_threshold], pixels_threshold100, area_threshold100) if blobs: # 找到最大的色块 largest_blob max(blobs, keylambda b: b.pixels()) # 绘制矩形和十字 img.draw_rectangle(largest_blob.rect()) img.draw_cross(largest_blob.cx(), largest_blob.cy()) # 通过串口发送中心坐标 data bytearray([0xFF, 0xFE, largest_blob.cx()8, largest_blob.cx()0xFF, largest_blob.cy()8, largest_blob.cy()0xFF]) uart.write(data) else: # 没找到目标发送一个特定值比如(0, 0)并在前面加个标志位 uart.write(bytearray([0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00])) # 控制帧率不设延时以最大速度运行这里的关键是稳定性和速度。阈值要调准确保在不同光照下都能稳定识别。发送数据时我们加了一个简单的帧头0xFF, 0xFE方便STM32解析。没找到目标时发送另一个帧头0xFF, 0xFD让STM32知道目标丢失可以进入搜索模式或保持上一位置。3.2 从像素坐标到舵机角度坐标转换模型这是整个系统的核心数学模型也是最容易出错的地方。OpenMV返回的是(cx, cy)这是目标在图像平面320x240上的像素坐标。我们需要把它转换成云台需要转动的角度(θ_yaw, θ_pitch)。这里不能用一个简单的线性比例关系因为摄像头是透视成像。我们采用小孔成像模型进行近似解算。假设摄像头光轴与云台转动中心对齐理想情况实际安装总有偏差。已知摄像头的焦距f以像素为单位和图像中心(u0, v0)。这些参数可以通过摄像头标定获得OpenMV也提供近似值。目标在摄像头坐标系中的真实角度偏移弧度制可以通过以下公式计算Δθ_yaw ≈ (cx - u0) / fxΔθ_pitch ≈ (cy - v0) / fy其中fx, fy是x轴和y轴方向的焦距u0, v0是主点坐标通常接近图像中心。在实际操作中我们采用了一种更实用的两步法粗标定将目标放在云台正前方足够远的位置记录此时OpenMV返回的(cx0, cy0)作为“零点”。比例系数法控制云台水平转动一个已知角度A如30度记录转动前后目标像素坐标的变化量Δcx。那么像素-角度比例系数 Kyaw A / Δcx。同理可以标定出垂直方向的系数 Kpitch。实时计算在运行时目标的角度偏差就可以近似计算为θ_yaw (cx - cx0) * Kyawθ_pitch (cy - cy0) * Kpitch这个方法的优点是避免了复杂的相机内参测量通过实验直接得到映射关系虽然有一定误差但在题目要求的距离和角度范围内完全够用且非常容易理解和实现。实操心得标定一定要在实际比赛的场地光照和背景下进行光线不同摄像头成像的亮度和对比度不同可能会轻微影响识别出的中心点位置。我们当时就在实验室的日光灯下标定得很好结果比赛当天场地是暖色光导致零点漂移现场又重新微调了系数。3.3 PID控制算法的实现与调参得到角度偏差(θ_yaw, θ_pitch)后我们的目标就是让这个偏差趋于0。这就是PID控制器的用武之地。我们为两个轴分别独立设置一个PID控制器。在STM32上我们实现了位置式PID。公式大家都很熟悉Output Kp * e(t) Ki * ∫e(t)dt Kd * de(t)/dt。但具体到代码里有些细节至关重要typedef struct { float Kp, Ki, Kd; // PID参数 float integral; // 积分项 float prev_error; // 上一次误差 float integral_limit; // 积分限幅 float output_limit; // 输出限幅 } PID_Controller; float PID_Update(PID_Controller* pid, float error, float dt) { // 比例项 float proportional pid-Kp * error; // 积分项带限幅和抗饱和 pid-integral error * dt; if (pid-integral pid-integral_limit) pid-integral pid-integral_limit; if (pid-integral -pid-integral_limit) pid-integral -pid-integral_limit; float integral pid-Ki * pid-integral; // 微分项用误差的差分近似 float derivative pid-Kd * (error - pid-prev_error) / dt; pid-prev_error error; // 计算总输出并限幅 float output proportional integral derivative; if (output pid-output_limit) output pid-output_limit; if (output -pid-output_limit) output -pid-output_limit; return output; }调参是PID的灵魂也是比赛调试时间的大头。我们的经验是“先P后I再D从小到大慢慢试”调Kp比例先将Ki和Kd设为0。逐渐增大Kp直到系统开始出现小幅度的、频率较快的振荡。此时系统响应快但无法稳定在目标点会在目标点附近来回抖。记录下这个临界Kp值。调Kd微分引入Kd目的是抑制振荡增加系统阻尼。从0开始慢慢增加Kd你会发现振荡幅度减小变得平滑。Kd太大反而会引入高频噪声使系统反应迟钝。调Ki积分最后引入Ki目的是消除静态误差即始终差一点对准的情况。Ki要非常小心地加因为积分累积效应强容易导致系统超调过大甚至震荡。一定要加上面代码中的integral_limit积分限幅。核心技巧对于云台舵机系统微分项Kd至关重要。因为舵机本身有惯性如果没有微分项仅用比例控制云台很容易像钟摆一样晃来晃去永远停不下来。合适的Kd能提供“阻尼”效果让云台平滑、稳定地停下来。我们的参数最终大概在 Kp1.2 Ki0.05 Kd0.8 左右具体值取决于你的系统机械结构、舵机速度、控制周期等。控制周期dt也是一个关键参数。它取决于你的图像处理帧率。如果OpenMV每秒能处理30帧那么理想控制周期就是33ms。在STM32中我们用一个定时器中断严格每隔33ms读取一次最新的目标坐标并执行一次PID计算和舵机控制。周期不稳定会导致控制效果剧烈波动。4. 系统集成与调试全流程4.1 软件架构与多任务处理STM32的程序我们采用“前后台”架构配合中断来保证实时性。后台主循环负责状态显示、参数设置通过按键或串口、模式切换等非实时任务。定时器中断这是核心节奏器。我们设置了一个1ms的定时器中断用于计时。在中断服务函数里我们设置一个33ms的“软定时器”。当33ms到达时置位一个标志位Vision_Update_Flag。串口中断用于接收OpenMV发来的坐标数据。一旦收到一帧完整数据通过帧头判断就立即解析并将最新的(cx, cy)坐标更新到一个全局变量中同时记录接收时间戳。主循环中的控制任务在主循环中不断检查Vision_Update_Flag。如果标志位为1则清除标志位。从全局变量中读取最新的目标坐标。检查坐标是否有效非零值且接收时间在最近100ms内防止用陈旧数据。执行坐标转换得到角度偏差。调用两个轴的PID计算得到舵机PWM的增量。更新舵机的目标PWM值。通过定时器PWM输出驱动舵机。这种设计确保了控制周期基本稳定并且图像数据的接收是即时的不会被主循环中的其他任务阻塞。4.2 通信协议与数据同步OpenMV和STM32之间的串口通信必须可靠。我们设计了简单的协议数据帧格式帧头1帧头2数据1高字节数据1低字节数据2高字节数据2低字节。0xFF, 0xFE代表找到目标后面4个字节分别是cx和cy。0xFF, 0xFD代表目标丢失后面4个字节忽略。STM32端在串口中断中用一个状态机来解析这个协议确保能正确应对数据丢包和错位。同步问题摄像头采集、处理、发送需要时间STM32接收、处理、控制也需要时间这造成了系统延时。如果目标运动很快这个延时会导致追踪永远慢半拍。我们的补偿方法是在PID中适当增大微分项Kd因为微分项本质上是对未来趋势的预测。更高级的做法是使用“预测滤波”如卡尔曼滤波根据目标前几帧的运动速度来预测它当前的位置但这在四天三夜的比赛中实现和调优风险较大。4.3 现场调试与性能优化调试阶段我们准备了几个工具无线串口模块将STM32的调试信息如当前坐标、角度偏差、PID输出、帧率实时发送到电脑用串口绘图工具如SerialPlot可视化。这是调参的“眼睛”没有它就是在盲调。手机慢动作拍摄用手机的慢动作视频240帧拍摄激光点追踪目标的整个过程回放时可以一帧帧看分析是反应慢、过冲还是抖动。参数在线微调我们在STM32程序里预留了接口可以通过电脑串口助手实时发送指令修改PID参数无需重新烧录程序极大提升了调试效率。优化点图像处理优化在OpenMV上将搜索区域ROI限制在上一帧目标位置附近而不是全图搜索能大幅提升帧率。运动预测当目标连续若干帧向一个方向运动时可以给舵机目标角度加一个很小的提前量。死区设置当角度偏差小于某个很小阈值如0.5度时不更新舵机位置避免舵机一直在“微颤”减少磨损和功耗。5. 常见问题与故障排查实录在实际制作和调试中我们遇到了无数坑这里把最典型的几个列出来希望能帮你避雷。问题现象可能原因排查与解决方法激光点完全跟不上目标滞后严重1. 系统总延时过大。2. PID参数过于保守Kp太小。3. 舵机速度太慢。1. 测量总延时从目标移动到激光点开始反应的时间。优化图像处理提高帧率检查通信是否拥堵。2. 逐步增大比例系数Kp观察反应速度。3. 更换更快舵机或提高舵机供电电压在额定范围内。激光点在目标点附近高频剧烈抖动1. 微分项Kd太小或为0系统欠阻尼。2. 比例项Kp过大。3. 机械结构松动云台固定不牢。1. 首要任务是增加Kd这是抑制振荡最有效的手段。2. 适当减小Kp。3. 紧固所有螺丝检查云台轴承是否有间隙。激光点缓慢漂移无法稳定在中心1. 存在静态误差需要积分项Ki。2. 坐标转换的“零点”标定不准。3. 摄像头或激光笔安装有物理偏差。1. 引入很小的Ki并严格设置积分限幅。2. 重新进行零点标定确保目标在云台正前方。3. 机械调整确保激光笔光轴与摄像头光轴尽可能平行。目标偶尔丢失丢失后乱转1. 图像识别阈值不适应环境光变化。2. 目标运动出视野。3. 通信误码或丢包。1. 采用自适应阈值或比赛前根据现场光微调。2. 目标丢失后让云台缓慢回到零点或进行小范围扫描搜索而不是停住不动。3. 加强通信协议校验如增加CRC校验降低波特率或检查接线。舵机动作时单片机或摄像头重启电源问题舵机启动瞬间电流极大拉低了整个系统电压。必须进行电源隔离舵机使用独立电池或稳压模块供电并与控制板电源共地。在舵机电源输入端并联一个大容量如1000uF的电解电容缓冲电流。帧率不稳定时快时慢1. OpenMV图像处理复杂度波动。2. STM32主循环被其他任务阻塞。3. 串口数据传输时间不稳定。1. 优化OpenMV代码使用clock()函数测量各部分耗时找出瓶颈。2. 确保控制任务在定时器触发下执行优先级最高。3. 确保串口波特率匹配且中断服务函数执行时间尽可能短。最后再分享一个比赛时的心态技巧电赛到最后一天系统可能还是不太稳定。这时不要盲目大改算法或硬件。“稳”字当头。检查所有接线是否牢固电源是否充足参数是否备份。做一个最基础、最稳定的功能版本保底。在这个基础上再去尝试那些“锦上添花”的优化功能。我们当时就是先确保匀速直线追踪绝对稳定拿到基础分然后再去挑战S形曲线追踪的加分项。记住一个能稳定拿到80分的系统远胜于一个理论上能拿100分但随时会崩的系统。