公司动态
智能车竞赛技术复盘:嵌入式系统与PID控制实战
26年的夏天我的最后一届智能车备赛结束了。四年里我拆过电机、调过摄像头阈值、焊过驱动板、也半夜在走廊里追着车跑最后留下的不是奖状而是一整套关于嵌入式系统、控制算法和工程协作的方法。下面我把这段经历整理成一份技术复盘智能车竞赛需要哪些知识体系最小系统怎么跑通PID 和赛道元素怎么处理调参遇到问题时应该从哪一层查起。文章会给出关键代码、参数表格和排查清单既适合正在备赛的队友也适合想用一个小项目把嵌入式技能串起来的开发者。1. 智能车竞赛本质上是一个完整的嵌入式系统工程很多第一次接触智能车的人会以为竞赛的难点是“让车跑得够快”。但实际上一辆能稳定跑完赛道的智能车是把感知、决策、执行、能源和调试五个子系统放在一辆车模上任何一个环节出问题速度都会归零。理解这一点比先找一套代码更重要。1.1 一辆能跑赢的智能车由哪几个子系统组成以最常见的摄像头组为例一辆车至少包含以下部分子系统作用典型器件最容易出的问题感知获取赛道信息CMOS 摄像头、电感、激光、陀螺仪曝光、反光、数据抖动决策由感知结果计算方向与速度MCU、嵌入式代码状态机混乱、控制周期不稳定执行把控制量变成物理动作舵机、电机、驱动桥PWM 频率不匹配、驱动能力不足供能提供稳定电压和电流动力锂电池、稳压模块电压跌落导致复位或数据漂移调试观察内部状态串口、无线模块、OLED、逻辑分析仪看不到数据只能盲调这五个子系统是耦合的。例如摄像头曝光参数没调好会导致边缘提取不稳定进而导致转向指令来回跳电池电压快耗尽时如果稳压模块压差不足舵机和摄像头可能同时出现奇怪现象。所以排查一个问题时不能只盯着算法层。1.2 为什么说“车跑得稳”比“车跑得快”更重要备赛初期我犯过的最大错误是拼命调目标速度结果车在直道上飞快遇到十字或环岛立刻冲出赛道。后来我意识到智能车的速度上限由“稳定可重复跑完”决定而不是由理论最大速度决定。竞赛计时比的是单圈成绩但单圈成绩只有在能完赛的前提下才有意义。稳定意味着同样的起跑位置多次跑出的路径几乎一致。不同的光照、地面摩擦和电池电压下赛道元素识别仍然可靠。每次程序更新后回退和比较都有数据支撑。这也是为什么竞赛组委每年会强调技术报告和调试记录。真正把车调稳定依赖的是工程方法而不是碰运气。注意学习环境里能跑起来不算完赛比赛环境里能连续跑三圈且路径一致才算初步稳定。2. 备赛前先把环境和硬件选型定下来后面才能少返工智能车竞赛每年都会发布当年版本的规则组别、传感器类型、主控芯片范围、允许使用电机数量都可能变化。所以这篇文章里关于具体器件的表述只能作为“常见方案”参考动手之前一定要先找到当年的正式规则文档。2.1 从竞赛组别出发选择传感器方案不同组别对传感器的依赖差异极大选错方向会浪费大量时间。常见组别大致可以这样理解组别类型核心感知方式控制的难点适合的知识背景摄像头组摄像头图像识别赛道图像处理、边缘提取、弯道速度规划图像处理、C 语言、PID电磁组电感感应磁场强度信号放大、归一化、中线拟合模拟电路、信号处理平衡组陀螺仪、加速度计、编码器直立平衡与速度控制的耦合自动控制原理、滤波算法视觉组摄像头、二维码、深度学习加速目标检测、多元素识别深度学习、Linux、嵌入式 AI有些年份组别名称和规则会调整。以摄像头为例你需要先确认摄像头的安装高度、视角、分辨率上限和图像延迟要求。以电磁为例你需要知道允许使用几个电感、赛道导线频率和信号类型。2.2 MCU、电机驱动和开发环境的常规选型思路主控芯片是整车的计算中心。竞赛历史上常见的有 K60、STM32、TC264、TC364、RT1064 等它们各有擅长场景。K60 资料多但速度一般STM32 生态最成熟TC264 和 TC364 在竞赛场景中性能强RT1064 适合需要快速图像处理的高端组别。主控典型定位开发环境适合人群STM32F4 系列入门、综合Keil MDK / STM32CubeMX绝大多数参赛者K60 系列传统竞赛方案IAR / Keil需要大量历史资料TC264 / TC364高性能处理AURIX Development Studio / Tasking需要更高性能的组别RT1064图像和算力要求高MCUXpresso / Keil摄像头组或视觉组进阶这里要强调不要高估“换更强主控”的作用。车跑不顺先怀疑算法和机械再怀疑主控算力不足。开发环境之外还需要准备串口调试工具、下载器DAP-Link、ST-Link 等、稳压电源、示波器或逻辑分析仪。备赛前期花半天时间把“烧录程序、查看串口数据、断点调试”三件事跑顺后面会节省成倍的时间。注意学习环境可以用开发板和现成车模快速起步比赛环境则需要按当年规则核查车模尺寸、电池电压和主控型号任何一项超标都可能影响成绩。3. 第一步不是调算法而是让最小系统先能动很多新手拿到车模后直接找网上的“全功能代码”结果连编译都过不了。正确的顺序是先搭最小系统点亮 LED、驱动电机、读取传感器、输出调试信息。四步做完后续所有算法才有运行基础。3.1 从点亮板载 LED 开始验证 MCU 最小系统最小系统验证的目标是确认三件事电源正常、晶振或内部时钟正常、下载器能烧录程序。以 STM32 为例可以写一个最简单的 GPIO 翻转程序#include main.h int main(void) { HAL_Init(); __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef led {0}; led.Pin GPIO_PIN_5; led.Mode GPIO_MODE_OUTPUT_PP; led.Pull GPIO_NOPULL; led.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, led); while (1) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); HAL_Delay(200); } }这段代码的目的是确认工程能编译、下载器能连接、芯片能运行。如果 LED 不闪烁优先检查引脚号、时钟使能和下载器驱动不要急着写控制算法。3.2 用 PWM 控制电机和舵机建立执行链路舵机和电机驱动的本质都是 PWM 输出但两者频率不同。舵机常见周期为 50Hz脉宽在 0.5ms 到 2.5ms 之间对应不同角度电机驱动常见频率在 10kHz 到 20kHz 左右具体看驱动芯片手册。下面是一个使用定时器输出两路 PWM 的示意// 假设定时器 TIM2 用于舵机TIM3 用于电机 void pwm_init(void) { // 舵机50Hz周期 20ms // 占空比 7.5% 对应中位约 1.5ms 脉宽 // 电机16kHz占空比根据速度环输出调整 }这里的“中位”很关键。舵机不通电时可以先手动掰到中间位置安装舵机臂时要保证车轮摆直时脉宽对应 1.5ms 左右。很多车跑不直不是 PID 问题而是舵机中位没标定。3.3 用串口把传感器数据传到上位机建立调试链路调车最怕盲调。串口是成本最低、见效最快的调试手段至少要把以下类型的数据实时打出来赛道中线、误差值、PID 输出、目标速度、实际速度、图像处理耗时。#include stdio.h void send_debug_info(int steer_pwm, int speed_pwm, float error) { char buf[64]; sprintf(buf, steer:%d,speed:%d,error:%.2f\r\n, steer_pwm, speed_pwm, (double)error); // 将 buf 通过 USART 发送到上位机 }这个阶段就可以开发一个小型上位机用 Python 或 Qt 读取串口数据并画曲线。没有无线调试模块时也可以先把车固定在支架上通过串口线调试。4. 核心算法从 PID 温控思想到赛道速度闭环PID 是智能车最核心的控制算法。它很简单但真正的难点在参数整定和边界情况处理。很多车到手后跑不过一个弯往往是因为只写了 PID 公式却没有理解 P、I、D 在物理上到底代表什么。4.1 PID 是什么为什么智能车离不开它PID 的目标是让某个误差快速变为零并且过程平稳。误差可以理解为“当前值与目标值的差”例如赛道中线偏差、当前速度和目标速度的差。参数通俗含义调大后的效果调大后的问题P当前误差越快纠正响应更快震荡、抖动I长期累积的小误差消除稳态误差积分饱和、过冲D抑制误差变化趋势更稳定、提前刹车噪声放大、高频振动以转向为例如果车偏离赛道中心 10cmP 项会给出一个与偏差成比例的舵机修正量D 项则会根据偏差变化率提前减小修正避免过冲I 项在长期存在固定偏差时提供持续补偿。4.2 位置式 PID 和增量式 PID 的选择速度环和转向环都可以用 PID但实现方式有区别。位置式 PID 直接计算完整输出适合舵机这类输出在固定范围内变化的场景。增量式 PID 输出的是本次相对于上次的增量适合电机这类需要平滑累加输出的场景。typedef struct { float kp; float ki; float kd; float integral; float last_error; } PID_Typedef; float pid_update(PID_Typedef *pid, float error, float dt) { float proportional pid-kp * error; pid-integral error * dt; float integral pid-ki * pid-integral; float derivative pid-kd * (error - pid-last_error) / dt; pid-last_error error; return proportional integral derivative; }实际使用中需要给积分项加限幅避免长时间无法消除误差时积分值过大。增量式实现则通常保存上一次输出并在每次调用时叠加增量float pid_incremental(PID_Typedef *pid, float error, float dt) { float p pid-kp * (error - pid-last_error); float i pid-ki * error * dt; float d pid-kd * (error - 2.0f * pid-last_error pid-last_last_error) / dt; pid-last_last_error pid-last_error; pid-last_error error; return p i d; }4.3 参数整定先 P 后 I 再 D 的调整顺序正确的整定顺序是先把 I 和 D 设为 0只保留 P从小到大增加 P直到系统出现轻微震荡然后回调到震荡前的 70% 左右接着增加 D让响应更稳最后再加入少量 I 消除稳态误差。每次修改参数都要记录车在同一个赛道上的表现。最好的方法是用统一测试赛道固定起跑位置重复跑三次记录过弯最大偏移、直道是否摆动、能否完赛。运行现象偏向哪个参数调整方向过弯迟钝转弯半径大P 太小增大 P直道来回摆动D 不足或 P 过大先增 D再考虑减 P始终存在固定偏差I 不足增大 I过弯冲出赛道减速不够或 D 过大检查速度规划再减小 D常见坑是“参数调到震荡边界”时没有留足余量比赛现场地摩擦力不同车直接就失控。留 20% 到 30% 的稳定余量是更稳妥的做法。5. 赛道元素识别是这个项目里最考验工程能力的部分普通直道和弯道用中线偏差就能解决但十字、环岛、坡道、路障这些元素需要的是“状态识别 差异化控制”。这也是智能车最像真实工程的部分要在不确定的环境里做可靠分类。5.1 摄像头图像处理从阈值分割到边缘提取摄像头图像处理的第一步是获得可靠的赛道边缘。以灰度图为例常见思路是先做二值化再按行扫描边界。// 示例思路固定阈值二值化再逐行找左右边界 void process_image(unsigned char *gray, int width, int height) { for (int row 0; row height; row) { int left_edge -1; int right_edge -1; for (int col 0; col width; col) { unsigned char value gray[row * width col]; if (value 128 left_edge 0) { left_edge col; } if (value 128 left_edge 0) { right_edge col; } } if (left_edge 0 right_edge left_edge) { int mid (left_edge right_edge) / 2; // 将 mid 保存为当前行赛道中线 } } }实际比赛中固定阈值往往不够。太亮的灯光、阴影和反光都会造成边缘断裂。这时候需要动态阈值、大津法或局部自适应阈值同时配合图像裁剪只处理近处有效区域减少计算量。5.2 用赛道类型状态机处理十字、环岛和坡道赛道元素不能只靠一帧图像判断最好使用状态机。普通状态下车辆按“直道/弯道”控制当检测到十字特征时进入“十字状态”保持当前方向继续前进当检测到环岛入口特征时进入“环岛状态”执行固定的转弯和路径规划。赛道元素核心特征常见处理策略十字两侧边缘同时消失前方出现横向赛道保持一段时间直行不强行纠偏环岛单侧边缘断开出现圆弧路径识别入口后切换环岛状态按半径控制坡道图像高度变化边缘形态拉伸提前加速或保持速度避免前轮抬起路障近处出现额外色块或高度信息强制降低目标速度完成后恢复状态机最忌“一拍脑袋”写逻辑。每个状态应该有进入条件、退出条件、超时保护。例如进入环岛状态后如果 2 秒内没有检测到出口必须自动退出并降速否则车会在环岛里乱转。5.3 速度规划弯道减速要提前直道加速要稳速度规划的目标是让车在直道尽量快在弯道提前减速并稳定过弯。可以用提前量实现根据前方 30cm 或 50cm 处的赛道曲率决定当前目标速度而不是等车到了弯道才反应。提前量的单位需要根据车速换算成图像行号这样做出的速度曲线才平滑。6. 怎么判断一台车调好了验证方法和数据复盘很多团队把所有时间用在写新功能上却忽略了建立验证标准。没有验证标准就无法判断修改是改进还是回退。6.1 建立自己的测试基线建议在实验室固定一条标准赛道记录以下基线数据直道最大速度。单个弯道通过速度。连续 S 弯最大可通过速度。十字和环岛的完成率。十次重复跑中冲出赛道的次数。每一项都是独立指标。修改代码后不需要跑完整圈先跑对应的狭窄场景比如只跑弯道只看弯道完成率。6.2 利用无线调试模块记录关键变量有条件的队伍建议使用无线串口模块记录跑圈过程中的完整数据。需要记录的关键变量包括赛道中线误差、目标速度、实际速度、转向 PWM 输出、当前赛道状态、图像处理耗时和每帧时间戳。跑完一圈后把数据导入 Python 或 Excel绘制误差随时间变化的曲线。如果曲线在过弯前有较大的反向尖峰说明转向逻辑有提前量错误或状态切换问题。6.3 验收标准不仅看圈速还要看一致性一圈跑得快不够至少要验证同一程序在同一赛道上连续跑五次最好能跑出相近的圈速和相似的路径。快速判断方法是固定手机录像机位把五次跑圈视频叠帧对比。路径差异大的位置就是需要继续调的位置。验收项学习环境要求比赛环境要求直道不摆动能接受轻微摆动无明显摆动弯道最大速度保证完赛即可逼近稳定极限元素识别率基本能识别多次测试零误判断电重启一致性能重启并重新烧录支持快速复位和重新起跑7. 备赛中最容易踩的 5 个坑和排查清单下面这些坑几乎每支队伍都会至少遇到一次。把它们列成排查清单能减少大量无效调试时间。7.1 常见问题现象与处理方案问题现象常见原因检查方式处理建议图像边缘频繁跳动曝光时间不合适、赛道反光查看原图和二值化结果固定曝光使用动态阈值或滤波高速过弯冲出赛道减速太晚或 D 项过大查看速度曲线和转向输出提前量加大检查 PID 输出是否饱和跑一段时间后速度下降电池电压下降、积分饱和串口查看实际速度和 PWM使用稳压模块给积分限幅舵机回正时抖动舵机中位偏移、齿轮虚位断开连杆手动测试重新标定中位更换磨损舵机臂程序卡死或复位电源跌落、看门狗未喂检查复位原因寄存器优化电源走线添加电压监控7.2 推荐的排查顺序当一个现象出现时不要直接改 PID。先按下面的顺序排查传感器数据是否正确。串口打印原始值确认不是采集链路问题。输入数据的时间戳是否连续。数据丢帧会导致控制周期不稳定。输出 PWM 是否在合理范围。检查占空比是否饱和舵机是否达到机械限位。电源电压在启动和过弯时是否波动。用示波器抓关键节点电压。最后再检查 PID、状态机和速度规划逻辑。注意不要在高频控制中断里运行耗时的浮点计算或串口打印否则会撑大控制周期导致整车响应变慢。8. 最后一届留下的不是代码而是一套可复用的工程方法到了最后一届车能不能拿名次已经不是唯一重要的事。回头看真正值钱的是沉淀下来的工程方法。这套方法能带到工作里也能交接给下一届队友。8.1 从一个人调车到一支队伍协作一个人调车很容易陷入“改两三行代码就上车跑”的循环。建议从第一天起就做三件事使用 Git 管理代码每个功能分支先合入主干再上车。记录每次参数修改至少包含修改日期、参数值、测试结果和备注。约定接口把图像处理、控制算法、串口输出拆成独立模块避免改一处崩全车。8.2 给下一届队友写一份交接文档交接文档不是代码注释而是面向新队员的“上车说明”建议包含# 智能车交接文档模板 1. 硬件清单和接线图 - 电源、舵机、电机、传感器、下载器接口对应关系 2. 开发环境搭建步骤 - 工具链版本、烧录方式、串口波特率 3. 代码目录结构 - 每个模块的作用从哪里改参数从哪里加功能 4. 当前调试状态 - 已知问题、未完成项、最近一次测试数据 5. 参数速查表 - 整定好的 PID 参数、摄像头曝光、图像裁剪区域 6. 常见错误和处理办法这份文档的价值会在赛季中后段显现新人快速上手老队员专注调车而不是重复回答“这个文件在哪里”。8.3 从智能车竞赛走向更广阔的技术方向智能车是一扇门。它背后的知识体系可以继续延伸实时操作系统RT-Thread、FreeRTOS、Linux 驱动、摄像头标定与 SLAM、机器人运动学、自动控制原理、嵌入式 AI 加速器。竞赛中养成的“从现象倒推原因”的排查习惯比任何一项具体技能都更持久。如果让我给下一届一个建议那就是尽早开始记录。车会退役代码会改版但完整的调试日志、参数记录和问题复盘能让你在最后一届结束后依然清楚地知道自己做过什么、为什么这样做。26 年的夏天已经结束了但把它写下来这件事比完赛本身更值得完成。