1. 从赛道到代码:一场智能车竞赛的完整复盘
刚结束的第十六届全国大学生智能汽车竞赛,像一场持续了数月的技术马拉松,终于冲过了终点线。从最初拿到赛题规则时的茫然,到中期调车调参的焦头烂额,再到最后赛场上的惊心动魄,整个过程浓缩了嵌入式开发、自动控制、机器视觉和团队协作的几乎所有核心挑战。我所在的队伍选择了基础四轮组,主控芯片没有随大流用STM32,而是尝试了基于RISC-V架构的CH32V103R8T6,这让我们在硬件和底层驱动上多花了不少功夫,但也收获了更深入的理解。这篇总结,我想抛开那些官方的技术报告模板,从一个一线队员的视角,聊聊我们是怎么把一堆芯片、传感器和代码,变成一辆能在赛道上自主狂奔的智能车的。无论你是准备参赛的新手,还是对嵌入式AI小车感兴趣的爱好者,希望这些踩过的坑和总结的经验,能给你一条更清晰的路径。
2. 整体方案设计与核心思路拆解
2.1 赛题理解与组别选择背后的逻辑
第十六届的规则在继承经典元素的同时,也引入了一些新变化。比如基础四轮组,赛道元素更加复杂,增加了环岛、十字路口、坡道等组合,对路径识别的鲁棒性和控制算法的适应性提出了更高要求。我们选择基础四轮组,主要是基于团队技术栈的考量:摄像头图像处理是我们的强项,而平衡节能、双车接力等组别对机械、控制的要求维度不同。选择往往比努力更重要,在备赛初期,花时间吃透规则、评估自身团队在视觉、控制、机械、电路等方面的能力长短板,是决定后续所有工作方向的基础。
注意:不要盲目追求“热门”或“高端”组别。比如摄像头三轮组对图像处理和控制耦合要求极高,节能组对硬件能效和软件优化是极致考验。选择与团队核心能力最匹配的组别,才能最大化备赛效率。
2.2 “RISC-V+CMOS摄像头”核心架构选型
我们的硬件核心是CH32V103R8T6 MCU和一款全局快门的MT9V034 数字摄像头。选型理由如下:
MCU:为什么是CH32V103R8T6?
- 性价比与可控性:在芯片普遍紧缺和价格上涨的背景下,这款基于RISC-V内核的MCU提供了不错的性能(108MHz主频)和丰富的外设(ADC、定时器、DMA等),且价格相对稳定。更重要的是,其开发环境(MounRiver Studio)和底层库相对简洁,迫使我们去更深入地理解寄存器配置和时序,而不是过度依赖黑盒化的HAL库。这对于学习嵌入式本质是有益的。
- 挑战与机遇:挑战在于社区资源远不如STM32丰富,很多驱动需要自己移植或从头编写。机遇也在于此,没有现成的“保姆式”代码,逼着我们搞清楚了从时钟树配置、GPIO初始化到中断优先级管理的每一个细节,这种能力在赛后看来是无价的。
传感器:摄像头 vs. 电磁?
- 我们坚定选择了摄像头方案。虽然电磁方案受光线影响小,稳定性高,但摄像头方案的信息密度是碾压级的。它能识别赛道边界、中心线、元素(环岛、十字等),为控制算法提供前瞻性和预判能力。MT9V034全局快门可以有效避免果冻效应,在车体高速晃动时依然能获取清晰的图像,这是成功的关键。
- 核心思路:将复杂的赛道环境,通过摄像头转化为二值化的图像矩阵,再从中提取出用于决策的“特征”——通常是左右赛道边界线。一切控制都基于这个抽象的“赛道模型”。
2.3 系统框架:从像素到电机PWM的信号流
我们的软件系统是一个典型的“感知-决策-控制”闭环,但具体实现上分层清晰:
[图像采集 (DMA)] -> [图像处理 (二值化,寻线)] -> [路径计算 (中线,曲率)] -> [控制决策 (PID, 状态机)] -> [执行输出 (电机PWM, 舵机PWM)]每一层都通过清晰的接口(变量、结构体)耦合,方便单独调试和优化。例如,图像处理模块只负责输出一个包含左右边线位置的数组,控制模块不关心这个数组是如何得来的。这种模块化设计在后期调参时节省了大量时间。
3. 核心模块实现与关键技术细节
3.1 CH32V103R8T6的底层驱动构建
这是所有工作的地基。由于缺乏成熟的生态,我们相当于自己搭建了一个微型“操作系统”。
时钟系统配置:
- CH32V103最高可运行在108MHz。我们选择使用外部8MHz晶振,通过PLL倍频到96MHz作为系统主频。这一步需要在启动文件
startup_ch32v10x.S和系统初始化函数中正确配置相关的时钟控制寄存器。 - 关键点:必须确保为外设(如定时器、ADC、摄像头接口)分配的时钟源和分频系数正确,否则后续所有时序都会错乱。我们曾因为ADC时钟配置错误,导致采样值完全不可信,排查了大半天。
- CH32V103最高可运行在108MHz。我们选择使用外部8MHz晶振,通过PLL倍频到96MHz作为系统主频。这一步需要在启动文件
GPIO与中断配置:
- 摄像头的数据引脚(D0-D7)、行场同步信号(VSYNC, HREF)需要配置为浮空输入模式,并开启上升沿/下降沿中断。
- DMA应用:这是提升性能的关键。我们配置了DMA通道,将摄像头数据端口(GPIO组)直接搬运到内存中的图像缓冲区。在VSYNC(帧同步)中断中启动DMA,在HREF(行同步)中断中配合DMA完成一行数据的接收。这样CPU几乎不参与数据搬运,解放出来进行图像处理。
定时器与PWM生成:
- 使用高级定时器TIM1产生四路PWM,分别控制两个电机的正反转和速度(实际上用了两路PWM加两个GPIO方向控制,实现双极性驱动)。
- 使用通用定时器TIM2配置为编码器模式,直接读取电机编码器的脉冲数,用于计算实际速度,构成速度闭环。
- PID计算中断:另一个通用定时器TIM3被配置为固定频率(如1kHz)的中断,在这个中断服务函数中进行速度PID和方向PID的计算,并更新PWM占空比。中断优先级要合理设置,确保控制周期稳定。
3.2 图像采集与二值化处理优化
图像处理的速度和稳定性直接决定了车的上限。
稳定采集:
- 除了使用DMA,还要注意信号消抖。摄像头VSYNC和HREF信号在硬件连接较长时可能有毛刺。我们在中断服务函数入口添加了简单的延时判断,连续采样几次引脚状态,确认是稳定电平变化后才执行后续操作,避免了误触发。
动态阈值二值化:
- 固定阈值在光线变化时效果很差。我们采用了大津法(OTSU)或自适应局部阈值。
- 大津法:在每帧图像开始处理前,对整帧或感兴趣区域(ROI)的灰度直方图进行计算,自动得出一个最佳分割阈值。计算量稍大,但全局效果稳定。
- 自适应阈值:将图像分成若干小网格,对每个网格单独计算阈值(如取均值或中值)。这种方法对光照不均的赛道适应性更强。我们最终采用了网格自适应,因为赛场顶光往往会造成赛道中间亮、两边暗。
- 优化技巧:不必对整幅图像(例如188*120)进行阈值计算。可以只对图像下方几行(车近处的赛道)和上方几行(远处的前瞻)分别计算阈值,既能适应光照变化,又减少了计算量。
3.3 赛道元素识别与状态机设计
识别出直道、弯道、十字、环岛等元素,是进行智能决策的前提。
边界搜索与中线提取:
- 采用经典的“爬边线”算法。从图像底部中心开始,向左向右搜索黑白跳变点,作为初始边线。然后逐行向上,以上一行的边线位置为起点进行小范围搜索,得到当前行的边线。将所有边线点拟合成两条曲线,其中间线即为引导线。
- 丢线处理:当某一行搜索不到边线时,不能简单停止。我们采用“记忆外推”策略,用之前几行边线的斜率或曲率,预测当前行的可能位置,扩大搜索窗口。如果连续多行丢线,则触发“丢线状态”,控制策略切换到保守模式(如减速、按上一有效曲率转向)。
元素识别逻辑:
- 十字路口:当左右边线同时大幅向外发散,且中间区域在一定行数内持续为白色(赛道色)时,判定为十字。我们的策略是进入十字后,保持进入前的角度和速度直行通过,同时抑制边线搜索,防止误搜到十字的横向边线。
- 环岛:这是难点。我们通过识别边线的“突变”来判断环岛入口。当单边边线(例如右边线)突然向内凹陷(形成弧口),而另一边线相对正常时,初步判定为环岛入口。随后进入一个专门的“环岛状态机”。
- 环岛状态机设计:
- 状态1(进入):识别到入口特征,开始记录内侧边线轨迹。
- 状态2(循迹):不再跟踪外侧边线,而是以识别到的内侧弧线作为单边引导线,同时结合陀螺仪(MPU6050)的Z轴角速度积分,判断车身是否已绕行约270度。
- 状态3(出岛):当角度积分达到预设值,且图像重新出现正常的双边线时,切换回普通循迹模式。
- 关键:出岛判断必须图像和陀螺仪数据融合,单靠任何一个都容易出错(提前出岛或卡在岛内)。
3.4 控制算法:PID与更高级的策略
双闭环PID控制:
- 速度环(内环):输入是目标速度(由路径曲率映射得到:弯道慢,直道快)和编码器反馈的实际速度,输出是电机PWM。使用PI控制器即可,积分项能消除静差,但要注意积分饱和,需要做抗饱和处理。
- 方向环(外环):输入是期望路径的横向偏差(中线与图像中心的偏差)和偏差变化率,输出是舵机目标角度(或直接是舵机PWM)。使用PD控制器。微分项能预测偏差趋势,让转向更平滑,抑制过冲。
- 参数整定心得:先调方向环,再调速度环。在车静止时,用手推动小车产生横向偏差,观察舵机能否快速、无超调地将车轮回正。然后低速在直道上跑,微调参数使车能沿直线行驶。最后上弯道,重点调整微分系数,抑制振荡。
前瞻与曲率预瞄:
- 简单的偏差控制是“滞后”的,因为它基于车当前位置的误差。我们引入了“前瞻点”概念。从图像中线上选取一个距离车头一定距离的点(例如图像顶部往下20行),计算该点与图像中心的横向偏差。用这个前瞻偏差来控制舵机,相当于让车提前转向,过弯更加流畅自然。
- 曲率计算:通过拟合出的中线,计算其曲率。曲率可以直接映射为目标速度(大曲率->低速),也可以作为前馈量加入到方向环控制中,实现“弯道提前打舵”。
4. 系统调试与性能优化全记录
4.1 开发调试环境搭建
- 硬件调试器:我们使用WCH-Link通过SWD接口对CH32V103进行程序下载和在线调试。MounRiver Studio内置的调试功能基本够用,可以查看变量、设置断点。
- 软件调试“后门”:
- 无线串口:在车上加装一个蓝牙串口模块(如HC-05),将关键数据(如图像行数据、边线位置、PID输出、状态机状态)实时发送到电脑上位机。
- 上位机软件:我们基于Python的PyQt5和OpenCV自己编写了一个简单的上位机。它能绘制出摄像头看到的二值化图像、提取的边线、计算出的中线,并以波形图形式显示速度、偏差等数据。这是调试效率提升十倍的关键。你可以直观地看到车“眼中”的世界,以及控制算法是如何理解的。
- 参数在线调参:在上位机中制作滑动条,通过无线串口动态修改车上的PID参数、阈值等,并立即观察效果,避免了反复修改代码、编译、下载的繁琐过程。
4.2 机械结构与参数调校
“软件决定上限,机械决定下限。”再好的算法,如果车装得歪歪扭扭,也跑不好。
- 重心与陀螺仪安装:电池、主板等重物尽量放低、居中。MPU6050陀螺仪模块必须用海绵胶牢牢固定在车体中心,并与车身轴线平行,避免振动干扰。
- 前轮前束与主销内倾:对于舵机转向的车,前轮通常设置为微小的“内八字”(前束),这有助于提高直行稳定性。这些微调需要耐心,每次只动一点,然后上路测试直行是否跑偏。
- 轮胎处理:新轮胎表面光滑,抓地力不足。我们用细砂纸轻轻打磨轮胎表面,增加摩擦力。胎压也要一致,不能一软一硬。
4.3 代码级优化技巧
当算法逻辑正确后,优化就是为了跑得更快。
减少计算量:
- 缩小ROI:车在高速运行时,远处图像细节来不及处理。我们动态调整图像处理的行数,车速越快,处理的图像行数越少(只关注近处赛道),保证控制周期稳定在5ms以内。
- 查表法:将一些频繁计算的结果预先算好,存入数组。例如,
atan2、sqrt函数非常耗时,我们可以根据偏差和偏差变化率,预先计算一个PD控制量的二维查找表。 - 整数运算:在MCU上,浮点运算速度远慢于整数运算。我们将所有PID参数、误差等变量都乘以一个放大系数(如1024),用
int32_t类型进行整数运算,最后输出时再缩小。精度完全足够。
提高系统稳定性:
- 看门狗:一定要开启独立看门狗(IWDG)。在main函数循环和关键任务中定期“喂狗”。一旦程序跑飞或陷入死循环,看门狗能复位系统,至少让车停下来,而不是撞毁。
- 堆栈大小设置:在启动文件中适当调大堆栈(Stack_Size)。复杂的函数调用和局部变量可能造成栈溢出,导致各种难以复现的诡异错误。
5. 赛场实战问题排查与应急方案
无论实验室跑得多好,赛场永远是另一回事。以下是我们遇到和观察到的高频问题:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 发车后原地不动或抽搐 | 1. 电机驱动桥未使能或损坏。 2. 编码器接线松动,速度反馈为0导致PID输出异常。 3. 程序未正常进入主循环。 | 1. 检查电机驱动芯片的使能引脚电平,测量电机两端电压。 2. 晃动编码器接线,在调试器查看编码器计数值是否变化。 3. 检查启动代码,添加LED闪烁指示,确认程序运行。 |
| 直道左右摇摆(振荡) | 1. 方向环PID微分系数D太小或为负。 2. 机械松动,舵机连杆有虚位。 3. 图像处理延时过大,控制滞后。 | 1.首要降低P值,增加D值。D是抑制振荡的关键。 2. 用手轻轻晃动前轮,检查是否有松动。 3. 通过上位机查看从图像采集到舵机输出的总延时,优化代码。 |
| 过弯时冲出赛道 | 1. 弯道速度过快。 2. 前瞻距离设置太短,转向不及时。 3. 图像在弯道丢线,策略未正确处理。 | 1. 建立曲率-速度映射表,弯道曲率越大,目标速度越低。 2. 增加前瞻点距离,让车“看得更远”。 3. 加强弯道处的丢线处理逻辑,如使用预测线。 |
| 元素(十字、环岛)误识别或漏识别 | 1. 光线变化导致二值化阈值失效。 2. 元素识别条件阈值设置不合理。 3. 车体经过元素时姿态不稳定,图像抖动。 | 1. 必须使用动态阈值。 2. 在赛场不同光照下大量测试,记录数据,调整识别条件的阈值(如白色连续行数、边线发散角度)。 3. 在元素识别期间,适当降低速度,提高图像稳定性。 |
| 跑着跑着突然复位 | 1. 电源不稳定,电压跌落触发欠压复位。 2. 程序堆栈溢出。 3. 看门狗未及时喂狗。 | 1. 用示波器监测电池电压,大电流负载时是否跌落到MCU最低工作电压以下。加大电容稳压。 2. 增加堆栈大小,检查是否有大型局部数组。 3. 检查看门狗喂狗函数是否在所有可能的主循环路径中都被调用。 |
| 无线调试突然断开 | 1. 赛场无线环境复杂,2.4GHz频段干扰。 2. 蓝牙模块供电不足。 | 1. 准备备用方案:将关键数据通过IO口输出到逻辑分析仪,或使用SD卡离线记录数据。 2. 为蓝牙模块单独使用LDO供电,避免电机启动时拉低电压。 |
赛场最后24小时检查清单:
- 硬件:所有螺丝点胶固定;所有线缆用扎带或热熔胶固定,避免松脱;电池电量满格,电极片用酒精擦拭干净;轮胎清洁无灰尘;传感器镜头擦拭干净。
- 软件:准备多个版本的固件(激进速度版、稳定完赛版);确认拨码开关或按键可以切换版本;将最重要的PID参数、速度映射表放在易修改的位置(如通过按键加减调整)。
- 心理:制定比赛策略。前一两圈求稳,确保完赛拿到基础成绩。后面几圈再尝试逐步提升速度。永远把稳定性放在速度前面。
回顾整个备赛过程,最大的收获不是奖状,而是这套从问题定义、方案设计、模块实现、集成调试到现场排错的完整工程实践能力。选择CH32V103让我们被迫深入底层,虽然过程痛苦,但回过头看,那些对着数据手册调寄存器的夜晚,才是真正理解嵌入式系统如何工作的时刻。智能车竞赛就像一个微缩的机器人项目,它教会你的,远不止如何让一辆小车跑起来,而是如何让一个复杂的软硬件系统可靠、智能地工作。如果再来一次,我可能还是会选那个“麻烦”的RISC-V芯片,因为你知道,你吃透的每一点,都是别人拿不走的资本。最后一个小建议:尽早开始做“系统集成”,不要等每个模块都完美了再拼起来。车只有跑起来,你才知道真正的问题在哪里。