2024电赛H题自动行驶小车:视觉循迹+STM32控制全解析

发布时间:2026/9/7 9:25:28
2024电赛H题自动行驶小车:视觉循迹+STM32控制全解析 简介2024年全国大学生电子设计竞赛H题“自动行驶小车”的开源参赛方案面向电赛参赛者与嵌入式系统学习者完整呈现基于MSPM0G3507的小车控制工程涵盖传感器数据采集、显示交互与运动控制等核心环节。压缩包共含89个文件以C/H源码、目标文件、编译依赖和系统配置为主辅以makefile、README及HTML说明文档整体大小仅386KB模块划分清晰便于导入CCS开发环境复现。上线至今已有3162人浏览学习在备赛交流中具备实际参考价值。方案覆盖OLED显示、MPU6050姿态采集、JY61P串口通信等关键模块从底层驱动到上层控制均有代码路径可参照同时保留编译映射、链接脚本与调试配置让环境复现更加省心。无论是入门电赛的新手还是希望优化既有方案的进阶者都能从中获得直接的工程启发。1. 赛题拆解一条“自动行驶小车”到底考什么2024年电赛H题“自动行驶小车”出得很有代表性题目不给一条固定的格子线而是让你在一张模拟道路上自动跑起来。小车要能沿标线行驶、识别路口类型、按任务卡要求转向或停车整个流程不能有人为干预。这比传统“循迹小车”至少高了一个台阶因为它把感知、决策、控制三件事串在了一起任何一个环节掉链子整车都会在赛场上表演原地画圈或者直接冲出场外。说实话这道题非常适合想系统练一遍嵌入式开发的同学。你用到的不是单一知识点而是摄像头图像处理、串口通信、电机驱动、PID调节、状态机设计这些在真实工程里天天碰的东西。不管最后有没有拿奖把这一套流程走通对后续做机器人、做自动驾驶相关项目都有直接帮助。1.1 题目意图与得分点拆解题目之所以把“自动”两个字放进标题核心考验的是系统在无人干预下的自主决策能力。赛场上的任务往往拆成多个阶段起点启动、沿轨迹行驶、通过路口时判断直行还是转弯、遇到停止标志或对应数字时精准停车。每个阶段都有明确的完成分所以真正的难点不是某一项有多深的技术而是“稳定地全部完成”。从我们实际做下来的经验看得分点主要集中在三个方面。第一是行驶稳定性小车不能跑着跑着偏移出赛道这依赖图像采样的稳定性和控制算法的响应速度。第二是路口判断准确率十字、T字、直角弯不能认错否则整个后续路线全部崩掉。第三是停车精度停得准不准直接决定分数档位这也是很多队临场翻车的地方。1.2 方案权衡为什么摄像头是绝对核心方案选型上我们对比过三类路线。纯灰度循迹模块最快但只能判断“黑/白”面对十字路口、连续弯道、位置偏移时信息严重不足基本只能靠盲猜。激光雷达精度高但对一辆电赛小车来说成本高、算法复杂普遍用来做避障而不是做全局路径感知。真正适合这道题的是视觉方案用摄像头采集赛道图像通过算法提取目标轨迹和路口特征。视觉方案最大的优势是“信息量大”一条画面里能同时看到赛道方向、前方路口形态、目标数字的位置所有关键信息都用同一个传感器解决了。而且现在OpenMV、树莓派加摄像头的组合非常成熟哪怕你之前没接触过图像处理只要肯花两三天时间搞懂基本阈值分割和找线逻辑一样能跑起来。我们最后选了OpenMVSTM32的组合OpenMV负责图像处理和路口识别STM32负责电机控制和整体状态切换两边通过串口通信分工明确调试起来很省心。2. 硬件选型用最稳的组合兜住赛题下限硬件这块不需要盲目堆料关键是把“下限”兜住。电赛现场环境复杂车子硬件越稳后期调算法的空间就越大。我们第一版车图便宜用了普通TT电机结果速度一高就打滑后来换了一套带编码器的直流减速电机效果立刻不一样。所以我的建议是电机、轮胎、电源这三样别太省其他的可以先用学有余力再说。2.1 底盘与机械结构底盘我建议选三轮结构或四轮后驱结构不要选四轮独立转向那是给自己增加难度。三轮车结构简单、转弯半径小但高速行驶时容易侧翻四轮后驱加前轮转向更接近真车跑起来也更稳。我们最终用的是四轮车模底盘前轮舵机转向后轮直流电机驱动这样“转向”和“驱动”两个维度可以分开调参数调试思路非常清晰。轮胎的选择容易被忽略强烈建议用软质橡胶轮胎不要用硬塑料轮。硬轮在赛道上抓地力差起步和刹车时滑动明显很容易造成视觉识别正常但车却跑偏的情况。重心方面电池尽量平放在底盘中部偏后摄像头支架不要太高否则过弯时车身侧倾会导致画面剧烈晃动。我们的经验是整车重心每降低一厘米过弯稳定性提升一个明显档次。2.2 主控、驱动与电源主控我们用的STM32F103C8T6原因很简单资料多、引脚够用、性能完全足够处理电机控制和状态逻辑。图像处理那边单独用OpenMV Cam负责。两块板子之间用UART串口连接OpenMV每隔30毫秒左右发送一次赛道识别结果和路口类型STM32收到之后再结合当前状态做决策。这样的好处是图像处理和控制逻辑解耦调试任何一个模块都不影响另一个。电机驱动芯片我们用的是TB6612比老式L298N发热小、体积小适合这种紧凑车型。PWM频率我习惯设为10kHz左右既能保证电机响应平滑又没有明显噪音。电源部分是很多队翻车的重灾区必须单独说明STM32、OpenMV、舵机、电机驱动不能全挤在一个5V输出上。我们的接法是——电池7.4V直接给电机驱动模块供电驱动模块内置稳压给逻辑电路用另外用一路5V降压模块单独给STM32和OpenMV供电。这样电机大电流抽载时控制电路电压不会剧烈波动避免出现摄像头花屏、单片机复位的诡异问题。2.3 传感器的合理搭配摄像头是核心但也不要只靠摄像头。我们在车底前方加装了三个灰度传感器分布在中线和左右两侧。它们不是用来主控循迹的而是作为“丢线保护”当摄像头因为反光、遮挡暂时找不到赛道时灰度传感器能够给出一个粗略的补位信号让车子不至于直接冲出赛道。编码器也建议加后轮或者电机尾部带编码器可以做里程计辅助判断路口距离。比如从识别到某个标志到停车位置之间到底要跑多少距离光靠图像估算误差很大用编码器累积脉冲数就能精确许多。3. 核心逻辑从图像到控制量摄像头图像是一张二维像素数组电机只认转速和转向角度中间这段“翻译”工作全靠算法完成。我们把整个算法拆成三个模块图像处理、路口识别、运动控制。三个模块按顺序执行每次循环的输出就是一个明确的控制指令。写代码的时候一定要保持这个分层否则后期改一个参数牵一发动全身那感觉比调车还折磨。3.1 图像处理从原始画面到赛道中线OpenMV上我用的是RGB565格式。处理流程是这样的先截取画面下方一块固定区域作为“有效赛道区域”因为离车近的部分畸变小、信息最可靠。然后做颜色阈值分割把赛道和背景分开。我们场地是深色线加浅色底所以阈值分割的目标是提取浅色赛道区域。找到赛道之后对每一行像素求赛道区域的中心点把这些中心点连起来就得到了当前赛道的中线轨迹。这里有个关键细节并不是整幅画面所有行都要加入计算。离车太远的区域弯道变形严重直接用于控制反而会造成转向过猛。我一般只取画面下方40%到70%之间的若干行来计算中线偏移量另外取画面最前方一小块区域的赛道中心位置用来做路口预判。处理完之后我用一个简单的线性函数把“中线偏移量”映射成舵机偏转角度偏移量越大转角越大但必须限制最大转角防止高速时转向过猛翻车。代码骨架大致长这样import sensor, image, math # 假设已经设置好了摄像头参数 def get_lane_offset(img, roi): # 截取有效区域转灰度 gray_img img.binary([threshold]) # 根据实际场地调整阈值 mid_points [] for row in range(roi_top, roi_bottom, step): row_data gray_img[row:row1, :] # 统计该行赛道像素的左右边界 left find_left_edge(row_data) right find_right_edge(row_data) if left ! -1 and right ! -1: center (left right) // 2 mid_points.append(center) # 用最后几行中线均值作为当前偏移值 offset sum(mid_points[-5:]) // 5 - img.width() // 2 return offset实际OpenMV代码里会有更多细节但这个流程是通用的。重点在于不管用什么平台思路都是“有效区域—阈值分割—逐行求中心—最后映射”。3.2 路口类型判断与状态机路口识别的本质是看画面顶部区域的赛道分布形态。我们采用的策略不是每帧都预测路口而是维护一个“路口置信度”计数器。当某帧识别到疑似路口的特征时置信度加一连续几帧都识别到同一个特征才认为确实是路口。这么做能有效防止误判因为单帧图像受噪声干扰影响太大但连续多帧都出现同样特征的概率就很低了。我们用状态机来管理整车行为。枚举定义大概是启动、直行、转弯、停车。实际比赛时一辆车里会预置多条路线通过任务卡或拨码开关选择。比如任务A要求“第一个路口左转第二个路口直行终点停车”那么状态机就会在识别到第一个路口时切换到左转状态转弯完成后切回直行状态等第二个路口时保持直行直到检测到终点标志再切换到停车状态。状态机的核心是每个状态必须有明确的进入条件和退出条件不能有空洞状态否则小车会在赛道上发呆全场观众都替你着急。C语言里的状态切换我习惯这样写typedef enum { STATE_INIT, STATE_FORWARD, STATE_TURN_LEFT, STATE_TURN_RIGHT, STATE_STOP } car_state_t; car_state_t current_state STATE_INIT; void state_machine_update(result_t vision_result, int distance) { switch(current_state) { case STATE_INIT: if (vision_result.start_flag) current_state STATE_FORWARD; break; case STATE_FORWARD: if (vision_result.intersection vision_result.turn_dir LEFT) current_state STATE_TURN_LEFT; else if (vision_result.intersection vision_result.turn_dir RIGHT) current_state STATE_TURN_RIGHT; else if (vision_result.stop_flag distance STOP_DISTANCE) current_state STATE_STOP; break; case STATE_TURN_LEFT: if (vision_result.heading_ok) current_state STATE_FORWARD; break; // 其余状态类似 } }状态机的价值在于它把“下一步该干什么”和“当前赛道长什么样”彻底分开了遇到意外情况时只需要处理当前状态不用全局梳理逻辑。3.3 PID控制与参数整定速度控制这块我们用的是增量式PID。因为直流减速电机的响应存在滞后如果只做简单的P控制速度一高就会震荡表现为车子一耸一耸的。增量式PID每次只在上一拍基础上加一个增量输出更平滑配合编码器反馈做闭环能够明显改善行驶稳定性。增量式PID公式大家应该都很熟输出增量 Kp * (e[k] - e[k-1]) Ki * e[k] Kd * (e[k] - 2*e[k-1] e[k-2])实际调参顺序是先只调Kp让车在直线上能稳定中速行驶然后加入Kd抑制超调让过弯不甩尾最后加一点点Ki消除稳态误差也就是长时间行驶后可能出现的速度缓慢下降。我们最终的参数大概是Kp1.2、Ki0.02、Kd8但这个参数跟具体车重、电机特性关系很大建议在现场根据实际表现微调。对于舵机转向我们没有用复杂的PID而是用了比例加限幅的控制方式。原因是舵机是一个位置闭环系统本身响应就比较快过度调PID反而会让转向抖动。4. 调试方法与实测流程很多队犯的错误是一上来就把所有代码合到一起然后跑到场上试失败了再乱改参数。我强烈不建议这种“跑偏就调大KP”的做法。正确顺序是把系统拆开从底层到上层一层一层验证每一层都稳定了再往上叠。4.1 先修图像再谈控制第一步是单独调试图像模块。在没装电机的情况下把OpenMV接到电脑上实时预览画面用IDE里的阈值调节工具调出最适合当前场地的二值化参数。很多新手在这里会卡住为什么上午能识别下午就丢线因为光线变了。所以阈值不能只调“当前光线”下的一个固定值应该多采集几个时间段、几个角度的画面找一个相对中庸的阈值范围。更稳妥的做法是在代码里做自动动态阈值每次初始化时先采集背景亮度再自动计算出分割阈值。图像调试通过的标准是在场地任意位置、任意光线条件下画面中的赛道区域都能被连续稳定地提取出来且没有大面积噪点。这个标准达不到后面的控制调得再漂亮都是白搭。4.2 分阶段测试直线、弯道、综合图像稳定之后才能装电机开始跑。我建议按照“直线—弯道—综合”三个阶段来推进。直线测试的目的是校准方向和基础速度车能从起点笔直走到终点不偏移再考虑其他。弯道测试时从大半径弯开始逐步减小转弯半径配合调整摄像头前瞻距离和P增益。综合测试就是把所有路口、停车点在一个完整场地上串起来跑。每个阶段我都在车上固定一个小摄像头录下比赛状态回去逐帧分析。很多现场看不到的问题比如某个路口的转向时机晚了100毫秒在慢放录像里一清二楚。用录像来回看问题比几个人盯着车猜原因高效得多。4.3 现场快速调参技巧比赛现场最怕的是参数全乱。我的做法是把所有需要调参数集中在程序最前面定义并且提前写好“参数备份板”——用一张打印纸记录每组参数对应的场地条件和调参方向。比如“舵机P0.5过弯早晚图像前瞻行数向上调”。这样即使现场被旁边队干扰或者光线剧变也能快速恢复到已知可用状态。另外一个习惯非常管用每一版改动前先跑一次基准赛道记录完成时间和是否完赛作为回归基准。改完任何参数后再跑一次对比。不要一次改多个参数不然出问题了都不知道是哪个改坏的。我和队友因为手痒同时改了两个参数结果花了一个多小时才定位到问题血的教训。5. 高频翻车点与避坑记录这里整理一下我们自己和周围队伍在调试中碰到过的高频问题按“现象—原因—处理方案”列出来方便大家赛前对照检查。现象可能原因处理方案跑直线时车身走S形舵机中位不准或图像偏移计算延迟先校准舵机中位再降低PID的Kp适当增大Kd过弯时车辆冲出去摄像头前瞻太远或转向角限幅不够减小前瞻行数增大转向角限幅降低过弯速度中途突然复位重启电源电压跌落单独给控制板供电在电机端加续流电容摄像头画面闪烁或花屏电机大电流干扰供电用屏蔽线连接摄像头电源线加磁珠或RC滤波路口识别时灵时不灵单帧误判未做连续帧确认加入置信度计数器连续N帧确认才触发状态切换停车位置飘忽不定只靠图像距离判断加入编码器里程计临近停车点切换为里程控制白天场地反光严重光照变化导致阈值失效做动态阈值或在镜头前加偏振片减少反光5.1 电路和干扰问题最容易被低估的是电路干扰。刚开始我们的OpenMV画面里总是出现横纹查了很久才发现是电机转动产生的电磁干扰沿着共地线路传到了摄像头。后来把摄像头线换成双绞屏蔽线屏蔽层单端接地画面立刻干净了。在电机两端并联一个104陶瓷电容和一个大容量电解电容也能明显减少火花干扰。如果你发现单片机偶尔莫名复位大概率不是代码问题而是电源问题。5.2 机械和行驶问题机械上的问题更多是“慢改”出来的。轮子胎压不足会导致行驶路线持续偏向一侧底盘螺丝松动会让摄像头晃动电机齿轮打滑会让速度闭环失效。每次上场前我都会做一遍螺丝紧固检查尤其是轮胎和舵机连接处。这些地方拧得不紧赛场上跑不了三圈就会变成灾难现场。5.3 逻辑和状态机问题状态机进入“死锁”也是高发问题。比如小车进入转弯状态后如果因为某个标志刚好在盲区里导致“转弯完成”的退出条件永远不满足车子就会一直在原地转圈。对策是给每个状态加一个超时保护进入转弯状态的同时启动计时超过2秒还没检测到退出条件就强制切换回直行状态。这种“带惩罚的兜底逻辑”在电赛现场能救回好几次意外失误。最后说点实际操作层面的心得。别在比赛前一晚大改结构或者重写算法最好的状态是提前两天冻结方案剩下的时间只做微调和保存参数。赛场的灯光和你母校实验室子的灯光肯定有差异到了现场先花20分钟重新标定摄像头参数比什么灵丹妙药都管用。把这套流程走完你会发现所谓“自动行驶小车”其实没有想象中那么神秘它就是一个感知—决策—控制的闭环工程而你已经亲手把它搭了出来。本文还有配套的精品资源点击获取