ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Arduino巡线机器人硬件选型与PID调试全链路

Arduino巡线机器人硬件选型与PID调试全链路

做 Arduino 巡线机器人这个题目,我前后折腾过七八台车,从最早用三路开关量传感器、L298N 驱动、PID 写成纯 P 调节的那种“能跑但不稳”的版本,到后来给竞赛队做的带编码器、能跑 3m/s 的版本,中间踩的坑足够写一本小册子。很多人以为巡线的难点在 PID 算法本身,实际上真正决定成败的是硬件层的几个基础决策和调试环节的信息可见性:传感器间距差 3mm,赛道上的表现能差一个档次;PID 参数没调好还能慢慢试出来,但电源设计有缺陷,车跑起来会随机复位,调参根本无从谈起。这篇内容把 Arduino 巡线机器人从硬件选型到 PID 算法调试的完整链路拆开讲,包含参数计算过程、代码落地的关键细节、以及实际赛道上常见的十几种故障的排查思路。不管你是刚买回一套套件准备做课程作业的新手,还是已经能跑起来但速度上不去想突破瓶颈的老手,下面这些内容都能直接拿去用。

1. 先想清楚:巡线机器人的核心矛盾在哪

1.1 巡线本质上是一个位置闭环问题

把巡线机器人剥开看,它的核心只有一个变量:车身相对于黑线的横向偏移量。传感器负责把这个几何量转换成电信号,控制器负责算出“该往左打多少方向”,执行机构负责把方向变成左右轮的转速差。整条链路上任何一环有延迟、有噪声、有非线性,最终都会体现为车在赛道上画龙或者冲出弯道。

理解这一点特别重要,因为它决定了你做设计时的优先级排序。很多新手的第一反应是“我要把 PID 算法写得漂亮一点”,于是花大量时间去研究微分先行、不完全微分、抗积分饱和这些理论,结果车还是抖。原因往往在于:传感器间距 20mm 配 30mm 宽的赛道线,中间永远有一段“两路都不压线”的盲区,误差量在正负之间来回跳变,再好的 PID 也治不了这种硬件层的信息丢失。

我用一个生活化的类比说明这件事:想象你蒙着眼睛在一条窄路上走,靠脚尖感知路面边缘。如果你的脚特别大,一脚踩过去直接跨过了路沿,你根本不知道自己是偏左还是偏右;如果你的脚特别小,感受很灵敏,但每走一步都要重新确认,反应就慢。传感器间距就是这个“脚的大小”,它必须和赛道线宽匹配,这是设计的第一性原则,任何算法都替代不了。

所以一个合理的开发顺序应该是:先确定车要跑什么样的赛道,由赛道线宽反推传感器间距和数量,再确定车速和电机规格,然后才是控制算法。反过来做,先写算法再配硬件,大概率要推倒重来。

1.2 三条技术路线的取舍:开关量、模拟量、摄像头

巡线传感器在市面上主要有三大类,选错方向会让后续开发效率差好几倍。

第一类是开关量传感器,典型代表是 TCRT5000 模块,输出高低电平,一位数字量。它的优点是便宜、接线简单、单片机直接读数字口不需要 ADC。缺点是阈值靠板载电位器手动调,场地光线一变就失效,而且只能告诉你“这一路在线上/不在线上”,没有程度信息。如果你做的是课程作业、赛道固定、室内光照稳定,它完全够用;但如果你想做速度,它的信息量太少了。

第二类是模拟量灰度传感器,通常是 5 路、7 路或 8 路集成模组,每一路输出一个模拟电压,反映该点反射光强。它比开关量多了一个关键能力:可以做归一化,可以算出亚像素级别的偏差量。用插值法可以做到比物理间距更精细的分辨率,这是提速的基础。

第三类是摄像头方案,用线阵 CCD 或者低分辨率摄像头,信息量最大,前瞻距离远,适合高速循迹。代价是需要处理图像,对主控算力有要求,调试门槛明显更高。

我的建议很明确:第一次做选 5 路模拟量灰度模组,能跑通完整链路又不会太难;要打比赛冲速度,上 7 路或 8 路模拟量,配合加权法算偏差;摄像头方案放在最后,等你把前两种玩明白了再说。

2. 硬件选型:每一分钱该花在哪个环节

2.1 主控板:UNO 够用,但要知道它的边界

主控选型的核心约束不是“性能越强越好”,而是“资源是否匹配需求”。Arduino UNO 用的是 8 位 AVR,主频 16MHz,32KB Flash,2KB SRAM,10 位 ADC,6 路 PWM 输出。对于巡线这个任务,它其实完全够用——模拟量读取加 PID 运算加 PWM 输出,一个控制周期的工作量在几百微秒量级,2ms 的控制周期绰绰有余。

但要知道它的几个硬边界。第一是 2KB SRAM,这意味着你不能开大数组做图像缓冲,浮点运算也要省着用,一个 float 占 4 字节,几个数组下去再加上串口缓冲就紧张了。第二是 10 位 ADC,理论分辨率 1024 级,实际有效位数会掉到 8 位左右,做归一化的时候要注意噪声。第三是 ADC 采样保持电路的输入阻抗特性,如果你用一个大的分压电阻网络,采样会不准,建议传感器输出阻抗控制在 10kΩ 以内。

如果要升级,Nano 是同芯片的小体积版本,做小车更合适;Mega2560 引脚多,适合接一堆外设的复杂版本;ESP32 是我现在更推荐的方向,12 位 ADC、双核、自带蓝牙方便在线调参、主频高到可以跑浮点运算毫无压力。用 ESP32 唯一的麻烦是 3.3V 逻辑电平,接 5V 的传感器模块要注意电平匹配和 ADC 输入范围,别把引脚喂烧了。

注意:ESP32 的 ADC2 在开启无线功能时不可用,接传感器时优先用 ADC1 对应的引脚,这个坑我在第一次用的时候卡了整整一个下午。

2.2 传感器间距与安装高度:两个被严重低估的参数

传感器间距是整套硬件设计里最值得反复推敲的参数。假设你用的是 20mm 宽的电工胶带做赛道线,那么相邻传感器的间距应该略小于线宽,让车在任意偏移位置下都至少有一到两路压在线附近。具体算一下:如果间距 15mm,5 路传感器总跨度 60mm,中心线到最外一路 30mm,那么当车偏移量在 ±30mm 内都能检测到线,这个范围足够用了。如果间距做到 25mm,5 路总跨度 100mm,虽然检测范围大,但中间会出现“空隙”,线正好落在两个传感器之间时,两个读数都在中间值,加权算出来的位置是准的,可一旦噪声让其中一个掉下去,误差就跳变。

我实际测试的经验值是:传感器间距取线宽的 0.6 到 0.75 倍比较合适,20mm 线宽对应 12mm 到 15mm 间距。7 路模组跨度在 72mm 到 90mm 之间,检测范围够宽,同时保持足够的分辨率。

安装高度同样关键。红外反射式传感器的最佳检测距离通常标称 2mm 到 15mm,实际用在 8mm 到 12mm 之间最稳。装太低容易剐蹭地面,尤其是赛道有接缝的时候;装太高则发射管的光斑扩散,反射回来的信号弱,黑白对比度变差,归一化之后动态范围不够。我一般先用一块活动支架把传感器固定在 10mm 高度,跑起来之后再微调,同时给传感器加一个遮光罩,避免环境光直射和相邻传感器的串扰。

2.3 电机与驱动:差速转向的物理基础

巡线车的转向完全靠左右轮转速差实现,所以电机的一致性直接决定了车的直线性。廉价的 TT 马达(1:48 减速比,空载 200rpm 左右)的问题是同一批次内两个电机的转速差异可能有 10% 甚至更多,导致你不给指令它自己就跑偏。这个偏差在代码里靠 I 项或者一个固定的偏置量来补偿,但补偿是静态的,负载变化后又会失配。

我推荐的方案是带编码器的减速电机,用编码器测速做速度闭环,把左右轮的实际转速反馈到控制回路。这样一来,电机个体差异、电池电压跌落、地面摩擦变化这些扰动都会被速度环吸收掉,外层的巡线环只需要专心处理位置偏差。这是从“能跑”到“跑得稳”的关键一步。

驱动选型上,L298N 是老牌方案,但它的问题是内部达林顿管压降大,标称 2V 左右,实际带载时发热严重,效率损失明显。TB6612FNG 是更现代的选择,MOSFET 输出,压降小,持续电流 1.2A,支持 100kHz PWM,封装小到可以直接焊在转接板上。我做过对比测试,同样的 7.4V 电池、同样的电机,L298N 方案在满载时电机两端电压只有 5.4V 左右,TB6612 能到 6.8V,转速差出 20% 以上,这直接决定了你最高能跑多快。

还有一个容易忽略的点是 PWM 频率。Arduino 默认的 PWM 频率在引脚 5、6 上约 980Hz,在引脚 9、10 上约 490Hz。这个频率落在人耳可听范围内,跑起来电机会有啸叫,更麻烦的是低速时每个 PWM 周期只有几十微秒的导通时间,电机的电流还没建立起来就被切断了,表现为低速启动困难、抖动明显。解决办法是通过修改定时器预分频寄存器把 PWM 频率提到 4kHz 以上,或者干脆用支持高频 PWM 的驱动芯片。改定时器会影响到 millis() 和 delay() 的时基,改完要重新核对时间函数,这个坑我踩过,做完之后串口打印的时间全乱了。

2.4 电源设计:随机复位的根源

电源是巡线车最容易出问题、又最难排查的一环。典型的故障现象是:车放在地上静止时一切正常,一给电机指令就重启,或者跑过弯道时随机复位。原因几乎都是电机启动瞬间的大电流把电池电压拉低,导致主控的稳压器输出跌出工作范围,触发掉电复位。

计算一下就能明白:两个 TT 马达堵转电流各 1.5A 左右,启动瞬间就是 3A,2S 锂电池的内阻加保护板内阻按 150mΩ 算,瞬态压降就是 0.45V,从 7.4V 掉到 6.95V,看起来还好。但如果你用的是 9V 层叠电池,内阻几百毫欧,压降能到 2V 以上,主控必挂。所以电源第一原则:不要用 9V 层叠电池给电机供电,那是课程作业里最常见的错误。

实用的方案是 2S 锂电池或两节 18650 串联供电,电机直接吃 7.4V,主控通过降压模块(或 LDO)取 5V。关键是两点:一是主控的供电和电机供电在走线上要分开,从电池端子分别引出两路,避免电机大电流在共用走线上产生压降干扰主控;二是电机电源端并一个 470μF 到 1000μF 的电解电容,再并一个 0.1μF 陶瓷电容,前者吸收低频浪涌,后者滤高频毛刺。这一套加上去,复位问题基本消失。

3. 传感器数据处理:从原始 ADC 到可信的偏差值

3.1 归一化:让参数在不同场地复用

模拟量传感器直接读出来的原始值没有通用意义。同一辆车,在白色地板上读 800,在黑线上读 200;换到深色地毯上,白底可能只有 500,黑线 150。如果你把阈值写死在代码里,换个场地就要重调,这在比赛现场是非常危险的。

归一化的做法是:在标定阶段,把车分别放在纯白区域和纯黑区域,记录每一路传感器的最大值和最小值,然后按公式换算。

norm[i] = (raw[i] - white[i]) * 1000 / (white[i] - black[i])

换算后每一路的输出在 0 到 1000 之间,0 表示完全在黑线上,1000 表示完全在白色背景上。这样做的好处是参数和场地的绝对亮度解耦,Kp、Kd 这些系数调好之后换场地基本不用动。

标定的实操细节:让车在赛道上缓慢走一圈,同时通过串口把每一路的原始值打印出来,取每一路的最大值和最小值作为标定值。要注意避免标定值被异常值污染,比如传感器边缘扫过反光胶带时读到的极端值,所以标定的时候最好做一次排序取 5% 和 95% 分位数,而不是简单的 max/min。

3.2 加权法算偏差:把离散读数变成连续量

有了归一化数据,下一步是把它变成一个单一的偏差量。最常用的是加权平均法,也叫质心法。假设 5 路传感器编号为 0 到 4,中心是 2,那么权重就是 -2、-1、0、1、2。

weighted_sum = Σ ( (2 - i) * norm[i] ) // 或者用 (1 - norm[i]) 表示黑度 weight_sum = Σ norm[i] error = weighted_sum / weight_sum

这里有一个关键点:用白度还是用黑度做权重。如果传感器在黑线上读数小(归一化后接近 0),在白底读数大(接近 1000),那么我们真正关心的是“哪一路最黑”,所以权重应该用黑度,也就是 1000 减去归一化值。这样算出来的误差量,符号表示偏移方向,绝对值表示偏移程度,物理意义清晰。

算出来的 error 范围大约在 -2 到 2 之间。这个量纲直接决定了后面的 PID 系数大小。如果你用 7 路传感器,权重变成 -3 到 3,同样的控制效果下 Kp 的数值会小一些。所以网上抄来的 PID 参数一定要结合自己的误差量纲来看,直接照搬大概率不对。

这里还有一个技巧:用插值可以提高分辨率。5 路传感器物理间距 15mm,但通过加权计算,实际能分辨的偏移量远小于 15mm,因为相邻两路的读数比例是连续变化的。这就是模拟量方案比开关量方案精细的根本原因。

3.3 特殊赛道元素的识别与处理

普通弯道靠 PID 就能过,但赛道上的十字路口、断线、直角弯、终点线这些元素必须单独处理,否则车会在十字路口直接冲出去。

判断思路是利用误差量的两个特征:有效传感器数量和总黑度。正常循迹时,总黑度大约对应一到两路压线;如果所有路都检测到黑,总黑度达到满量程,那就是横向的十字路口或终点线;如果所有路都是白,总黑度为 0,那就是断线。

处理逻辑:

  • 全黑且持续时间超过设定阈值:判定为十字路口,进入直行模式,锁定当前舵量或直接给左右轮相同速度,持续固定时间或固定距离后恢复循迹。
  • 全白:先做“记忆循迹”,保持上一次的转向方向继续走一段,同时降低速度,如果在设定时间内容错仍未恢复,就原地转向搜索。
  • 单侧多路持续黑、另一侧全白:可能是直角弯,需要加大转向力度,甚至允许原地转向。

提示:十字路口的直行延时不要用固定毫秒数,因为速度一变延时对应的距离就变了。用编码器累计脉冲或者用固定时间乘以当前速度估算距离,换速度时不用重新调参。

4. PID 算法落地:从数学公式到定时器中断

4.1 位置式还是增量式:先看你的执行机构

PID 有两种常见形式,位置式和增量式。位置式直接算出控制量的绝对值,增量式算的是本次比上次应该增加多少。

巡线场景下我更推荐位置式,因为执行机构是 PWM 占空比,本身就是一个绝对量,位置式算出来直接映射到 PWM,逻辑清晰。增量式的好处是不需要累加误差、天然抗积分饱和、切换手动自动时无扰,但它的输出需要做累加,一旦中间有一步算错,误差会一直累积下去,现场的“车越跑越歪”很多时候就是增量式累加器溢出或初始化不当导致的。

位置式的公式:

error = 目标位置 - 当前位置 // 巡线中目标位置恒为 0 P = Kp * error I = Ki * (I_prev + error * dt) D = Kd * (error - error_prev) / dt output = P + I + D

因为采样周期 dt 固定,实际写代码时可以把 dt 吸收进系数里,变成纯离散形式:

output = Kp * error + Ki_sum + Kd * (error - error_prev) Ki_sum += Ki * error

这样每一步只有两次乘法和几次加法,AVR 上跑起来毫无压力。

4.2 采样周期:这个参数比 Kp 更容易出错

采样周期是我见过最多人忽略的参数。很多人把 PID 运算直接丢在 loop() 里,读传感器、算 PID、写 PWM,然后循环。问题是 loop 的执行时间不固定:串口打印会拖慢它,中断服务程序会插队,传感器读取如果用了带延时的库函数更是灾难。结果就是实际的 dt 在 2ms 到 20ms 之间随机波动,而你代码里假设它是固定的,微分项直接炸掉。

正确的做法是用定时器中断或者 millis() 做严格的时间片调度。我一般用 5ms 一个控制周期,具体选择依据是:车的最高速度 2m/s,传感器前瞻 30mm,那么通过前瞻距离的时间是 15ms,控制周期必须明显小于这个值才能及时响应,5ms 对应 3 次控制,够用。如果车速更高,控制周期要相应缩短到 2ms。

用 millis() 实现固定周期的骨架大概是这样:

unsigned long last_time = 0; const unsigned int PERIOD_MS = 5; void loop() { unsigned long now = millis(); if (now - last_time >= PERIOD_MS) { last_time += PERIOD_MS; // 注意不是 last_time = now int error = readLineError(); int output = computePID(error); setMotor(output); } }

注意last_time += PERIOD_MS这一行,而不是last_time = now。区别在于前者能自动补偿处理耗时,长期运行不会累积漂移;后者会把每次的执行时间误差累积起来,跑几分钟之后周期就完全走样了。

4.3 输出限幅、死区补偿与抗积分饱和

PID 算出来的 output 是转向量,需要叠加到一个基础速度上才能变成左右轮的 PWM:

left_pwm = base_speed + output right_pwm = base_speed - output

这里有几个必须处理的边界情况。

输出限幅:output 的绝对值不能超过 base_speed,否则一边轮子会变成负值,出现“倒转”,车会原地打转。正确做法是先对 output 做限幅,限幅范围取 base_speed 的一定比例,比如 0.9 倍。

死区补偿:直流电机有启动死区,PWM 低于某个值(实测 TT 马达大约 30 到 50,取决于负载和电池电压)电机根本不转。当 base_speed 较小、output 又让某一侧 PWM 掉到死区以下时,那个轮子会停转,车的转向特性和你算的完全不一样,表现为低速循迹时“一顿一顿”。解决办法是给 PWM 输出加一个最小绝对值映射:非零输出统一抬到死区以上,或者用查表补偿。

抗积分饱和:积分项在车长时间偏离中心时会一直累加,等车回到中心后积分项还很大,导致过冲。标准的处理是积分分离加限幅:当误差绝对值超过阈值时停止积分,同时给积分项一个上下限,比如限制在 output 最大值的 0.3 倍以内。

巡线场景下,I 项其实经常可以不用。因为理想情况下赛道左右对称,机械误差可以通过一个固定的偏置量补偿掉,比积分项更可控。我大部分作品里 Ki 都是 0,只在电机差异特别大的廉价套件上才开一点,取值大概是 Kp 的 1/50 到 1/100。

4.4 完整控制代码的结构

把上面几块拼起来,一个可用的控制循环大概长这样:

const int BASE_SPEED = 130; float Kp = 32.0, Kd = 180.0, Ki = 0.0; float error_prev = 0, integral = 0; const int DEAD_ZONE = 40; int computePID(float error) { float P = Kp * error; integral += Ki * error; integral = constrain(integral, -40, 40); float D = Kd * (error - error_prev); error_prev = error; float out = P + D + integral; return constrain((int)out, -110, 110); } void setMotor(int turn) { int left = BASE_SPEED + turn; int right = BASE_SPEED - turn; left = compensate(left); right = compensate(right); drive(left, right); } int compensate(int pwm) { if (pwm > 0 && pwm < DEAD_ZONE) return DEAD_ZONE; if (pwm < 0 && pwm > -DEAD_ZONE) return -DEAD_ZONE; return constrain(pwm, -255, 255); }

这一套跑起来就是最小可用版本。注意 error 的量纲是 -2 到 2(5 路),所以 Kp 取 32,输出最大约 64,配合 base_speed 130 就能在急弯上实现明显的差速。

5. 调参实录:一套可复现的整定流程

5.1 先调机械,再调参数

在动 PID 参数之前,必须先确保机械状态是好的,否则你调出来的参数是在补偿机械缺陷,换台车就失效。检查清单包括:两个轮子直径一致、轮胎没有偏心、车轴平行、重心在几何中心略偏后、传感器板刚性固定不晃动。

一个特别容易被忽略的点是传感器板的固定方式。用扎带或双面胶固定的传感器板,跑起来会有微小振动,采样值跟着抖,表现为 PID 微分项输出尖刺。我一开始不信,后来换了一块 2mm 亚克力板用四个螺丝固定,抖动立刻小了一半。这个改进零成本,收益比调两天参数还大。

还有一个基础检查是做“直线测试”:给左右轮相同的 PWM,在空地上跑 3 米,看车的偏移量。偏移量超过 30cm 说明电机差异太大,要么换电机,要么在代码里加补偿系数。这个测试能帮你判断后面遇到的问题是机械的还是算法的。

5.2 分步整定:P、D、I 的顺序不能乱

我用的整定流程是从 Ziegler-Nichols 的临界振荡法简化来的,不追求理论最优,追求快速可复现。

第一步,Ki 和 Kd 全部置 0,只留 Kp。把车放在赛道上,Kp 从 5 开始逐步增大,每次加 20% 左右,观察直线段。Kp 太小的表现是:车在直道上懒洋洋地慢慢往线中间靠,过弯时贴不住线,切内道或者冲出去。Kp 增大的过程中会到达一个点,车开始在直道上左右摆动,这叫临界振荡,记录下此时的 Kp 记为 Ku,摆动的周期记为 Tu。

第二步,按经验公式给初值:纯 PD 控制取 Kp = 0.6 × Ku 到 0.8 × Ku,Kd 取 Kp × Tu / 8 到 Kp × Tu / 10。这个初值不一定最优,但能让你直接跳到“基本能跑”的状态,省掉大量瞎试的时间。

第三步,精调。先小幅增加 Kd,观察直道上的摆动是否收敛更快。Kd 太小的表现是过弯后车头回正慢,来回摆两次才稳定;Kd 太大的表现是直道上高频抖动,像筛糠一样,同时电机发热明显,因为微分项在放大噪声。Kd 合适的标志是:车过弯时有一个干脆的“切入”动作,切完立刻稳住,不拖泥带水。

第四步,只有当车在长直道上持续向一侧偏、或者左右弯表现明显不对称时,才引入 Ki。Ki 从 Kp 的 1/100 开始,每次翻倍,直到偏移在 2 秒内被纠正。Ki 一旦过大,车会在直道上出现周期很长的缓慢摆动,比 P 项的摆动频率低但幅度大,非常难看。

阶段参数状态观察什么典型现象下一步
粗调 PKi=Kd=0直道收敛速度P 小则懒散,P 大则左右摆找到临界振荡 Kp
加 DKp=0.7Ku过弯后回正次数D 小回正慢,D 大高频抖D 增到刚好不抖
加 IKi 从 0 起长直道是否单侧偏偏则加,不偏则不加加到偏移 2 秒消
提速复核三参数固定最高速下过急弯冲出去说明 P/D 不够回退速度或重调

5.3 让参数“看得见”:在线调参与波形观察

调参效率的高低,取决于你能不能实时看到 error 曲线的形状。我强烈建议在调试阶段把 error 值通过串口发出来,用串口绘图器看波形。一个调好的系统,error 曲线在直道上应该是一条贴着零的小幅波动线,过弯时是一个平滑的峰,过弯后迅速回落,没有二次振荡。

串口打印本身会占用时间,5ms 周期打印十几个字符,在 115200 波特率下大约耗时 1ms,还在可接受范围。如果控制周期更短,就要改成每 N 个周期打印一次,避免影响实时性。

更进一步的做法是在线调参。三种常见方案:一是接三个电位器到模拟口,分别映射 Kp、Kd、base_speed,比赛现场直接拧电位器改参数,最土但最可靠;二是接蓝牙模块,手机发字符串指令P32.5、D180解析后更新参数,适合调试阶段;三是接一个 OLED 加两个按键做菜单,适合成品。

我在比赛现场最信任的还是电位器方案,因为它不依赖任何无线链路,不会在关键时刻连不上。经验做法是电位器映射到参数的一个合理区间,比如 Kp 映射到 10 到 60,转动的时候串口同步打印当前值,调好之后记下来写死到代码里。

6. 常见问题与排查技巧实录

6.1 直道画龙与过弯冲出:两个方向的故障

直道画龙(蛇形走位)是最常见的投诉。原因按概率排序:Kp 过大、Kd 不足、采样周期抖动、传感器间距过大导致误差跳变、机械振动。排查顺序建议从采样周期开始,先确认 loop 的实际执行周期是否稳定,用示波器量一个调试引脚的高低电平翻转间隔,或者在中断里翻转引脚看周期。如果周期稳定再动参数。

过弯冲出赛道的原因通常是:速度过高而前瞻不足、Kp 不足导致转向不够、传感器检测范围太窄在急弯时丢线。这里有个反直觉的点:很多时候不是 PID 参数不够猛,而是传感器根本“看不见”弯道。5 路传感器总跨度 60mm,在半径 100mm 的急弯上,线很快就会移出检测范围。解决办法是加大传感器跨度(换 7 路或 8 路),或者过弯时主动降速,用速度换检测窗口。

降速的实现可以用一个简单的规则:当 |error| 超过阈值时,把 base_speed 按比例降低。这个逻辑在代码里加三行就够,效果比调半天 PID 明显。

int speed = BASE_SPEED; if (abs(error) > 1.2) speed = BASE_SPEED * 0.7; if (abs(error) > 1.8) speed = BASE_SPEED * 0.5;

6.2 低速抖动与启动困难

低速时车一顿一顿、走不直,八成是 PWM 死区问题。前面提过,直流电机有一个最小启动占空比,低于它电机不转或者转得很勉强。当 base_speed 只有 80,而 output 让一侧变成 40 时,这一侧可能完全停转,车就往一边偏,PID 又猛拉回来,形成低频抖动。

诊断方法:把车架空,逐个给固定 PWM 值,从 0 开始每 5 递增,记录电机开始稳定转动的阈值。两个轮子的阈值可能不一样,要分别测。然后在代码里做补偿:任何非零的 PWM 输出,绝对值都不低于对应的启动阈值。

另一个低速抖动来源是电机减速箱的齿轮间隙。TT 马达的塑料齿轮组间隙能达到好几度,正反转切换时有一段“空行程”,PID 输出在零点附近反复穿越时就表现为咔哒咔哒的抖动。这个没法从软件根治,只能通过设置一个小的输出死区来缓解:当 |output| 小于某个值时直接输出 0,让车滑行而不做微调。

6.3 电源、干扰与复位类问题

车跑起来随机复位,按这个顺序排查:先量电池电压,跑动过程中用万用表或者分压后接 ADC 监测,如果跌到主控稳压器输入下限以下,就要改电源方案;再检查所有地线是否单点汇聚,电机地和信号地不能串成一条细线;然后看电机端有没有加续流电容,没有的话电机换向时产生的尖峰会通过电源线窜进主控;最后检查复位引脚有没有被拉低,有些扩展板会把复位信号引到外部,走线太近容易耦合干扰。

传感器读数偶发跳变,特别是车经过特定位置时突然全黑或全白,通常是反光或者环境光干扰。排查方法是用手机摄像头对着传感器发射管看,能看到紫红色的光点,如果光点在白色地面上的反射面积很大,说明安装高度太低。另外,传感器之间会互相串扰,相邻两路的发射光打到地面后反射到隔壁的接收管里,解决办法是给每个传感器加一个黑色热缩管做遮光筒,或者分时点亮(分时点亮需要更复杂的驱动电路,一般不值得)。

现象最可能原因快速验证处理
直道蛇形Kp 过大 / Kd 不足减小 Kp 看是否改善重调参数
过弯冲出速度高 / 检测范围窄降速跑是否正常降速或换宽传感器
低速一顿一顿PWM 死区 / 齿轮间隙架空测启动阈值加死区补偿
随机复位电源压降 / 干扰监测电池电压分路供电加电容
读数突跳环境光 / 串扰遮光后复测加遮光罩调高度
十字路口冲出未做特殊元素识别观察是否全黑加全黑判定直行
单侧持续偏电机差异 / 传感器不对称直线测试加偏置或速度环
参数换场地失效未归一化看原始值范围做黑白标定

我个人在实际操作中的体会是,巡线车这个项目最像真实工程的地方在于:它的问题从来不是单点的,而是层层嵌套的。你以为是 PID 参数没调好,其实是传感器间距不对;你以为是传感器不行,其实是电源在抖;你以为是电源问题,其实是地线走了同一条细线。所以真正高效的做法不是“遇到问题改参数”,而是建立一套可观测的调试手段:让 error 曲线看得见,让采样周期量得准,让电源电压监测得到。这套东西搭起来要多花两三个小时,但能省掉后面几十个小时的盲试。

最后分享一个提速阶段的小技巧:当参数调到极限、车在直道上已经很稳但过弯还是会甩出去时,别急着继续加大 Kp,先试试把传感器往前挪 10mm。前瞻距离增加带来的过弯改善,往往比调一周参数都明显,代价只是需要重新标定一下安装支架。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进