智能车竞赛代码包拆解:从单片机驱动到PID闭环控制

发布时间:2026/9/1 16:08:53
智能车竞赛代码包拆解:从单片机驱动到PID闭环控制 简介本资源是一套面向嵌入式竞赛场景的智能循迹小车完整工程代码适用于单片机开发初学者及智能车赛事备赛者解决红外传感器数据采集、十字路口识别与电机闭环控制等核心问题。压缩包共163个文件含42个头文件.h定义硬件接口与模块功能41个C源文件.c实现STM32底层驱动如tim、rcc、adc、i2c等、PID调速算法及巡线逻辑另有调试配置.uvprojx/.uvoptx、编译输出.axf/.hex/.map及构建脚本.bat整体体积3.77MB结构符合Keil MDK标准嵌入式开发规范。已有249人学习下载提供可直接烧录运行的工程框架涵盖4路红外巡线定位、3路红外十字判别、编码器测速反馈及电机PWM驱动等关键模块代码注释清晰、模块划分合理便于理解传感器信号处理流程与实时控制逻辑是掌握嵌入式C/C实战开发的典型参考案例。 拿过来一份写着比赛代码小车.zip的压缩包懂行的人都知道这绝对是某个智能车竞赛或者电子设计竞赛的产物。压缩包里面装的根本不是普通的课堂作业而是一整套从底层寄存器配置到上层控制策略的完整嵌入式工程项目。这类小车的核心赛道基本都绕不开单片机选型、传感器采集、电机驱动、PID闭环控制这几座大山语言也几乎统一在C/C这个生态里。这篇文章就结合我折腾过多个比赛代码包的经验从工程结构、底层驱动、控制策略、传感器融合、赛场排错五个角度把这类项目彻底拆开讲透。不管你是刚接触嵌入式准备参赛的初学者还是已经调过车但总在某个环节翻车的老手这篇内容都值得你花几分钟看完。1. 读懂比赛小车代码包从工程结构到模块职责拆解1.1 代码包的文件构成与编译环境绝大多数的比赛小车代码包解压之后都是标准的分目录结构。我随手拿一个典型的STM32小车工程举例子你打开后会看到这些目录USER/入口文件main.c、中断处理、系统时钟配置这是启动一切的起点。HARDWARE/底层硬件驱动里面一般有motor.c、encoder.c、infrared.c、ultrasonic.c每个文件对应一组外设。SYSTEM/延时函数、串口打印、定时器基础封装这些是各模块公用的基础设施。CORE/启动文件与内核相关配置通常不需要修改。APP/策略层代码比如control.cPID控制器、track.c循迹逻辑、avoid.c避障逻辑这一层是整个代码包最有含金量的地方。编译环境方面老牌项目基本都是Keil MDK近几年也有不少用STM32CubeMX VSCode GCC工具链的。点开工程文件时版本不一致会导致打开报错这种问题经常出现在队友之间互相传代码的场景中——有人用 Keil 5.23有人用 5.38低版本打不开高版本的工程高版本打开低版本又会提示迁移。我在实际中处理这类问题优先用新版本 Keil 打开后让它自动迁移大多数情况能直接跑通。1.2 核心模块边界与调用关系代码包的最大价值不在于某个炫酷的算法而在于模块边界的划分是否干净。一个能稳定跑完全程的小车代码模块之间一定遵循底层驱动不掺策略、策略层不直接操作寄存器的原则。我拆过很多份代码包好代码的调用关系大致是这样main()里只做三件事初始化各模块、进入while(1)主循环、在主循环里按10ms或20ms的周期调度策略函数。motor.c对外只暴露三个接口Motor_Init()、Motor_SetSpeed(uint8_t id, int16_t speed)、Motor_Stop()。上层根本不用关心 PWM 寄存器怎么填也不用管 GPIO 拉高拉低。encoder.c对外暴露Encoder_GetCount()返回编码器累计脉冲数控制层拿这个值算转速。control.c里才是 PID 计算逻辑它只负责根据目标速度与实际速度的误差算出一个新的 PWM 输出值然后调用Motor_SetSpeed()把结果交出去。这种分层写法的好处我在比赛现场体会特别深。真到了赛场上你不可能所有模块都顺利工作。如果传感器挂了一个你得能在十分钟内把传感器部分单独隔离出来测试而不是在几万行代码里翻来翻去。有一次队友把循迹传感器线序接反了我们没有改硬件直接在infrared.c的数据解析函数里把通道映射调换一下三分钟解决问题。如果没有模块化这种快速修复根本做不到。1.3 代码包里的隐藏关键文件除了目录结构代码包里还有几个容易被忽视但至关重要的文件。一个是.uvprojxKeil 工程文件它记录了你选了哪些芯片型号、定义了哪些宏、包含了哪些头文件路径。很多人把代码拷到别的电脑上编译报几十个fatal error: xxx.h: No such file or directory十有八九是工程文件里的头文件路径没带过来。另外一个关键文件是readme.txt或者注释文档。比赛前三周写的代码到比赛前两天你可能已经看不懂了。我见过很多代码包里面的注释是这么写的// 这里改了参数但忘了为什么改 PID.kp 48.5f;这种注释等于没写。我自己的习惯是在调参的时候用有明确语义的注释比如// 直道速度高2.2m/s弯道曲率大时降到1.4m/s否则会甩尾 PID.kp 48.5f; // 直道增益过直角弯时偏大配合输出限幅使用代码包拿到手之后先花十分钟把main.c里的while(1)循环体读一遍看看主循环调了哪些函数、每个函数的调用周期是多少。这一个动作能帮你快速判断整个工程的调度节奏比逐行读代码高效十倍。2. 底层驱动实现定时器、PWM 与编码器测速的配合逻辑2.1 GPIO 配置与中断优先级安排小车的硬件执行链路是控制芯片输出 PWM → 电机驱动芯片如 TB6612 → 直流电机转动。而感知链路是编码器脉冲 → 定时器计数 → 软件换算成转速。这两条链路的一切源头都是 GPIO 初始化和定时器配置。GPIO 配置里最核心的坑是推挽输出与开漏输出的选择。驱动电机方向引脚AIN1、AIN2、BIN1、BIN2必须用推挽输出因为它需要强烈的电平驱动能力而 I2C 通信引脚如果接了 OLED 或者传感器则需要开漏输出并外部上拉。如果混用轻则信号不稳定重则烧引脚。中断优先级方面这里有一个我踩过很深坑的经验。编码器测速的定时器更新中断优先级一定要高于串口中断但低于系统滴答定时器中断。因为编码器计数如果丢失速度闭环就废了小车会表现为一卡一卡的而串口中断丢几个字节最多是调试数据少打几行不影响车辆控制。第一次调车时我把串口优先级设得最高结果电机转速一高串口疯狂打印数据中断把编码器采集全堵了速度环振荡小车在直道上像醉酒一样左右扭。排查了一整天才定位到是中断优先级的问题。2.2 PWM 输出与电机驱动芯片的配合PWM 频率选择是个看似简单但其实影响很大的参数。直流减速电机的 PWM 频率建议设在 10kHz 到 20kHz 之间这样既避开了人耳可听范围不会发出尖锐的啸叫又不会因为频率过高导致驱动芯片开关损耗过大。设置 PWM 输出时最容易出问题的是死区时间和占空比方向。使用 TB6612 这类驱动芯片时逻辑关系如下AIN1AIN2PWM电机状态高低高正转低高高反转高高/刹车低低/惰行自由滑行注意很多代码包里为了方便直接用一个Motor_SetSpeed(int16_t speed)函数内部用正负号区分方向。如果你写的是speed -1000那么对应的 PWM 占空比应该经过绝对值处理方向引脚则根据符号位切换。这个逻辑如果写反表现出来就是小车一给负速度就反方向疯跑这类错误在代码包中极其常见。我在这里补充一个实操细节PWM 定时器重载值ARR和比较值CCR的选择。F103 系列常用的定时器是 16 位ARR 最大 65535。比如你时钟频率 72MHz预分频 71那么定时器计数频率是 1MHzARR 设为 999PWM 周期就是 1ms对应 1kHz。如果想让 PWM 频率达到 10kHz需要把 ARR 设为 99这样计数频率 1MHz 除以 100 正好是 10kHz。很多人调车时发现电机声音特别尖或者特别闷大概率就是 ARR 和 PSC 没匹配好算出来的频率落在了音频敏感区。2.3 编码器测速定时器正交解码模式电机转速反馈是闭环控制的基础。小车常用的增量式编码器输出两路方波A 相和 B 相相位差 90 度。STM32 定时器的编码器模式就是为了这种信号设计的它可以根据 A、B 相的电平相位关系判断旋转方向同时自动完成 4 倍频计数。这是硬件上的优势如果在程序里用外部中断来数脉冲那就完全丧失了这种效率。配置编码器模式时有一个特别关键的寄存器位TIM_EncoderInterfaceConfig()里的TIM_EncoderMode_TI1和TIM_EncoderMode_TI2的区别。TI1模式只在 TI1 信号上计边沿也就是 A 相上升沿或下降沿TI2模式只在 B 相上计数。如果想要 2 倍频效果就用这两个模式之一想要 4 倍频就必须用TIM_EncoderMode_TI12让它同时捕获两相边沿。很多代码包里你看不到这个配置细节因为它直接决定了测速分辨率。举个例子电机输出轴转速经过减速器后假设输出轴转一圈编码器发出 13 个脉冲常见 13PPR 减速电机使用 4 倍频后每圈就是 52 个计数。如果你的代码里用的是 1 倍频且没注意测出来的速度会离谱地偏低PID 计算出来的输出自然就不对。我测速时有一个习惯把电机悬空手动转动轮子一整圈打印编码器的累计值。如果打印结果约等于编码器线数 × 减速比 × 4说明配置正确。这个步骤虽然简单但可以确认减速比、编码器线数、倍频系数三个变量的乘积是对的后面算速度时才有底气。2.4 定时器资源的合理分配F103C8T6 这类芯片的定时器资源有限通常有 1 个高级定时器 TIM13 个通用定时器 TIM2/3/4。在一个典型的四轮小车项目里两个电机需要两路 PWM 输出TIM2 的两个通道、两个编码器需要两个定时器TIM3 和 TIM4 各配一路编码器。这样一来定时器资源几乎被占满。如果你的方案里还要做超声波测距的回波捕获那 TIM1 的输入捕获也得安排上。很多初学者刚开始规划资源时没有这个全局观代码写了一半发现定时器不够用了只好拆东墙补西墙。我建议在动手写代码前先画一张资源分配表不用正规的画图工具Excel 或者纸笔都行外设资源用途定时器/引脚TIM2_CH1左电机 PWMPA0TIM2_CH2右电机 PWMPA1TIM3左电机编码器PA6/PA7TIM4右电机编码器PB6/PB7USART1调试串口PA9/PA10GPIOB红外循迹模块PB0~PB4这张表在我做了无数个项目之后已经成了肌肉记忆但我还是建议每个人在拿到一套硬件时候自己重新梳理一遍。因为不同厂家的小车开发板引脚映射千差万别直接抄别人代码配置必炸。3. 控制策略落地PID 闭环调试与赛道场景适配3.1 为什么小车必须用闭环控制开环能跑但跑不稳是比赛小车项目里最常见的状态。开环控制就是你给电机一个固定的 PWM 占空比不关心轮子实际转速。这在空载、电压稳定时看着还行但电池电压从 8.4V 掉到 7.2V 时同样的 PWM 占空比下电机转速已经明显下降遇到地面摩擦力不均匀或者电量下降导致驱动芯片输出能力变化小车就会显著跑偏。闭环控制的核心思想就一句话测出实际速度和目标速度比较用误差驱动控制量修正。编码器提供测量值PID 算法提供修正量这就是测-算-控闭环。我在比赛前的测试中做过一个对比实验开环给定 50% PWM小车在光滑瓷砖上 2 秒走 1.2 米换到粗糙跑道相同 PWM 下只走 0.9 米。上了速度闭环之后两种地面上都能让轮子转速稳定在目标值附近误差在 2% 以内。这个差距就是比赛过弯成绩波动大的根源。3.2 位置式 PID 与增量式 PID 的选择PID 控制器有两大类实现方式位置式和增量式。大多数比赛小车代码包用的是增量式 PID因为它的输出是控制量的增量对执行机构PWM 占空比更友好且没有积分累积过大的问题。位置式 PID 的输出公式长这样output kp * error ki * integral kd * derivative;增量式 PID 则输出一个 PWM 增量值delta_output kp * (error - last_error) ki * error kd * (error - 2 * last_error last_last_error);实际使用时增量式 PID 的输出要累加到上一次输出上并且要做输出限幅防止 PWM 超范围。我在控制代码里通常这样写float pid_incremental(PID_TypeDef *pid, float error) { float delta pid-kp * (error - pid-last_error) pid-ki * error pid-kd * (error - 2 * pid-last_error pid-last_last_error); pid-last_last_error pid-last_error; pid-last_error error; pid-output delta; // 输出限幅 if (pid-output pid-out_max) pid-output pid-out_max; if (pid-output pid-out_min) pid-output pid-out_min; return pid-output; }关于积分项很多比赛代码包干脆把ki设成 0这是有道理的。在速度控制场景中如果电机堵转或者传感器丢信号积分项会持续累积输出直接顶到限幅值造成积分饱和。对比赛小车我可以接受 2% 以内的静差换取控制系统的抗扰动能力。如果你真的需要高精确的速度保持建议在积分项里加一个只在误差小于某个阈值时才积分的条件而不是无脑积分。3.3 速度环与转向环差速转向的实现小车最常见的运动模型是差速驱动左轮和右轮速度相同则直行速度不同则转弯。控制上就要两个环速度环和转向环。速度环左右轮分别闭环到目标速度。比如目标直行速度是 1.8m/s那么左右轮都按 1.8m/s 的目标做 PID 调节。转向环根据循迹传感器或陀螺仪计算出一个转向修正量correction然后left_speed_target base_speed correction; right_speed_target base_speed - correction;这个修正量的正负由传感器误差方向决定。很多代码包里看到steering_pid指的就是这个转向修正量。我调试的时候最反感一种做法把速度环的 PID 参数和转向环的 PID 参数混在一起调。这两个环的响应带宽要求完全不一样——速度环可以慢一点100ms 左右响应转向环必须快20ms 以内响应因为赛道上的连续弯道要求小车实时调整方向。如果你上来就调转向环的kp但是速度环还在振荡那么两个环会互相激励参数怎么调都调不好。正确的调参顺序是先只跑速度环用直道测试让左右轮实际速度都能稳定跟踪目标速度误差不超过 3%然后再叠加转向环从低kp开始逐步加大直到小车过弯平滑没有抖动。3.4 调参顺序与现场整定技巧具体到 PID 参数的整定我喜欢用先 P 后 D 最后微调 I的顺序具体步骤可以这样操作ki和kd先设 0只保留kp。逐渐增大kp观察实际速度响应如果速度缓慢接近目标且没有超调说明kp太小如果实际速度在目标值上下振荡且幅度不减说明kp太大。找到一个临界kp让速度开始出现等幅振荡然后取这个值的 50%~60% 作为工作kp。保持kp不变增大kd微分项观察动态响应。kd增大可以让系统更快稳定但过大会让系统对噪声非常敏感表现出来就是 PWM 值高频抖动电机发出滋滋声。最后再决定是否加ki。如果实际速度与目标速度之间存在稳定偏差比如总差 0.05m/s再加一点ki消除静差。对绝大多数比赛场景不加ki也能跑出不错成绩加了反而可能带来积分饱和风险。现场整定时还有一个小技巧把 PID 参数存进 EEPROM 或者 Flash 的指定地址用串口命令在线修改参数。比如发送kp 30.5程序解析后直接把 PID 结构体里的kp字段改成 30.5。这样你在赛道上试跑时不需要反复刷固件就能快速调参实测下来一轮测试能省至少三分钟。这个功能很多代码包都不提供但我认为这是比赛调试效率提升最明显的一个小工具。4. 传感器数据处理循迹信号、避障判断与多源融合4.1 红外循迹模块的信号解析循迹小车的眼睛通常是红外对管阵列常见的有 3 路、5 路、8 路。以 5 路灰度传感器为例每个传感器在白色地面上输出高电平在黑色引导线上输出低电平不同模块极性可能相反需要看原理图确认。代码包里对传感器数据的处理通常不是简单的哪路黑就怎么转而是先做加权归一化。我给 5 路传感器分别设位置权重// 左1、左2、居中、右2、右1 int16_t weights[5] {-40, -20, 0, 20, 40};每路传感器输出0或11 表示检测到黑线然后计算加权和int16_t position 0; for (int i 0; i 5; i) { position sensor_binary[i] * weights[i]; }这个position值就是小车相对于黑线的横向偏差。position 0表示车正好在黑线正上方position 0表示车偏左了需要往右修正具体符号取决于权重方向设计。把position作为转向 PID 的输入输出转向修正量就构成了完整的循迹闭环。这里有一个很多人忽略的细节传感器扫描周期和速度环的周期要匹配。如果速度环是 20ms 周期传感器采集却放在主循环里随缘执行可能 5ms 也可能 30ms那转向 PID 的输入会抖动得很厉害。我自己的代码里有一个统一的定时器中断每 5ms 扫描一次传感器每 20ms 执行一次控制策略所有数据都按固定周期更新这样小车的控制才稳定。4.2 多路传感器的状态判定与鲁棒性处理5 路传感器一共有 32 种可能状态但只有少数几种是正常状态比如只检测到中间一路、检测到中间加右一、检测到最右两路等。有些状态是异常边界状态比如全部为 0小车冲出赛道、全部为 1传感器跨在很粗的十字线上。代码包里处理这些异常状态的策略决定了比赛时小车的生死。我见过的处理方式全部为 0说明丢线了。此时不更新position沿用上一次position同时根据上一次的方向继续加大转向修正尝试找回黑线。全部为 1大概率是停在十字交叉线上。此时保持直行让小车快速通过十字区域。最左或最右路检测到说明弯道很急需要将转向修正量输出放大必要时配合减速。我在这个位置的注释里会写得很清楚// 丢线保护连续丢线超过200ms则停车防止小车盲目冲出赛道 if (line_lost_time 200) { Motor_Stop(); }这个丢线保护逻辑我用在了好几届比赛上。曾经有两次小车在高速入弯时直接冲出赛道正是靠这个逻辑在冲出赛道前果断刹车才没把车壳和传感器摔坏。你看代码包的时候如果发现里面没有丢线保护逻辑强烈建议自己加上。4.3 超声波避障的数据滤波与阈值设定避障模块常用 HC-SR04 超声波测距触发脚给至少 10us 高电平模块自动发出 8 个 40kHz 的脉冲并检测回波回波高电平持续时间和距离成正比距离 (cm) 高电平时间 (us) / 58。超声波数据最大的问题是噪声和干扰包括声波打到斜面产生的多次反射、同频超声波模块互相串扰、快速移动中产生的异常值。我处理超声波的流程很简单但很有效连续采 5 次去掉最大值和最小值取中间 3 次的平均值。如果最新值和上次有效值差超过 30cm直接丢弃继续用上一次的有效值。只有当连续 3 个控制周期都确认有障碍物且距离小于阈值时才触发避障动作。这个三次确认逻辑能够在很大程度上避免小车被一个瞬时噪声骗到。超声波的避障阈值通常设为 25~30cm小于这个距离执行停车-转向-绕过动作。转向方向取决于障碍物在左边还是右边有时候代码包还会配合陀螺仪做 90 度精确转向。4.4 多源冲突仲裁循迹优先还是避障优先一个完整的比赛方案往往同时装有循迹和避障传感器这时就会遇到多传感器打架的问题循迹模块说该左转超声波模块说左边有墙不能转。代码包里必须有一个明确的仲裁逻辑。我自己的仲裁策略很简单按优先级分三个等级第一优先避障。超声波检测到前方 25cm 内有障碍物时优先执行避障动作循迹信号暂时忽略。第二优先循迹。避障动作结束后根据循迹传感器状态回到赛道跟踪。第三优先行驶状态机。包括启动延时、通过停车线停车、结束比赛等状态。状态机的实现用switch-case最常见typedef enum { STATE_INIT, STATE_START_DELAY, STATE_TRACKING, STATE_AVOIDING, STATE_FINISH } CarState; CarState car_state STATE_INIT;在main循环或者固定周期中断里先处理状态迁移再根据当前状态执行对应动作。这种结构的好处是任何时刻你都知道小车在做什么调试时加日志打状态值看一眼就能定位问题。很多代码包没有状态机一个while(1)里把循迹、避障、速度控制全混在一起跑起来完全失控时根本没法查。5. 赛场实测常见故障排查链路与调参经验5.1 电机不转的排查链路比赛现场最吓人的情况永远是上电之后小车完全不动。这时候别慌也别急着拆车按下面这条链路一步步查先看电源指示灯。如果用 8.4V 锂电池确认主控板稳压到 3.3V/5V 正常。很多小车莫名其妙死机根源就是电池电压掉到保护阈值以下。用万用表量电机直接供电端。如果电机端子电压为 0检查电源开关、接线端子如果电压正常但电机不转跳下一步。把电机线从驱动板上拔下来直接接一个电池点亮或者转一下排除电机本身烧毁。如果电机没问题检查驱动板逻辑电源VCC是否到位确认 TB6612 的 STBY 引脚是否被拉高——这个引脚不拉高驱动芯片永远不输出。用示波器或者逻辑分析仪看 PWM 引脚有没有输出波形。如果 PWM 是 0 或恒定高检查程序里的Motor_SetSpeed()是否真的被调用以及速度值是否被 PID 限幅限成了 0。有一次我的小车在测试中突然右轮不转排查完毕发现是驱动板上一颗贴片电容被撞掉了本身不影响电路但当时代码里 STBY 引脚配置成了浮空输入而不是推挽输出引脚电平不稳导致驱动芯片偶尔关闭。这种硬件问题很难从代码层面发现所以排查到第 4 步时一定要停下来测一下 STBY 电平。5.2 小车跑偏的根因定位小车直道跑偏是比赛中的高发问题。很多人第一时间去调 PID 参数但我的经验是先查机械和硬件再查软件。机械原因左右轮子胎压不一致、轮胎磨损程度不同、底盘变形导致重心偏移。这类原因是不管 PID 调得多好都会跑偏。硬件原因左右编码器接线顺序不一致导致一边计数正一边计数负代码里如果没有做绝对值处理闭环就直接打架。软件原因编码器方向配置错误速度符号反向了。验证方法很简单把小车架空左右轮同时给 20% 相等的 PWM看编码器读出的实际速度是否一致。如果编码器读数一致但轮子转速不一致说明是电机本身有差异如果编码器读数就不一致优先检查接线和定时器配置。解决直道跑偏还有一个土办法给速度环加一个直道偏置补偿。在直道检测条件下让右轮一直多跑一点点或左轮少跑一点点把机械差异带来的误差吃掉。这个方法是权宜之计不能替代硬件问题的修复但如果赛前 10 分钟发现跑偏它是最快的止血手段。5.3 过弯甩尾与出界的调参对策弯道是比赛小车的分水岭。过弯时如果速度太高惯性会把小车往外甩表现为甩尾——轨迹比预期弯曲半径大很多甚至直接冲出赛道。这个问题的根因不是转向修得不够而是进弯速度没有降下来。我处理弯道问题的方法是分段速度规划高速直道段目标速度 2.0m/s。180 度掉头弯目标速度降到 1.0m/s。直角弯目标速度 1.3m/s。S 型连续弯道全程 1.2m/s。速度切换的平滑性也很重要。直接从 2.0m/s 降到 1.0m/s 会让小车猛点头甚至导致重心前移、后轮抓地力下降。所以速度规划里要加一个加速度限制float accel_limit 2.0f; // m/s^2 float max_delta_v accel_limit * dt; if (target_speed current_speed max_delta_v) { target_speed current_speed max_delta_v; } else if (target_speed current_speed - max_delta_v) { target_speed current_speed - max_delta_v; }这个加速度限制让速度像平滑斜坡一样过渡既保证了过弯稳定又不会让直道加速太肉。实测下来同样的弯道加上这个限制后过弯平均速度只降了 0.1m/s但稳定性有质的提升。另外过弯时转向 PID 的响应速度和直道是不一样的。我通常给转向 PID 设置两组参数直道沿用低kp1.0 左右检测到大偏差比如最外侧传感器触发时切换高kp2.0 左右并同时降低目标速度。这种提前减速 强力转向的组合拳是过弯不甩尾的核心。5.4 现场赛的备份策略与版本管理比赛现场突发状况比实验室多得多。到了场地灯光、地面颜色、摩擦力都和训练场不一样你必须预留足够的现场调试时间。我一般会提前准备以下几件事代码备份比赛前一天的稳定版本压缩包命名加上日期和备注比如car_v1.4_final_night_before.rar。每个版本的代码必须是当时能跑的最佳状态不要带半成品改动。参数备份PID 参数、速度规划表、传感器阈值写成独立的config.h现场改只动这个文件。如果有多个调试方案用条件编译切换#define TRACK_MODE_LIGHT 1 // 浅色地面参数组 #define TRACK_MODE_DARK 2 // 深色地面参数组测试脚本比赛现场打一次完整的测试日志记录各传感器的原始值和 PID 输出值。根据日志判断需要调哪些参数。这个习惯帮我避免过很多次调了参数但不知道是哪个参数起作用的混乱。硬件备件额外带一套驱动板、电机、编码器、传感器模块别问为什么——每次比赛总有人的板子冒烟或者轮子飞出去。我有一次比赛前 20 分钟发现传感器支架断了幸好备用支架带了才没让整队几个月的心血白费。我在多个比赛季里反复体会到一个道理代码包本身只是起点真正让小车跑起来的是你对底层驱动、控制逻辑和现场调试的完整把握。每次比赛结束我都会把当时的代码包原封不动地存一份包括那些跑飞了的版本。赛后复盘时你看着那些鬼畜参数反而能回忆出当时发生了什么这些经验就是下一届比赛最大的财富。本文还有配套的精品资源点击获取