ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

智能循迹小车控制进阶:从if-else到PID闭环的完整实践指南

智能循迹小车控制进阶:从if-else到PID闭环的完整实践指南 刚接触智能循迹小车那会儿我把大半时间都花在调赛道上。代码里用了一堆if-else判断赛道类型直道、弯道、十字、起跑线都能识别小车也能跑起来但走线奇丑无比——左一下右一下整个人坐在地上看它跑都觉得上头整个轨迹像条蛇在游。直到把控制部分从离散的阈值判断换成PID控制算法车子才第一次做到了丝滑过弯。这篇文章就把我在三轮电磁循迹小车上从if-else迁移到PID闭环、再到把参数调到稳定跑完全程的完整过程记录下来包括电感怎么排、偏差怎么算、增量式PID怎么写、双闭环怎么搭、参数怎么调以及那些让我反复返工的坑。适合准备校内智能车竞赛、或者自己DIY循迹小车的朋友参考哪怕你用的是红外循迹或摄像头循迹控制层的思路也通用。我写它的另一个原因是发现很多人卡在同一个地方PID公式背得滚瓜烂熟但一上车就不知道怎么调要么Kp拧太大整车抖成筛子要么Kd给太猛电机啸叫。其实PID在循迹车上没有那么多玄学它就是一套“根据偏差算控制量”的规则你把它放到车的模型里去理解参数自然就有谱了。1. 先把控制对象想清楚循迹小车到底在控什么1.1 循迹系统是典型的负反馈结构智能循迹小车的本质是一个移动机器人放在控制论的框架里看它是一个非常标准的负反馈系统。传感器负责测量“我离目标线有多远”控制器负责根据这个偏差决定“往哪边打多少方向”执行机构电机和车轮负责把方向指令转成真实的转向运动。这里最关键的一点是控制量必须是连续的。你不可能在高速状态下用三个离散挡位把车稳在赛道中央就像开车时你不会把方向盘只握在“正、左满、右满”三个挡位一样。电磁三轮车的执行端是两个驱动电机加一个万向轮靠左右轮差速实现转向。所谓“控制”本质上就是在实时计算两轮之间的转速差让车绕着一个假想的转向中心平滑地拐弯。很多新手把重点放在“识别赛道”上花大量时间优化图像识别或阈值判断结果车还是跑不稳。原因就在于识别只是感知层的事情真正决定走线质量的是决策层的算法。识别做得再好控制量的输出是跳变的车身姿态就一定会跟着跳变。1.2 阈值判断为什么不行离散控制的典型缺陷如果用一句话总结阈值控制的缺陷就是“控制量不够细腻”。三段式控制大概长这样if (error 0.2) { set_steer(LEFT_FULL); } else if (error -0.2) { set_steer(RIGHT_FULL); } else { set_steer(STRAIGHT); }这段代码跑起来车会不停地跨越中线形成S形走线。原因很简单当偏差从0.19变成0.21控制量瞬间从“直行”跳到“左满转”车身会猛地向左然后偏差变成负的又瞬间右满转。车的运动惯性没有变控制量却在一格一格地跳走线自然没法看。高速状态下这个问题会被无限放大。车速越快同样的控制周期内车走过的距离越长状态切换的滞后越明显。你在低速时感觉还凑合一旦把PWM拉上去车几乎是必飞的。对比一下离散控制和连续控制方案使用的偏差信息输出特征典型效果阈值判断只有左/中/右几个区间离散多档蛇形走线高速必飞比例控制连续偏差值连续比例输出平滑但可能振荡PID控制偏差及变化趋势连续且带阻尼平滑且稳定1.3 为什么PID是循迹小车最合适的起点PID适用于单输入单输出、可以被近似为线性的被控对象。电磁循迹小车的横向偏差和转向力矩之间恰好可以近似成这种线性关系。更关键的是PID不需要你精确建立车辆动力学模型只需要根据实际响应去调三个参数就能得到一个工程上足够好的控制器。在智能车竞赛圈里高阶的赛道队伍最终会转向LQR、模型预测控制或者模糊PID但这些都建立在你对车辆模型足够理解的基础上。对绝大多数人尤其是起步阶段的同学PID就是性价比最高的方案它只靠误差的现在、过去、未来三个信息就能完成稳定的闭环控制。先把PID玩明白后面想上更复杂的控制你也有对比的基准。2. 电磁三轮组的“感官系统”电感布局与偏差计算2.1 电磁检测原理20kHz交变磁场下的感应电动势电磁循迹的思路和红外循迹完全不同。赛道下方埋的是一根通有20kHz交变电流的导线电流会在这根导线周围产生同频率的交变磁场。车头上安装的电感线圈相当于一个磁场探头导线中的电流变化时穿过线圈的磁通量也跟着变化线圈两端就会感应出同频的电动势。感应电动势的大小和线圈与导线的距离相关离得越近信号越强。这个强度-距离关系大致呈1/d甚至1/d²的形式衰减也就是说电感离导线远一点信号会迅速掉下来这给“根据信号强度判断横向位置”提供了基础。电感输出的原始信号是一个20kHz的交流小信号电压幅度通常在几毫伏到几十毫伏必须经过运放放大再通过峰值检波电路整成直流电平然后才能送进MCU的ADC采样。如果你直接在程序里采20kHz的交流波采样率不够是采不到有效幅值的所以硬件上一定补一个“信号调理”环节。这一步如果不做后面算法再好也是白搭。2.2 电感布局水平三电感和八字方案怎么选常见的电磁电感布局大概有三种水平三电感车头中间一个、左右各一个形成一组可以算左右偏差的三元组。结构最简单适合入门。水平五电感在左右两边再各加一个更外侧的电感提供更大的线性检测区间同时方便做十字路口、起跑线等特殊元素的识别。八字电感把电感斜向放置斜45度利用磁场水平分量可以在更高速度下提前感知弯道趋势相当于给控制系统提供了一个“前瞻量”。我自己用下来最顺手的是“水平三电感做主偏差计算 垂直八字电感做前瞻”的混合方案。水平三电感负责生成主控用的连续偏差垂直电感的信号则被用来做弯道预判——在高速入弯前提前得到一个较小的修正量让转向环提前动作而不是到了弯心才反应。这里有个特别重要的物理规律电感信号强度随距离衰减极快所以车上电感的安装位置稍微差一厘米归一化之后的偏差曲线就会差不少。装车时固定好之后轻易不要动机械结构否则参数全部重新调。2.3 偏差归一化不做好这步PID参数全是废的拿到三个电感的ADC值之后最大的忌讳就是直接拿原始值做差。为什么因为电感的一致性、运放的放大倍数、电池电压波动都会让原始ADC值在一个很宽的范围内变化。你今天在满电状态下标定好的阈值跑半小时后电压掉了同样的物理位置读出来的ADC可能差了20%偏差量也跟着变了PID参数自然不再匹配。归一化的核心公式是err (R - L) / (L M R);这个式子用四个通道总和做分母把左右差变成“占总信号的百分比”。电池电压变化、整体信号同时增强或减弱时偏差值基本保持不变。这样的偏差天然落在[-1,1]区间负值偏左正值偏右后面PID参数的物理意义也就一目了然。实际代码里要加一个保护float get_lateral_error(void) { uint16_t L adc_read(CH_L); uint16_t M adc_read(CH_M); uint16_t R adc_read(CH_R); float sum (float)L M R; if (sum 1.0f) { return 0.0f; // 信号丢失时回中避免除零 } float err ((float)R - L) / sum; return err; }很多从阈值判断转过来的同学容易忽略归一化直接把左右差值丢给PID结果换了一组电池参数就变了。这个坑我踩过印象极深。2.4 执行端两轮差速转向的映射关系三轮电磁小车一般是一个万向轮加两个驱动轮转向靠左右轮速差实现。假设转向环输出是steer范围在[-1,1]那么映射到PWM上就是pwm_left base_speed steer * delta_max; pwm_right base_speed - steer * delta_max;左边轮子快、右边轮子慢车就往右偏反之亦然。这里base_speed是基准速度delta_max是最大差速量具体数值取决于你的电机和驱动器的响应范围。后面说PID参数的时候会再展开讲这个映射关系的标定。3. PID三种作用在赛道上的真实行为P、I、D分别什么时候出力3.1 比例项整个控制的“骨架”比例项的输出是Kp乘以当前偏差意思很直白偏差大就打大方向偏差小就轻打。这个环节决定了一个系统的响应速度。Kp太小小车入弯时方向打得不够会直直地冲出赛道Kp太大偏差只是稍微越线就会猛打方向结果就是整车左右摇晃跑起来像抽筋。我手上那辆三轮车归一化偏差范围在±1之间转向输出叠加到PWM上的量级需要到±800左右。初期我把Kp设到2直道没问题入弯直接冲出去调大到10直道开始剧烈抖动最后落在6左右才勉强能跑。这个例子说明Kp的合适区间和你的执行机构行程有直接关系不能照搬别人的参数。比例项的缺点是它始终滞后于偏差先有偏差才有输出。想让车提前对弯道作出反应就得靠微分项。3.2 微分项它不是在“刹车”而是在“预判”微分项看的是偏差的变化率也就是e(t)比e(t-1)变化了多少。入弯时偏差开始变大微分项给出一个“阻止偏差继续变大”的作用看起来就像在踩刹车。出弯时偏差开始变小微分项又会让转向平滑地回中避免冲过头越过中线。用开车来类比比例项是你看到车偏了再打方向盘微分项是你感觉到方向盘有自动回正的力矩提前稳住方向盘。它给了系统阻尼这也是为什么微分能显著抑制振荡。但微分项有个臭名昭著的毛病它是高频噪声放大器。传感器ADC值只要抖一点点计算出的变化率就可能很大转向输出跟着狂跳电机发出细细的啸叫。所以D往往要配合滤波一起用比如先对偏差做滑动平均再进PID。控制周期也要稳定否则上一次和这一次的时间间隔不是常数求出来的变化率毫无意义。3.3 积分项在循迹小车上要省着用积分项处理的是历史累计误差把过去所有偏差加起来乘以Ki。它主要对付系统的固定偏置比如传感器装歪了、一边轮子打滑率稍高导致车一直偏着开比例项拉不回来。循迹小车有没有稳态误差严格说长直道上可能会有。但从实际跑赛道的角度看积分项带来的麻烦常常比好处大。为什么因为积分是对整个过程的累积。入弯时偏差持续为0.5积分器攒了一笔输出出弯后偏差回到0附近积分值还在逼着车继续转向这就是过冲的来源。所以我的做法是给积分加限制一是积分限幅只让积分输出不超过某个小值二是条件积分只有偏差绝对值小于某个阈值时才累计大偏差时直接短路积分器。代码里看起来就是if (fabs(err) 0.3f) { integral err * dt; } else { integral 0.0f; }顺便说个关联话题同样的PID放在温度控制里积分就变得极其重要。恒温箱控温时如果偏差持续存在积分就会一直加直到加热功率把温度顶上去。温度系统是大惯性、大滞后对象比例项单独工作会产生固定的稳态误差只有靠积分才能消除。但循迹小车是一个快速运动的低惯性对象它的误差一直在变积分不是主角。同一个PID算法在不同被控对象上参数的权重完全不同这才是“调参”背后的本质。3.4 位置式还是增量式我为什么选增量式位置式PID每次输出的是绝对的控制器输出值。增量式PID每次输出的是在上一个控制量基础上的变化量。对电机这种“带积分性质”的执行机构来说增量式更自然我们只需要告诉电机“你再偏快一点”而不是“你必须转到这个PWM值”。增量式的核心代码不复杂typedef struct { float Kp, Ki, Kd; float err_last; float err_pre_last; float output; } IncrementalPID; float incremental_pid_update(IncrementalPID *pid, float err, float dt) { float delta pid-Kp * (err - pid-err_last) pid-Ki * err pid-Kd * (err - 2.0f * pid-err_last pid-err_pre_last); pid-output delta; if (pid-output LIMIT_MAX) pid-output LIMIT_MAX; else if (pid-output LIMIT_MIN) pid-output LIMIT_MIN; pid-err_pre_last pid-err_last; pid-err_last err; return pid-output; }dt没有单独写进公式是因为我把Kp、Ki、Kd都做成了“已经包含dt”的系数固定控制周期运行时不需要每次再除一遍。增量式天然有抗饱和的好处如果执行机构已经到头了之后的增量不会让输出无限制增长。这也是它在电机控制里特别受欢迎的原因。4. 双闭环架构转向环和速度环应该先调谁4.1 为什么单靠转向环过不了高速弯只用转向环时车速是开环的给一个固定PWM车速完全由电池电压和负载决定。问题是同样的PWM在直道上能跑出3m/s在弯道上因为转向阻力车速自己会掉下来。更关键的是如果速度不随弯道曲率调整转向环输出得再准过弯时的横向加速度也会把车甩出去。所以我的做法是加一个速度环根据当前偏差实时调整目标速度偏差大说明接近弯道就减速偏差小说明在直道就恢复高速。这本质上是一个两级控制外环决定“该跑多快”内环决定“怎么跑到这个速度”。4.2 速度环的反馈编码器测速三轮电磁车一般使用带编码器的直流减速电机。编码器输出的脉冲信号进MCU定时器最常见的是M法测速在固定时间窗口内统计脉冲个数再换算成转速。伪代码如下volatile uint16_t left_pulse_cnt; volatile uint16_t right_pulse_cnt; void TIM_MS_ISR(void) { left_speed_rpm (uint32_t)left_pulse_cnt * 60000.0f / (1000 / TIM_MS) / PULSE_PER_REV; right_speed_rpm (uint32_t)right_pulse_cnt * 60000.0f / (1000 / TIM_MS) / PULSE_PER_REV; left_pulse_cnt 0; right_pulse_cnt 0; }测速窗口需要跟控制周期匹配。我把速度环控制周期设在20ms窗口太短时低速状态下窗口内可能只有几个脉冲算出来的速度跳来跳去速度环反而成了扰动源。测量数据稳定比实时性重要这是速度环调好的前提。4.3 目标速度随偏差动态调整目标速度的生成逻辑不复杂但需要实际标定target_speed SPEED_MAX - k * fabs(lateral_error); if (target_speed SPEED_MIN) target_speed SPEED_MIN;k值的标定方法让车在直道上跑到最大速度然后在发卡弯前观察它降到多少才不会甩尾。我这边常用的配置是最大速度2.5m/s偏差绝对值最大约1.0k取0.8弯道最低限速1.0m/s。如果你发现某个弯还是甩就把SPEED_MIN再往下压。4.4 级联PID的调参顺序为什么先内环后外环很多人一上来就调转向环结果怎么调都感觉转向“发不上力”其实原因是速度环还没收敛车速本身就在振荡。正确顺序是先内环后外环。先给速度环一组稳定的参数让车速在阶跃目标下能快速平稳到达。在此基础上再调转向环调起来会轻松很多。我在调速度环时有个技巧把目标速度设成方波通过串口把实际速度发到上位机看它在100ms左右能不能跟住、有没有超调。速度环稳了转向环的烦恼会消失一大半。5. 参数整定实操从振荡发散到丝滑过弯的完整过程5.1 临界比例度法先找到Kp的极限PID调参第一步永远是只保留P。把Ki和Kd设为0只加Kp然后让车在长直道上跑逐步增加Kp观察车是否开始等幅振荡。找到临界点后记录此时的Kp作为临界增益Ku同时用上位机测量振荡周期Tu。根据Ziegler-Nichols经验公式可以得到一组初始参数控制器KpKiKdP0.5Ku--PI0.45Ku0.54Ku/Tu-PID0.6Ku1.2Ku/Tu0.075Ku*Tu注意这套经验公式来自过程控制用在循迹小车上只是一个起点。我在车上按公式得到的参数通常会再缩小30%到50%因为小车的被控对象比恒温箱轻快得多反应也快得多。5.2 用上位机波形做微调偏差曲线是照妖镜调参过程强烈建议把偏差和控制量通过串口实时发到上位机画波形。没有波形全靠肉眼看车跑基本等于盲调。几个典型的排查方向偏差波形收敛过慢加大Kp。过中线后明显超调加大Kd。长时间等幅振荡Kp太大或者Kd不足。波形上有高频毛刺D太强或ADC信号没滤波。直线稳定但有固定偏移加少量Ki或者检查机械安装是否对称。5.3 不同赛道元素的专项调参直道的要求是“别乱动”这一段偏差只有微小噪声转向输出应该接近零。发卡弯是极限工况偏差快速增大P项要大胆输出D项要足够压制回中过冲同时速度环要提前降速。S弯的节奏很快转向环必须响应灵敏但又不能陷入振荡最考验D的微调。调S弯的时候我经常用蓝牙把偏差波形传到手机上看寻找那个“刚好平滑、没有毛刺”的D值。调完之后记住这组参数的感觉直道安静、大弯有力、S弯轻快不抖基本就是及格线。5.4 一次完整的标定记录拿我当时的整车参数举例增量式PID控制周期10ms偏差范围[-1,1]转向输出范围限定在[-600,600]基准PWM为4000。先只加PKp从1.0开始。1.5时直道正常但入弯冲线2.5时直道开始摇晃3.0时出现明显的等幅振荡。于是记录Ku3.0Tu0.45s。按Ziegler-Nichols公式推出来Kp1.8实际再降一点到1.6。接着加Kd从0.2开始试到0.8时发现高频噪声被放大退回0.6后弯道回中非常平滑。最后Ki只给了0.01并且只在偏差小于0.3的时候才累计用来纠正轻微的机械偏置。这套参数最终跑下来的效果是直道不抖、大弯不甩、S弯勉强稳定圈速比之前if-else时代快了近一倍。这个提升主要不是参数有多神奇而是连续控制本身比离散控制高一个维度。6. 调试现场最常见的坑与我的排查记录6.1 直道上的PWM毛刺现象是直道明明应该直直地开电机声音却忽高忽低。我先怀疑是电机驱动问题换了一个还是这样。后来用上位机看转向输出发现波形上叠着一层高频毛刺判断是ADC信号抖动被D项放大。解决办法分两步走先对归一化偏差做一次滑动平均窗口取5再把D值从0.8降到0.6。毛刺立刻消失走线也稳了。这个案例的教训是D项要和信号滤波成对出现否则它就是在帮你放大噪声。6.2 出弯回不到中线积分饱和背锅有一次跑发卡弯出弯之后车总是偏向外侧要过好一阵才回来。我把偏差曲线和积分项一并导出才发现入弯时偏差长时间为0.7积分器攒了一大笔输出。出弯后偏差变成0.2比例项已经要求小角度转向积分项却还在逼着车继续打方向结果车冲到外道。解决方式是给积分输出限幅限制在±30以内并且只在偏差小于0.3时累计。这种条件下Ki才能稳定地发挥作用而不是帮倒忙。6.3 低速转向没反应、高速突然猛转电机死区直流电机存在死区PWM低于某个阈值时驱动力矩不足以克服静摩擦车轮根本不转。转向输出叠加到基准PWM后如果基准太小左右轮差速在低速段根本拉不出有效转向一旦越过死区转速又会对PWM变化非常敏感于是出现“没反应—突然猛转”的跳变。处理办法有两个方向一是把基准PWM保持在死区之上宁可用速度环的限速去压低平均速度也不要让PWM落入死区二是在死区附近做分段补偿给PWM加一个小的跳变补偿量把非线性尽量修正成线性。6.4 电池电压下降导致参数漂移最容易被忽略的坑是电池电压下降。刚充满电跑得很好跑了五分钟之后走线变差这不是心理作用而是锂电放电过程中电压下降同样的PWM占空比下电机实际功率减了不少。开环PWM控制系统里等效的“系统增益”变了PID参数自然就不再最优。解决这个问题的最优解是速度环闭环让速度环自己通过编码器反馈去补偿电压波动。如果暂时没上速度环可以做电压前馈补偿采集电池电压按V_ref/V_bat比例修正目标PWM。但说到底双闭环才是根治办法这也是我一再强调速度环重要性的原因。最后再说一个习惯每次调参之前先把车架空垫起来用上位机观察各个传感器信号和输出曲线是否合理确认传感器是干净的、PID输出是光滑的再下地跑。这样可以排除掉大量软硬件干扰。智能循迹小车这东西说难不难说简单也不简单关键是把每个环节拆开来看清楚。信号对了模型清楚了剩下的就是花点时间耐心拧参数。希望这篇记录能帮你少走弯路早点让你的三轮车丝滑地跑起来。
返回列表