
“进度要完蛋了。”如果你正在备赛智能车竞赛这句话大概率不陌生。它可能是深夜调完一轮代码后的真实感叹也可能是队友看到测试视频时的集体恐慌。说这句话的队伍通常不是没有干活而是不知道离“能完赛”还差多少。从很多参赛队伍的备赛情况来看我的判断是绝大多数进度危机并不是技术能力不够而是项目节奏失控。具体来说是里程碑没有拆、调试手段无效、参数调整靠玄学。这篇文章就把这三件事逐一讲透并给出一套可以直接照着做的备赛路线、代码示例和排查清单。智能车竞赛和普通课程设计最大的区别在于它不是一个“做完提交”的项目而是一个“必须跑得快又跑得稳”的实时系统。赛场上任何一次抖动、误判、丢线、堵转都会直接转化成脱轨或冲出赛道。正因为结果反馈极其直接备赛过程中的进度问题会被迅速放大。如果团队还在用“感觉差不多”的方式推进进度自然会一天比一天紧张。不管你是哪一届、哪一级的队伍也不管你参加的是摄像头、电磁还是其他组别只要赛题本质仍然是“感知赛道—决策控制—执行驱动”这条链路下面这套方法论就适用。1. “进度要完蛋”背后的真实问题1.1 里程碑没有拆到能检查的程度很多队伍的计划就是“五周搞定三周调车”。这个计划根本无法执行因为“调车”本身不是一个验收项。真正可检查的验收项应该是“摄像头画面每天都能稳定输出中线”“舵机跟随中线没有明显滞后”“在目标速度下能连续跑完一圈”。如果计划里全是任务没有验收标准你永远不知道进度是提前还是落后。更常见的情况是队伍在硬件组装上卡了三天又在图像采集上卡了一周等到真正开始写控制算法时比赛已经临近。1.2 调试手段停留在“盲调”智能车是一个闭环系统传感器采集、算法处理、控制输出、机械执行任何一个环节出问题都会表现为“车跑偏”“车抖动”“冲出赛道”。如果只盯着最终现象改参数就会陷入“调了 KP 没用又调 KD还是没用”的循环。根本问题在于你没有把链路拆开。比如车跑偏可能是图像中线提取偏了可能是转向舵机中位不准也可能是左右电机转速不一致。没有中间量输出就只能靠猜。调试手段落后的队伍10 分钟可以加一个参数却要花一下午猜测问题出在哪一层。1.3 参数调优靠碰运气PID 参数、摄像头阈值、车速规划、弯道前瞻这些参数在智能车项目里非常多。如果每次调参都直接改代码、烧录、跑车你会发现“上一版明明能跑为什么改了一个参数就不行了”。这实际上是缺少参数记录和版本管理。正确做法是让参数外部化、可配置并记录每一组参数对应的现象。调参不是玄学而是一个“变量隔离”的实验过程。一次只改一个变量记录输入输出和现象才能形成有效经验。1.4 从“焦虑”到“可控”解决办法不是给自己打鸡血而是建立一张进度检查表。把整车拆成若干子系统每个子系统都有明确的模块边界、测试方法和完成标志。进度不是“感觉还早”而是“完成了哪几个模块剩下哪几个模块每个模块几天能做完”。后面第 4 节会给出一份可以直接照着用的里程碑清单。2. 智能车项目的技术架构与核心概念2.1 一套完整的实时嵌入式链路智能车是一个典型的实时嵌入式系统。按数据流向可以拆成感知、决策、执行三层。感知层负责获取外部信息摄像头采集赛道图像电磁传感器采集磁场强度编码器反馈实时车速陀螺仪和加速度计反馈车身姿态。决策层是 MCU 内部运行的逻辑对图像进行二值化和中线提取对电感值做差值计算结合当前车速计算出期望的转向角度和期望速度再通过 PID 控制器输出 PWM。执行层包括电机驱动和舵机它们把 PWM 信号转换成实际的转速和转角。任何一个环节的数据异常都会在“跑偏”“抖动”“冲出赛道”这些现象上体现出来。所以备赛的第一步是保证这条链路的每一层都可观测、可验证。2.2 核心概念一赛道中线提取大多数摄像头组算法的起点不是“识别赛道类型”而是“找到图像里赛道的中线位置”。因为智能车是沿赛道行驶的只要知道中线相对车身左偏还是右偏就能决定打多少角度。中线提取的难点在于光照变化、反光、赛道边缘不清晰以及十字、环岛等复杂元素的干扰。第一步通常是用二值化把图像变成“赛道是白、背景是黑”或者相反再从每一行扫描左右边界取中点。进阶做法包括大津法、自适应阈值、行中点拟合、基于透视关系的弯道前瞻。2.3 核心概念二PID 控制PID 是智能车最基础的控制算法。转向环节常用 PD 控制速度环节常用 PI 控制。P 决定响应的快慢I 消除稳态误差D 抑制超调。新手最容易犯的错误是把 PID 当成黑盒出现任何问题都往 PID 上猜。实际上在调 PID 之前必须确认底层执行是可控的电机响应正常、编码器读数准确、舵机中位正确。否则 PID 参数再准也没有意义。2.4 不同组别的技术差异智能车竞赛的分组名称每年可能调整但按常见技术路线做通用对比大致如下组别特点感知方式决策重点常见痛点摄像头类图像采集中线提取、弯道前瞻光照干扰、处理耗时电磁类电感采集电磁场差比和、路径预测传感器标定、远场信号弱惯性/速度类编码器、IMU闭环调速、姿态融合加减速震荡、温漂这里的核心结论是不论哪个组别底层控制逻辑都离不开编码器测速、PID 输出、PWM 驱动。这套基础能力越扎实后面调整赛题专项时越从容。3. 备赛环境与工程工具准备3.1 开发环境大多数队伍使用的 MCU 以 STM32 或竞赛板卡厂商提供的平台为主。Windows 下常用 Keil MDK 或 IAR 开发也可以使用 GCC 工具链加 VS Code 的流程。具体工具和版本以你手上的板卡为准但团队内部必须统一避免“你代码在 Keil 能编我这边全是红色报错”的尴尬。建议直接从芯片厂商或板卡厂商提供的例程开始改不要从零搭工程。原因很简单例程已经处理好了时钟、引脚复用和下载配置能省下一到两周的时间。3.2 下载与调试调试器常见的有 ST-Link、DAP-Link 等视板卡而定。下载失败时不要急着重装驱动先检查调试器和板卡连接、目标板供电、调试接口引脚是否被复用。如果程序里把 SWD 引脚复用成了普通 GPIO下一次下载就会失败。解决办法是按住复位键点击下载或者使用带一键下载电路的开发板。3.3 辅助工具清单串口助手查看打印日志确认中间量。VOFA 等虚拟示波器把变量实时画成曲线调 PID 效率会提高很多。逻辑分析仪观察 PWM 波形、编码器脉冲、串口数据。一个普通 USB 摄像头或手机拍下跑车视频方便回看转向时机和车身姿态。3.4 Git 版本管理Git 不只用来管理代码还可以管理参数、文档、上位机配置。建议每次跑车测试后记录对应的代码 commit 和参数标签例如“commit f3a2c9 speed_2m5 light_noon”。这样如果某次改动后性能下降可以马上回退。智能车调试中有大量“改着改着就坏了”的情况没有版本管理你会连“上一版为什么能跑”都说不清。4. 备赛里程碑拆解把进度拉回“可控”4.1 里程碑总览以赛前 6 周为例可以把备赛过程拆成六个里程碑。如果时间更紧可以压缩前三个阶段但不能跳过验证标准。周次里程碑完成标准负责角色第 1 周整车供电与底层驱动电机能转、舵机能打角、编码器能读数硬件/嵌入式第 2 周传感器数据稳定图像稳定二值化、电磁值平滑可读嵌入式/算法第 3 周开环跑通赛道固定油门能跑完一圈不依赖控制算法算法第 4 周基础闭环控制直线稳定直行、弯道能转弯低速连续跑圈算法第 5 周复杂元素专项十字、环岛、坡道等元素逐个通过全队第 6 周稳定提速与冗余测试按目标速度连续跑圈保留备份配置全队这里的“负责角色”不是固定岗位而是强调每个里程碑必须有一个明确负责人。如果所有人都在做同一件事大概率意味着任务没有拆开。4.2 每个里程碑怎么验收每个里程碑都要有明确的数据或现象作为验收标准。比如“编码器能读数”不能只是串口有输出还要确认读数方向和实际转向一致数值和实际转速在合理误差范围内。“图像稳定二值化”不能只看一帧要看连续 10 秒内没有大面积抖动。很多队伍的问题是验收标准太模糊“差不多了”“看起来可以”变成了口头语。把标准量化之后你才能知道今天是否有真正进展。4.3 紧急情况下的砍需求原则进度真的完蛋时要忍痛砍功能。比赛成绩首先取决于“稳定完赛”其次才是速度。如果环岛一直处理不好可以先保证普通赛道不冲出如果加减速频繁导致震荡可以先固定速度跑。砍需求不是放弃而是把有限的调试时间放到收益最高的模块上。毕竟一辆稳定跑完赛道的车永远比一辆高速冲出赛道的车更有成绩。5. 核心代码模块实现示例5.1 串口调试日志先跑通串口打印所有中间量才能被看见。下面是一个简单的日志模块。// 文件路径Src/debug_log.c #include debug_log.h #include stdio.h static UART_HandleTypeDef *debug_uart; void Debug_Init(UART_HandleTypeDef *huart) { debug_uart huart; } void Debug_PrintFloat(const char *tag, float value) { char buf[64]; int len snprintf(buf, sizeof(buf), [%s] %.2f\r\n, tag, value); HAL_UART_Transmit(debug_uart, (uint8_t *)buf, len, 100); }// 文件路径Inc/debug_log.h #ifndef DEBUG_LOG_H #define DEBUG_LOG_H #include main.h void Debug_Init(UART_HandleTypeDef *huart); void Debug_PrintFloat(const char *tag, float value); #endif使用方式在主函数初始化时调用Debug_Init(huart1)之后在定时中断或主循环中打印目标速度、实际速度、中线位置等中间量。注意snprintf在某些嵌入式编译器环境下可能较大如果工程内存紧张可以只打印整数或者使用更轻量的格式化函数。调试代码始终应该用宏开关包起来比赛提交前把日志等级调低避免影响实时性。5.2 编码器测速速度闭环必须依赖编码器。下面是一个定时器编码器模式的简化写法不同芯片 API 略有差异以你手上板卡的示例工程为准。// 文件路径Src/encoder.c #include encoder.h void Encoder_Init(TIM_HandleTypeDef *htim) { HAL_TIM_Encoder_Start(htim, TIM_CHANNEL_ALL); } int32_t Encoder_GetCount(TIM_HandleTypeDef *htim) { int32_t count (int32_t)__HAL_TIM_GET_COUNTER(htim); __HAL_TIM_SET_COUNTER(htim, 0); return count; }说明每次周期性读取后清零计数器得到的就是一个周期内的脉冲增量。速度换算公式为速度 脉冲增量 / 编码器单圈脉冲数 / 采样周期 * 轮周长。单位建议统一到 mm/s 或 m/s方便和图像中线等数据一起调试。真正容易踩坑的地方是方向。编码器正转和反转计数方向由 A、B 相接法决定。如果发现速度闭环后车越跑越快先不要怀疑 PID先检查编码器方向是不是反了。5.3 PID 控制器下面的位置式 PID 适合速度环、转向环的常见场景。// 文件路径Inc/pid.h #ifndef PID_H #define PID_H typedef struct { float kp; float ki; float kd; float target; float integral; float last_error; float out_max; } PidController; void PID_Init(PidController *pid, float kp, float ki, float kd, float out_max); float PID_Update(PidController *pid, float measured); #endif// 文件路径Src/pid.c #include pid.h void PID_Init(PidController *pid, float kp, float ki, float kd, float out_max) { pid-kp kp; pid-ki ki; pid-kd kd; pid-out_max out_max; pid-target 0.0f; pid-integral 0.0f; pid-last_error 0.0f; } float PID_Update(PidController *pid, float measured) { float error pid-target - measured; pid-integral error; if (pid-integral pid-out_max) pid-integral pid-out_max; if (pid-integral -pid-out_max) pid-integral -pid-out_max; float derivative error - pid-last_error; pid-last_error error; float output pid-kp * error pid-ki * pid-integral pid-kd * derivative; if (output pid-out_max) output pid-out_max; if (output -pid-out_max) output -pid-out_max; return output; }使用示例速度环每 10ms 调用一次目标速度来自上层速度规划。转向环每 10ms 或 20ms 调用一次目标值来自图像中线。新手最容易犯的错误是微分项在设定值突变时产生冲击。如果目标速度从 0 直接跳到 2m/s微分项会瞬间变大表现为电机猛冲一下。更稳妥的做法是让目标值斜波变化或者在误差变化率上做低通滤波。这个细节在提速阶段尤其重要。5.4 摄像头二值化与中线提取摄像头图像先做二值化再用逐行扫描提取中线。下面是一个单帧处理的最小示例。// 文件路径Src/image_process.c #include image_process.h #define IMG_W 188 #define IMG_H 120 uint8_t gray[IMG_H][IMG_W]; uint8_t bin[IMG_H][IMG_W]; void Image_Binarize(uint8_t threshold) { for (int row 0; row IMG_H; row) { for (int col 0; col IMG_W; col) { bin[row][col] (gray[row][col] threshold) ? 0xFF : 0x00; } } } float Image_GetCenterLine(int row) { int left -1; int right -1; for (int col 0; col IMG_W; col) { if (bin[row][col] 0xFF) { left col; break; } } for (int col IMG_W - 1; col 0; col--) { if (bin[row][col] 0xFF) { right col; break; } } if (left 0 || right 0) { return -1.0f; // 丢线 } return (left right) / 2.0f; }说明这里假设白色为赛道黑色为背景。如果你的赛道恰好相反把比较符号换过来即可。固定阈值简单但容易受光照影响进阶做法是使用大津法或自适应阈值。中线提取的后续还可以根据连续几行的中点做线性拟合得到一个更平滑的期望转角。每帧图像处理的时间要控制在 10ms 以内否则控制周期会被拖慢。如果发现耗时过高优先优化循环内部的浮点运算比如把乘法改为查表把 float 改为 int。6. 运行结果与效果验证6.1 串口输出验证先把车架起来让后轮悬空通过串口观察。编码器数值是否随车轮转动增加。速度环目标值和实际值是否接近。PID 输出是否在合理区间震荡。如果编码器数值不变化检查编码器电源和 AB 相接线。如果变化但方向不对交换 AB 相。如果实际速度一直追不上目标先检查电机输出电压是否饱和再检查 PID 参数。6.2 虚拟示波器观察使用 VOFA 或类似工具把目标速度和实际速度同时画成曲线。正常情况是实际速度曲线平滑贴近目标值没有持续的等幅震荡。如果震荡频率很高多半是微分项太大或控制周期不稳定。如果响应很慢多半是比例项太小。转向环可以打印“期望中线偏差”和“实际舵机 PWM 输出”。先确认偏差计算正确再确认 PWM 方向正确。所谓“方向正确”是指偏差为正时舵机打向对应一侧而不是一边修正一边加剧错误。6.3 整车测试的验收标准整车测试不能只看“能跑”这个结果要看几个量化指标。连续 5 圈内出赛道的次数是否降到 0。高速入弯时是否出现明显点头或甩尾。图像丢线后是否能快速恢复。电量从满电到低压过程中速度是否保持一致。如果电量变化导致速度漂移说明速度闭环没有真正生效或者电机驱动已经进入低压保护区。这也是很多队伍在比赛下午突然“变慢”的常见原因。6.4 失败时的排查顺序整车跑飞时按照“先传感器再算法再执行”的顺序排查。第一步看图像或电磁值是否正常。第二步看中线或偏差计算是否正确。第三步看转向 PWM 是否输出正确。第四步看舵机机械结构是否有卡滞。绝大多数“跑飞”都发生在传感器数据异常或者舵机中位偏移而不是 PID 系数不够好。如果在这些基础项没确认之前就盲目调参只会让问题更隐蔽。7. 进度失控时的紧急排查清单问题现象可能原因排查方式解决方案电机不转供电不足、PWM 通道配置错误、使能脚未拉高万用表测驱动板电源示波器看 PWM单独给驱动板供电重新检查 GPIO 配置电机方向反编码器方向或电机线序错误悬空后轮打印速度值交换电机接线或取反馈负号舵机抖动电源纹波大、控制周期不稳定、输出限幅不够示波器看供电波形检查控制周期舵机单独供电增加稳压电容图像全黑或全白摄像头初始化失败、曝光过大、阈值错误把原始灰度图打印到上位机检查镜头是否打开调整曝光和阈值中线剧烈跳变二值化阈值不对、图像有反光连续打印一行像素数据使用局部阈值或大津法直道跑偏左右轮速不一致、舵机中位偏移悬空测给定相同 PWM 时左右轮是否同速校准舵机中位增加方向闭环下载失败SWD 引脚被复用、连接松动、目标板没供电按住复位键点击下载检查接线换用可用下载方式避免复用调试脚电池掉电快电机堵转、驱动效率低测堵转电流检查机械阻力设置电流保护救援原则三句话先跑通再优化砍掉低收益功能保留上一版能跑的配置。赛前一周不要大改机械结构否则你会在“改动”和“适应”之间消耗掉最后的时间。8. 工程化最佳实践与团队协作8.1 用 Git 管理代码和参数即使团队只有两个人也要用 Git。每次跑车前把代码状态提交一次跑完后把现象记录到 commit message 里。参数文件单独存放不要散落在各个源文件里。这样在赛场上临时调参也能保证随时回到“上一版能跑”。8.2 建立参数记录表用表格记录每一组关键参数日期、目标速度、PID 系数、摄像头阈值、赛道光照条件、现象、结论。调参时一次只改一个变量。如果没有记录你会反复调整同一组参数浪费大量时间。很多队伍最后几天调不出来不是因为工程能力差而是因为同样的坑已经踩了三次。8.3 软硬件接口约定把软硬件之间的接口固定下来。比如电机 PWM 是低电平有效还是高电平有效舵机中位对应的 PWM 占空比是多少编码器方向约定是正向为 1 还是反向为 -1。这些约定写在文档里避免硬件换人后整个算法全部要重新标定。接口约定越早确认后期联调越顺利。8.4 电气与机械安全电池供电的板子电压不低接反很容易烧主控。建议在电源入口加防反接二极管和保险丝主控板与电机驱动板分开供电。机械上轮胎悬空测试时一定要把车架固定好防止高速转动时飞出。摄像头镜头要固定牢固比赛前检查排线是否松动。细节问题在赛场上会被放大成致命故障。8.5 赛前一周的收敛策略赛前一周不是做大幅创新的时间而是稳定收敛的时间。每天只做一次整车测试每次只改一个参数跑完记录结果。把“能稳定完赛”的配置单独备份命名为final_backup再在这个基础上尝试小幅提速。如果提速失败切回备份配置不要犹豫。9. 总结与下一步建议智能车备赛进度紧张不是少数队伍的问题而是竞争性项目里必然出现的压力。但进度压力能被管理前提是把它拆成可以验收的里程碑、可观测的调试数据、可回退的版本配置。先跑通链路再稳定复现最后逐步提速是一条比“熬夜硬调”可靠得多的路径。如果你现在正处在“进度要完蛋”的状态建议今天就把整车拆成感知、决策、执行三层打印出每一层的中间数据找出第一个断点。先恢复串口日志确认电机和传感器都正常再谈提速和复杂元素。只要链路是通的进度就还掌握在你们手里。等跑稳之后可以继续深入图像自适应阈值、弯道前瞻、路径拟合、速度规划、惯性导航融合这些方向把整车性能一步步推向极限