智能车竞赛开源目录:从硬件设计到PID控制与图像处理全解析

发布时间:2026/11/6 4:47:11
智能车竞赛开源目录:从硬件设计到PID控制与图像处理全解析 1. 开源目录的第一眼文件结构决定了新手能不能入门每年智能车竞赛结束后的几个月都是各支队伍集中开源的高峰期。作为21届疯狂电路组Soberup战队的一员我把整个项目整理成了开源目录源码、原理图、PCB工程、调试笔记全放进去目录发出去之后被问最多的问题是应该从哪里开始看。问的人多了我自己也重新审视了一遍那些文件才发现一份好的开源目录价值不亚于一整年的备赛笔记。这篇文章不打算按环境搭建-代码讲解-总结的套路写而是用我们实际备赛的顺序讲一遍先看硬件再看控制链路然后聊图像处理里最容易翻车的场景最后说怎么用好这份目录希望能给准备下一届比赛、或者正在准备校级选拔赛的队伍一点参考。1.1 目录怎么分背后是项目管理的习惯开源目录的顶层结构我刻意压得很简单任何人打开都能看懂Soberup_21th_ICar/ ├── 00_Docs/ # 赛事规则、调试笔记、例会记录 ├── 01_Principle/ # 整车原理框图、信号流说明 ├── 02_MCU/ # 单片机工程源码TC264/TC377分支 ├── 03_CMOS/ # 摄像头图像处理模块 ├── 04_Algorithm/ # 控制算法PID、补线、元素识别 ├── 05_PCB/ # 原理图与PCB工程 └── 06_Tools/ # 串口助手、上位机、离线测试脚本这个结构不是一开始就有的。最早我的网盘里只有一个新建文件夹里面躺着后缀为.c、.h、.SchDoc、.png的几十份文件到第二次重构车模时已经分不清哪个是旧版、哪个是能跑的最新版。后来参考一些老开源项目把目录按文档-硬件-软件-算法-工具拆开下面再按模块细分总算解决了两个实际问题一是队员交接时不用把每个文件都打开一遍二是现场调车时能顺着目录快速定位问题源头——车跑飞了去04_Algorithm画面出条纹了去03_CMOS供电不稳了去05_PCB而不是一个文件一个文件猜。1.2 文档目录里那些看起来没用的文件其实最值钱00_Docs里放的不是什么高深东西主要内容三类竞赛规则的关键条款摘录、每周例会的会议记录、还有每个人当天的调试笔记。调试笔记看起来最不起眼但最有价值。我们队伍有个不成文的规定当天改过什么代码、动过哪个电位器、换过哪根线都要按日期记进笔记。一开始大家嫌麻烦后来靠它破过一个大案——车在入环时偶尔甩尾查了两天没结果翻笔记才发现三天前有人为了减轻后轮阻力调松了后轮的机械限位。把笔记放进开源目录也是想让后来的队伍知道稳定代码固然重要但那些失败的调试记录能帮你少走很多弯路。1.3 为什么我们把算法单独拆一个目录算法目录从MCU工程里拆出来一开始单纯为了调试效率图像处理、中线提取、元素识别这些代码经常改如果把算法文件直接放在单片机工程里每次烧录都要编译整个工程几分钟就没了。拆出来通过include路径引用之后我们还可以在PC上做离线测试——把摄像头采集的RAW图按帧存到SD卡再用PC上的test脚本跑同一份算法代码跑完直接对比输出结果。开源目录里那些test开头的文件就是这么来的。很多人拿到目录后不理解为什么算法文件不跟主工程放一起这里解释一下。这种做法的代价是工程配置稍复杂但收益是调试速度提升非常明显尤其在反复调环岛阈值的那两周没有离线测试的话我们根本不可能一天迭代几十个版本。目录结构看起来只是文件摆放问题实际上决定了一个新手能不能在两小时内找到自己想要的代码。你拿到的是一棵树不是一堆落叶。2. 电路组硬件核心电源拓扑、驱动选型与传感器接口2.1 整车电源拓扑别小看一路5V的纹波很多第一次画板的队伍习惯把电池电压直接送到所有模块每个模块自己降压。这个做法省事但问题也大电机一启动动力线上的电压会瞬间跌落摄像头和编码器的供电如果跟驱动共用一级画面就会出现横纹编码器数据甚至可能丢脉冲。我们整车电源拓扑分了三路7.2V电池直接进驱动部分一路大功率DC-DC降到6V专门给舵机舵机启动电流大单独一路能避免电压跌落传染给逻辑电路再通过低噪声LDO降到5V给摄像头、编码器和无线模块最后在MCU板本地用3.3V LDO给主控。GND采用单点连接5V和舵机地用磁珠隔开。为什么舵机单独用6V不用5V舵机的响应速度跟供电电压有关6V下力矩和速度比5V明显好但绝对不能超过舵机规格上限。如果你的舵机标称工作电压是4.8V到6.0V6V刚好在边缘得留足余量别为了快那零点几毫秒把舵机烧了。电源设计没有标准答案关键是把动不动就大电流的负载和对纹波敏感的逻辑分开。2.2 电机驱动MOS管半桥与续流回路的取舍电机驱动部分我们最后用的是分立半桥加互补PWM。为什么不直接用集成驱动芯片备赛后期电机要频繁正反转集成芯片一旦触发过热保护会主动限流车在关键时刻直接软掉。分立MOS的好处是坏了可以单独换调试时能拿示波器逐相看波形。缺点也明显栅极驱动匹配、死区时间都靠自己调。给一个最容易被忽视的参数——栅极驱动电阻Rg。我们用的MOS栅极电荷大约60nC驱动电压10V如果希望开关时间控制在500ns以内平均栅极驱动电流至少需要60nC/500ns0.12A。很多标称峰值电流0.1A的驱动器在这个要求下其实是不够的结果就是开关损耗变大MOS温度快速上升。所以选驱动器不能只看导通电阻还要看栅极驱动能力Rg也不能一味加大来缓解振铃否则会拖慢开关上下管共通的风险反而变高。分立方案的死区时间和Rg一定得上示波器验证仿真数据只能当参考。2.3 传感器接口摄像头、电磁和编码器怎么接线不容易打架信号接线是排查故障时最容易被忽略的一环。摄像头数据线多如果D0到D7、PCLK、VSYNC和编码器AB相挤在同一排排针上高速翻转的信号会互相串扰图像边缘会出现随机噪点。我们的做法是摄像头信号单独一组排针并且尽量远离电机驱动编码器AB相用双绞线靠近MCU的定时器输入引脚电磁传感器用屏蔽线进入MCU前加一级RC滤波。还有一个细节所有接插件都要选带锁扣的比赛现场震动很大普通杜邦线在跑圈时松开是常有的事。我们曾经因为摄像头的排针接触不良在赛前测试时花了三小时查图像间歇性花屏最后重新插拔一遍排针就好了。硬件上的很多问题表面看是代码滤波不够本质上都是布局和接触问题。3. 从图像到车轮角速度PID闭环这条主链路3.1 为什么PID的输出是角速度而不是PWM占空比很多新手刚开始写转向控制时都是把图像偏差直接经过一个PID输出就是舵机PWM。我们第一版也这么干结果车速一过3m/s就开始不对劲同样一个偏差低速时转向力度合适高速时却明显过头车头左右甩。原因在于PWM占空比到舵机角度再到车体实际横摆角速度中间隔着舵机死区、响应延迟和轮胎侧偏这些环节在不同速度下根本不是线性关系。直接拿PID输出当占空比等于让一个线性控制器去控制一个非线性很强的对象。后来改成两级控制外环对图像横向偏差做PID输出一个目标角速度内环把陀螺仪Z轴角速度作为反馈闭环跟踪这个目标角速度。角速度是车体本身的运动量受车速和轮胎磨损影响小所以同一组参数在2.5m/s和4m/s下都能用。这就是智能车PID输出角速度这个说法的来源。3.2 内环到底怎么实现角速度反馈从哪里来角速度反馈优先用陀螺仪Z轴因为陀螺仪直接就是角速度输出不需要差分采样噪声也小。如果没有陀螺仪可以用左右编码器速度差除以轮距来估算但转向时内外侧轮的打滑情况不一样估算误差会偏大尤其在高速急转时直接用估算值闭环很容易振荡。我们的最终方案是陀螺仪做内环反馈编码器速度差只作为外环的补偿项。核心代码很短// 每2ms执行一次的方向控制 void SteeringControl(void) { float lateral_error GetLateralError(); // 图像中线偏差 pid_outer.Update(lateral_error); // 外环PID输出目标角速度 float omega_target pid_outer.output; float omega_real gyro.GetZ(); // 陀螺仪Z轴角速度 pid_inner.Update(omega_target - omega_real); // 内环PID输出舵机PWM servo.SetPwm(pid_inner.output); }控制频率这里强调一下方向和速度环至少2ms跑一次不要放在主循环里随便延时否则内环的相位滞后会直接把环路搞不稳定。我们用定时器中断触发所有控制代码都在中断里执行主循环只处理通信和显示任务。3.3 调参顺序先内环后外环别两个环一起动调试这一块我们走过弯路。一开始两个环的PID参数同时乱调车既摆头又响应迟钝根本分不清是谁的问题。后来总结了一套顺序先把外环的输出暂时接死直接把内环目标角速度设成阶跃信号单独调内环让舵机能快速、稳定地跟上目标然后固定内环参数再调外环。外环调参不能只看超调还要看角速度反馈曲线有没有高频抖动。如果反馈一直在目标值附近来回打颤说明内环增益太高或者微分项不合适如果外环输出已经很大、角速度却跟不上考虑是不是内环比值限制设低了。我们车最终用的一组起步参数大约是内环Kp1.2、Ki0.1、Kd0外环Kp0.35、Ki0.02、Kd0.08。这只是起点不同车模的舵机死区、机械结构和轮胎磨损都会改变最优值。4. 环岛、十字和坡道图像处理里最容易翻车的三个场景4.1 环岛识别不是看到弧线就进环环岛是21届规则里的高频考点也是我们调得最久的一个元素。很多代码的思路是图像里出现弧线就判断为环岛这种方案在单独测试时能过一上赛道就不行了因为十字出弯、斑马线、甚至路边的一条地砖缝都可能被误判成弧线。我们最终靠的是多特征组合核心条件有三个一是连续多帧检测到一侧边线出现稳定的弧状外凸而不是单帧突变二是该侧的边界斜率在连续几帧里保持同向变化说明车正在沿圆弧接近三是当前车速不低于一个门槛否则就认为是停在弯道附近的干扰。三个条件同时满足才触发进环策略。任何只依赖单特征的元素识别在实际赛道上都会出现概率性的误判。4.2 十字补线补的是逻辑不是像素十字补线可能是群里讨论最多的话题。新人容易纠结怎么让图像里穿过十字区域的那段线看起来完整其实没有必要真的去改图像像素。我们做的是算出中线之后把十字区域内的无效点标记出来丢掉再用两端有效中线做线性拟合外推得到当前应该跟踪的虚拟中线。这个做法的好处是速度快不用维护大块图像缓存坏处是当车离十字还很远时外推的距离比较长拟合值会偏。所以补线用的拟合窗口不能太长我们一般只取离车最近的两段有效数据外推长度限制在图像高度的一半以内。补线算法本质上要回答的不是这条线长什么样而是我现在应该朝哪个方向走。4.3 坡道判断别只靠图像陀螺仪和电机电流一起用坡道元素对图像算法的挑战是远端信息大面积丢失行边线在坡顶处会发生翻转如果只靠图像判断坡道很容易在坡顶前把转向打错。我们加了两路辅助信息一是陀螺仪X轴俯仰方向角速度入坡时会有明显的俯仰变化连续几帧积分后能给出一个俯仰角二是电机电流上坡时电流会比平路明显增大。把图像特征、俯仰角、电流三个信号做一个投票至少两个信号同时触发才认为进入坡道这样比单靠图像稳很多。很多队伍嫌加传感器麻烦但坡道场景下宁可多一个可信信号也不要靠猜。4.4 前瞻长度不该是一个写死的常数动态前瞻是我在调环岛时体会最深的一点。固定前瞻在低速弯道和高速直道上永远只能迁就一边前瞻取近了高速直道上发现弯道太晚前瞻取远了低速弯道里看到的全是远处干扰。我们最后把前瞻分成两档甚至三档根据当前速度和目标角速度输出实时切换。直道高速时用长前瞻让车早一点看到弯入弯后速度降下来再把前瞻缩短避免被远端弧线误导。切换过程不能跳变否则中线会跟着突变。这个逻辑写在算法目录的lookahead.c里参数不多但需要结合自己的赛道反复标定。5. 备赛期我们踩过的电路坑与代码坑5.1 三个典型硬件故障的排查链路备赛那几个月我们至少烧过三块驱动板、换过两套摄像头排线这里挑三个最典型的问题按排查链路写出来故障现象根因排查链路舵机低速时正常高速时持续抖动舵机供电电压被电机启动扯低先测空载电压再用示波器测动态波形发现6V跌落超0.8V换更大功率DC-DC并加储能电容解决图像右半屏规律性横纹摄像头数据线和编码器信号线相互串扰先换摄像头排除相机问题再用示波器对比PCLK与电机PWM波形发现排针并列走线过长分开两组排针后消除跑圈过程中单片机偶发复位逻辑电源跌落触发了欠压复位查复位引脚波形发现毛刺确认是电机大电流瞬间拉低5V增加一级低噪声LDO并用磁珠隔离后未再复现这些问题的共同点是症状在代码里看起来像干扰实际拿示波器一测基本都指向电源和布局。5.2 软件调试工具串口绘图比看数组快十倍调PID的时候只靠看车跑圈来感觉稳不稳太慢了。我们把控制量打包成固定帧头发出去用串口绘图上位机实时显示图像偏差、内环角速度、外环输出和电机转速。一组参数好不好看几条曲线比跑十圈更直观。协议也很简单帧头加数据长度加多个float加校验波特率用115200以上。关键是发送函数必须放在主循环而不是控制中断里避免影响控制时序。后来还加了一个录制功能把车上实时算出的所有关键变量按时间戳存到数组跑完圈再通过串口全部导出回放分析。这个习惯帮我们在入环甩尾问题上节省了很多时间。5.3 代码层面的隐形坑int/float混算与中断优先级新手代码里最常见的隐形坑是图像采集和PID控制共用中断或者图像处理用了大量float计算导致控制周期抖动。我们遇到过一个问题单片机工程里只要加入十字补线后舵机响应就变慢半拍后来定位是补线里的浮点拟合把中断占住了。解决方法是压缩补线拟合的时间能查表的就别实时算能在定点整数域做的就别用float。另一个坑是int和float混算造成的截断误差比如图像中线坐标本来是几个uint16相减如果直接和一个float型PID输出相乘编译器会做隐式转换结果在不同优化等级下都不一样。建议所有控制变量统一用float图像坐标只在最外层转换成浮点避免在算法内部混用类型。6. 开源目录的正确打开方式先复现再改造别被开源资料喂饱6.1 完整复现的路径先读文档再跑原版工程最后动硬件如果你是第一次接触智能车拿到这份目录后不要直接跳去改算法。我的建议是三步走第一步把00_Docs里所有文档按顺序看一遍特别是调试笔记和规则摘录先建立对整车系统的整体认识第二步用与开源版本相同的单片机型号建一个最小工程先把原版代码烧进同一款开发板跑通基本循迹流程确认代码能编译、图像能输出、控制能响应第三步再把注意力放到PCB目录参考电源拓扑和接口分布画一块自己的板子。复现阶段的目标不是超越原版而是确保自己的环境能够原样运行这套代码。很多人跳过第一步直接去改参数最后代码改了却不知道改的是什么反倒更难调试。6.2 哪些资料只适合参考不适合照抄开源目录里不是所有数据都能直接搬到自己车上。PID参数是典型的参考值因为不同车模的舵机死区、重心位置、轮胎磨损完全不同照抄只会得到一个看起来有模有样、实际跑起来很别扭的车图像阈值也一样赛道光照、反光、地胶颜色都会改变最佳值。PCB布局则要谨慎抄可以借鉴模块划分和电源树但整板照抄连元器件封装尺寸都一样的话很容易在焊接和调试阶段翻车因为你并不理解当初为什么在这个位置放了一颗电容。机械结构更是如此每辆车的电机安装角度、轮距、重心高低都会影响控制参数开源视频里的跑圈效果是它自己整车系统的结果不是单独某个参数的结果。6.3 复现之后怎么办从改参数到改逻辑当你已经能让车在原版代码基础上跑起来接下来就该有意识地脱离原版了。我建议的顺序是先改参数记录每个参数对车的影响再改逻辑比如把固定前瞻改成动态前瞻最后尝试重构某个模块哪怕只是把补线算法换成自己想的方案。这个阶段犯错误是好事因为只有在你理解了原版为什么这么写之后你才能知道自己改得对不对。开源目录的意义不是让你照着一份代码去比赛而是让你站在这份代码的肩膀上更快地形成自己的调试方法。回到标题里的开源目录我想说的其实是开源不是把代码丢出去就结束了好的开源目录应该是一份可以让人沿着你的思路重走一遍备赛过程的说明书。如果这篇文章能让准备参加下一届比赛的队伍少走几个弯路少烧几块板子那这份目录就值了。最后再分享一个小技巧不管目录整理得多漂亮一定要在自己还记得每个文件是干什么的时候写一份README。等比赛结束两三个月再回头补你会发现很多当时觉得这还用记吗的东西已经彻底想不起来了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询