1. 从一块烧掉的驱动板说起:为什么我要把两套方案全部开源
21届智能车竞赛结束那天,我把车从赛道上抱下来,第一件事不是庆祝,而是把主控板上那颗烧糊的MOS管拍了个特写发到群里。配文只有一句:这玩意儿我调了三个月,烧了四块板子,今天终于跑完了全程。底下有人回:开源吗?我想了想,回了一个字:开。
这就是这篇内容的由来。我参加的是21届全国大学生智能车竞赛,组别涉及视觉方案和惯导方案两条技术路线,最终拿到了国赛特等奖。赛后我把两套完整的方案——包括PCB工程文件、核心控制代码、视觉算法参数、惯导标定流程——全部整理开源了。不是因为我有多高尚,而是因为我太清楚那种“对着一个不亮的LED查一整个通宵”的滋味了。如果我的踩坑记录能让后面的人少烧一块板子、少熬一个通宵,那这件事就值了。
这篇文章面向的是准备参加智能车竞赛的在校学生,尤其是第一次接触嵌入式硬件和视觉/惯导方案的初学者。我会把两套方案的设计思路、PCB布局的关键决策、视觉算法的调参逻辑、惯导的标定流程,以及我在调试过程中遇到的各种“玄学问题”全部拆开讲清楚。不管你是刚拿到开发板的新手,还是已经跑过一届想优化方案的老手,应该都能从里面找到有用的东西。关键词就三个:开源、智能车、视觉与惯导双方案。下面直接进入正题。
2. 双方案整体设计:为什么视觉和惯导要分开做两套
2.1 两条技术路线的本质差异
视觉方案和惯导方案,本质上解决的是同一个问题:让车知道自己在哪、该往哪走。但它们的实现路径完全不同。视觉方案靠摄像头采集赛道图像,通过图像处理提取赛道边线和中线,再计算舵机打角和电机转速。惯导方案则依赖陀螺仪和加速度计,通过积分运算推算车的姿态和位置变化,结合赛道元素(比如电磁信号或者预设的路径信息)来决策。
我选择两套都做,原因很直接:视觉方案在光照稳定的室内赛道上精度高、响应快,但对光线变化极其敏感;惯导方案不受光照影响,但积分漂移是绕不过去的坎,跑久了姿态就飘了。21届的赛道元素比往年更复杂,有连续S弯、十字交叉、坡道和环岛,单靠一套方案很难在所有元素上都做到最优。所以我的策略是:视觉方案作为主力,惯导方案作为冗余和互补,两套方案共用同一套底层驱动和PCB平台,通过软件切换。
这个决策背后有一个很实际的考量:PCB只做一版,但通过跳线帽和软件配置可以适配两种传感器组合。这样既省了打板成本,也省了调试两套硬件的时间。我见过太多队伍做了两三版板子,最后时间全花在焊接和飞线上了,算法反而没时间调。
2.2 硬件平台的统一设计思路
统一硬件平台的核心在于主控选型和接口预留。我选的是TC264D,双核200MHz,对于视觉方案来说,它的算力足够跑灰度图像处理和一阶滤波;对于惯导方案来说,它的浮点运算能力也够用。更重要的是,TC264的外设资源丰富,有足够的ADC通道、PWM输出和编码器接口,不用外扩太多芯片。
PCB设计上,我做了几个关键决策。第一,摄像头接口和IMU接口都做成独立排针,通过跳线选择哪一路信号进入主控的哪个引脚。第二,电源部分做了两路独立的LDO,一路给主控和数字传感器,一路给舵机和电机驱动,避免电机启动时的电流波动干扰传感器。第三,所有关键信号线(摄像头数据线、IMU的SPI线)都做了包地处理,减少串扰。
注意:电源分割是智能车主控板设计中最容易被忽视的环节。我第一版板子就是把舵机电源和主控电源混在一起,结果舵机一转,摄像头图像就出现横纹,查了两天才发现是电源耦合。
2.3 为什么选择开源而不是闭源
智能车竞赛的圈子有一个不太好的风气:很多人把方案捂得严严实实,生怕别人学了去。我理解这种心态,毕竟大家都是竞争关系。但我个人的经历是,我入门的时候全靠学长留下的几块旧板和一份残缺的代码,很多地方看不懂,只能自己摸索。如果当时有人能给我一份完整的工程文件,我至少能省下两个月的时间。
开源还有一个好处:倒逼自己把东西做规范。当你知道别人会看你的代码和PCB时,你会不自觉地加注释、整理文件结构、把那些“临时凑合”的飞线去掉。我在整理开源文件的过程中,重新审视了自己的代码,发现了好几处逻辑冗余和潜在bug,顺手就修掉了。
3. PCB设计实操:从原理图到打板的全流程避坑指南
3.1 主控板原理图设计的关键模块
主控板的原理图分为几个核心模块:电源模块、主控最小系统、摄像头接口、IMU接口、电机驱动接口、舵机接口、编码器接口、调试接口。每个模块都有需要注意的地方。
电源模块我用了两级降压:第一级用TPS563201把电池电压降到5V,第二级用AMS1117-3.3降到3.3V给主控和传感器供电。TPS563201的开关频率是580kHz,效率在90%以上,发热很小。这里有一个细节:TPS563201的使能引脚我加了一个RC延时电路,让5V输出比3.3V输出晚启动约10ms,确保主控先上电、传感器后上电,避免传感器在电源不稳定时输出错误数据。
主控最小系统部分,TC264的晶振我用的是20MHz无源晶振,负载电容选了12pF。这个值不是随便选的,要根据晶振的规格书来。我一开始用了22pF,结果起振时间偏长,偶尔会启动失败。后来查了晶振的负载电容公式:CL = (C1 * C2) / (C1 + C2) + Cstray,其中Cstray是PCB走线的寄生电容,一般取3-5pF。代入计算后,12pF是比较合适的值。
摄像头接口我预留了8位数据线、PCLK、VSYNC、HSYNC和SCCB控制线。这里有一个坑:数据线一定要等长走线,否则高速采集时会出现数据错位。我第一版板子没有注意这个问题,图像上偶尔会出现随机的噪点,后来把数据线重新布线,保证每根线的长度差不超过5mm,问题就消失了。
3.2 PCB布局与走线的实战经验
PCB布局我遵循一个原则:按信号流向布局,从电源到主控到传感器到执行器,一条线走到底,不要来回绕。主控放在板子中央,摄像头接口放在板子前端靠近车头的位置,IMU放在板子中心且远离电机驱动,电机驱动和舵机接口放在板子后端。
走线方面,有几个硬性规则。电源线宽度不低于20mil,电机驱动的大电流线不低于40mil。信号线宽度6-8mil,差分信号线(如果有)要严格等长。过孔尽量少打,尤其是电源线上的过孔,每个过孔都会引入寄生电感。我数了一下,我的主控板上电源路径上一共只打了3个过孔,而且都用了双过孔并联。
实操心得:PCB打板前一定要做DRC检查,但不要完全信任DRC。我遇到过DRC通过但实际短路的情况,原因是焊盘之间的间距太小,焊接时锡渣搭桥了。所以关键区域(比如主控引脚之间)要手动检查间距,至少保证0.2mm以上。
还有一个容易被忽视的点:螺丝孔的放置。智能车主控板一般用M3螺丝固定,螺丝孔内径3.2mm,外径6mm,周围要留出至少2mm的禁布区,否则螺丝头会压到走线或元件。我第一版板子就是把螺丝孔放在了一个电容旁边,拧螺丝的时候直接把电容压碎了。
3.3 打板文件输出与焊接注意事项
打板文件输出时,我一般会导出Gerber文件和钻孔文件,同时生成一份坐标文件给贴片厂。Gerber文件导出后,一定要用Gerber查看器打开检查一遍,确认每一层都正确。我遇到过一次因为层选错了,阻焊层和丝印层搞反了,板子回来之后丝印全是反的,虽然不影响功能,但看着很别扭。
焊接方面,我建议先焊电源部分,焊完测一下电压是否正常,再焊主控和传感器。这样如果电源有问题,不会把后面昂贵的芯片烧掉。TC264的引脚间距是0.5mm,手工焊接需要一定的技巧。我的做法是:先用烙铁把芯片对准位置固定两个对角引脚,然后用热风枪配合焊膏整体加热。热风枪温度设到280度,风量调到最小,避免把旁边的元件吹飞。
4. 视觉方案核心实现:从图像采集到舵机打角的全链路拆解
4.1 摄像头选型与图像采集配置
视觉方案我选的是总钻风摄像头,MT9V034全局快门,分辨率可以配置到188x120。全局快门的好处是高速运动时不会出现果冻效应,这对智能车来说很关键。卷帘快门在车快速转弯时,图像会扭曲,边线提取就会出错。
图像采集的配置有几个关键参数。曝光时间我设的是自动曝光,但限制了最大曝光时间,避免在暗处曝光过长导致帧率下降。增益也是自动的,但上限设得比较保守,因为高增益会引入噪声。采集回来的原始图像是灰度图,每个像素8位,一帧188x120的数据量是22560字节,TC264的RAM完全放得下。
图像预处理我做了三步:第一步是去噪,用3x3的中值滤波,去掉椒盐噪声;第二步是二值化,用大津法自适应阈值,把灰度图变成黑白图;第三步是边缘提取,用Sobel算子计算梯度,找到赛道边线的候选点。
4.2 赛道边线提取与中线拟合算法
边线提取是视觉方案的核心。我的做法是:从图像底部往上逐行扫描,每行找到左右两个边线的跳变点。跳变点的判断依据是灰度值从白到黑或从黑到白的突变。为了提高鲁棒性,我加了两个约束:一是跳变点的灰度差要大于一个阈值,二是跳变点的位置要和上一行的边线位置连续,不能跳太远。
中线拟合我用的是最小二乘法,把左右边线的中点作为样本点,拟合一条二次曲线。为什么用二次曲线而不是直线?因为赛道有弯道,直线拟合在弯道处误差太大。二次曲线能很好地描述弯道的曲率变化。拟合出来的曲线方程是 y = ax^2 + bx + c,其中a反映弯道的急缓程度,b反映车头的偏转角度,c反映车在赛道中的横向位置。
舵机打角的计算基于中线的斜率和曲率。具体来说,我取图像底部往上20行的中线点,计算这些点的切线斜率,然后根据斜率查表得到舵机的PWM占空比。这个表是我在调试过程中一点点试出来的,从直道的中间值开始,往左弯和右弯各扩展,每个角度对应一个占空比。
4.3 视觉方案的调参记录与效果验证
调参是视觉方案最耗时的环节。我前后调了大概两周,主要调三个参数:二值化阈值、边线提取的跳变阈值、舵机打角的比例系数。
二值化阈值我用的是大津法自动计算,但在实际使用中发现,大津法在赛道上有阴影的时候会失效。后来我加了一个补偿:如果图像上半部分的平均灰度明显低于下半部分,说明有阴影,就把阈值调低10%。这个补偿逻辑很简单,但效果很明显。
边线提取的跳变阈值我设的是30,也就是灰度差大于30才认为是边线。这个值太小会把噪声当成边线,太大又会漏掉浅色的边线。我试过20和40,最后30是最稳的。
舵机打角的比例系数我用了分段控制:小角度时系数小一点,避免抖动;大角度时系数大一点,保证响应速度。具体来说,当计算出的打角小于10度时,系数是0.8;10到20度时,系数是1.0;大于20度时,系数是1.2。这个分段策略让车在直道上很稳,在弯道里又能快速切入。
5. 惯导方案核心实现:姿态解算与漂移抑制的实战技巧
5.1 IMU选型与数据预处理
惯导方案我选的是ICM20602,六轴IMU,三轴陀螺仪加三轴加速度计,SPI接口,最高采样率8kHz。我实际用的采样率是1kHz,每1ms读一次数据。ICM20602的陀螺仪量程我设的是±2000dps,加速度计量程是±8g。量程的选择要匹配实际使用场景:智能车的角速度一般不会超过500dps,但过弯时可能会有瞬间的尖峰,所以留了足够的余量。
数据预处理我做了两步:第一步是零偏校准,车静止时采集1000个样本,取平均值作为零偏,后续每次读取都减去这个零偏;第二步是低通滤波,用一阶巴特沃斯滤波器,截止频率设的是50Hz,滤掉高频振动噪声。这里有一个细节:滤波器的截止频率不能设太低,否则会引入相位延迟,影响姿态解算的实时性。
5.2 姿态解算:互补滤波与卡尔曼滤波的取舍
姿态解算我用的是互补滤波,而不是卡尔曼滤波。原因很简单:互补滤波计算量小,TC264跑起来毫无压力,而且参数调节直观。卡尔曼滤波虽然理论上更优,但调参太麻烦,Q和R矩阵不好确定,调不好反而效果更差。
互补滤波的核心思想是:陀螺仪积分得到角度,短期精度高但会漂移;加速度计测量重力方向得到角度,长期稳定但噪声大。两者加权融合,高频部分信任陀螺仪,低频部分信任加速度计。我的融合系数是0.98,也就是98%信任陀螺仪,2%信任加速度计。这个系数是试出来的,0.95的话角度会跟着加速度计的噪声抖动,0.99的话漂移抑制不够。
姿态解算的输出是三个欧拉角:横滚角、俯仰角、偏航角。智能车主要用偏航角来判断车头的方向变化。偏航角的积分漂移是惯导方案最大的问题,我跑一圈大概30秒,偏航角会漂移5到10度。为了抑制漂移,我加了一个“零速修正”:当编码器检测到车速为零时,强制把偏航角的积分增量置零。这个逻辑很简单,但效果很好,漂移量降到了2度以内。
5.3 惯导方案与视觉方案的融合策略
两套方案的融合我做了两种模式。模式一是“视觉为主,惯导为辅”:正常情况下用视觉方案决策,当视觉方案连续多帧丢失边线时,切换到惯导方案,用偏航角的变化来维持车的方向,直到视觉方案恢复。模式二是“惯导为主,视觉为辅”:在赛道元素复杂、视觉容易误判的区域(比如十字交叉),用惯导方案主导,视觉方案只提供横向位置修正。
融合的切换逻辑我用了状态机:视觉正常时状态为VISION,视觉丢失超过5帧时状态切到IMU,IMU状态下如果视觉恢复连续3帧则切回VISION。这个状态机避免了频繁切换导致的抖动。
实操心得:融合策略的调试一定要在实车上做,仿真和理论分析只能参考。我在仿真里跑得很好的融合逻辑,实车上因为传感器延迟和机械响应的问题,完全不是那么回事。后来我把切换的阈值从5帧改到8帧,抖动就小了很多。
6. 常见问题与排查技巧实录
6.1 硬件类问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 主控上电后不启动 | 电源电压不足或晶振未起振 | 万用表测3.3V和5V,示波器测晶振引脚 | 检查LDO输出,更换晶振负载电容 |
| 摄像头图像有横纹 | 电源耦合或数据线串扰 | 示波器测电源纹波,检查数据线走线 | 分离电源,数据线包地,加去耦电容 |
| IMU数据跳动大 | 电源噪声或SPI时钟太快 | 示波器测SPI波形,检查电源纹波 | 降低SPI时钟,加LC滤波 |
| 舵机抖动 | PWM信号不稳定或电源不足 | 示波器测PWM波形,测舵机供电电压 | 加RC滤波,独立供电 |
| 电机启动时主控复位 | 电源跌落 | 示波器测电机启动瞬间的电源电压 | 加大电容,分离电机和主控电源 |
6.2 软件类问题排查思路
软件问题最典型的是“跑着跑着就飞了”。这种情况一般是数组越界或者指针错误。我的排查方法是:在关键数组的读写处加边界检查,如果越界就点亮一个LED或者通过串口打印错误信息。TC264有硬件看门狗,我开了看门狗,如果程序跑飞了会自动复位,至少不会一直失控。
还有一个常见问题是“图像处理耗时太长”。我一开始的图像处理代码用了浮点运算,一帧要处理20ms,导致控制周期只有50Hz,车跑快了就反应不过来。后来我把浮点运算全部改成定点运算,一帧处理时间降到了5ms,控制周期提到了200Hz,车的响应明显变快。
6.3 调试工具与技巧分享
调试工具我主要用三个:示波器、逻辑分析仪和串口打印。示波器用来看电源纹波和PWM波形,逻辑分析仪用来看SPI和I2C的时序,串口打印用来输出中间变量。串口打印有一个技巧:不要在主循环里直接打印,因为串口发送很慢,会阻塞控制循环。我的做法是把要打印的数据存到缓冲区,在控制循环结束后统一发送。
还有一个技巧是“二分法排查”:当问题出现时,先判断是硬件还是软件,再判断是哪个模块,然后逐步缩小范围。比如车不跑,先看电机转不转,电机不转就看驱动信号有没有,驱动信号没有就看主控有没有输出,一层层往下查,很快就能定位。
7. 开源文件结构与使用说明
7.1 开源仓库的组织方式
开源仓库我分了五个目录:Hardware、Firmware、Vision、IMU、Docs。Hardware里是PCB工程文件,包括原理图和PCB源文件,用的是嘉立创EDA格式,也导出了Gerber文件方便直接打板。Firmware里是主控的底层驱动和主循环代码,用C语言写的,基于TC264的官方库。Vision里是视觉方案的完整代码,包括图像采集、预处理、边线提取和舵机控制。IMU里是惯导方案的代码,包括数据读取、姿态解算和融合逻辑。Docs里是调试记录和参数说明,包括我踩过的坑和对应的解决方案。
每个目录下都有一个README文件,说明这个目录的内容和使用方法。代码里我加了详细的注释,关键参数都标了取值范围和调节建议。PCB文件里我标注了关键元件的型号和封装,方便直接采购。
7.2 如何快速上手这套方案
如果你刚拿到这套方案,我建议按这个顺序上手:第一步,先看Docs里的调试记录,了解整体思路和关键决策;第二步,打板焊接,先焊电源部分,测电压正常后再焊主控和传感器;第三步,烧录Firmware里的测试程序,确认主控、摄像头、IMU都能正常工作;第四步,选择视觉或惯导方案,烧录对应的代码,在静态下调试参数;第五步,上车跑,从低速开始,逐步提速。
注意:不要一上来就两套方案一起跑。先把一套跑通,再融合另一套。我见过有队伍两套方案同时调,结果出了问题不知道是哪套的锅,排查起来非常痛苦。
7.3 后续优化方向与扩展思路
这套方案还有不少优化空间。视觉方面,可以尝试用边缘检测加霍夫变换来提取赛道边线,比逐行扫描更鲁棒。惯导方面,可以尝试用扩展卡尔曼滤波替代互补滤波,进一步抑制漂移。融合方面,可以尝试用模糊控制来动态调整两套方案的权重,而不是简单的状态机切换。
硬件方面,下一版我打算把主控换成TC377,算力更强,可以跑更复杂的算法。电源部分打算用PMIC替代LDO,效率更高,发热更小。PCB打算做四层板,信号完整性和电源完整性都会更好。
这套方案我前后调了三个月,烧了四块板子,熬了无数个通宵。开源出来,希望能帮到后面的人。智能车这个圈子,技术迭代很快,但分享的精神不能丢。我入门的时候靠的是学长的旧板和残缺的代码,现在轮到我把东西传下去了。愿智能车越来越好,愿每一个熬夜调车的人都能跑完全程。