尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

智能循迹小车PID控制全解析:从原理到调参实战

智能循迹小车PID控制全解析:从原理到调参实战 1. 智能循迹小车到底难在哪里很多朋友入手智能小车项目第一反应是不就是沿黑线跑吗写个if判断不就行了。等你真的把车放到赛道上面对连续弯道、十字路口、发夹弯就会发现原来的想法太天真了。循迹小车真正的难点在于它本身是一个典型的非线性、时变、强耦合的物理系统。车体的重心分布、电机响应速度、轮胎材质、电池电压、赛道摩擦系数这些因素全都在动态变化。你上一次调试测出的完美参数换块电池可能就完全不适用了。PID控制算法在这里的价值就是用一套统一的理论框架把传感器读到偏差到驱动电机修正这个闭环过程量化、稳定化、可调参化。它不是在某个特定赛道上跑出来的wild hack而是一个能够适配不同赛道、不同车型、不同传感器方案的通用控制框架。这也是为什么不管是做电磁三轮、红外两轮、还是摄像头四轮最终大家都会走到PID这条路上来。本文适合正在准备智能车竞赛的学生、自学的嵌入式爱好者以及已经实现能跑但想优化跑得稳、跑得快的开发者。我会结合电磁三轮智能小车这个比较典型的平台把PID的拆解、实现、整定、踩坑一路讲透尽量用大白话把控制原理讲明白文章里给到的参数、步骤都是可以直接抄作业的。2. 为什么是PID而不是其他控制方式2.1 开关控制的局限先看最基础的方案。传感器检测到偏左就右转偏右就左转。这叫开关控制bang-bang control。它最大的问题有两个一是输出量只有全速左转/全速右转两个状态小车在赛道上来回横摆跑起来像喝醉了酒二是执行机构的惯性会带来严重超调尤其是速度稍快一点车身就会冲出赛道。我做第一版循迹车的时候就用的这种方式入弯还算勉强出弯必然画龙。原因是开关控制根本没有误差有多大就修正多少的概念它只有一个定性的判断没有定量的程度。2.2 开环控制的瓶颈另一种思路是开环控制提前设定好电机PWM值直线给固定值弯道给另一个固定值。这种方式的缺陷更明显——它无法感知实际脱离理想轨迹的程度。电机老化、地面湿滑、电池电压波动任何一个扰动都会让实际路径和预期路径越偏越远而控制器对此一无所知。开环控制适合环境绝对稳定的场合但循迹小车恰恰相反它面对的是一个充满随机扰动的真实环境。2.3 闭环PID凭什么胜出PID的全称是比例-积分-微分控制它属于闭环控制核心逻辑是不断比较目标值与当前值的偏差然后根据偏差的大小、历史累积、变化趋势三个维度计算出一个连续的控制量。把它和前面两种方式对比一下差距就很明显了控制方式是否闭环输出连续性抗扰动能力调参复杂度开关控制是离散弱无开环控制否连续无低PID闭环控制是连续强中实际上温度控制、无人机姿态稳定、四轴飞行器、云台增稳这类工业和生活场景里大量用到PID原理完全一样只是被控对象从小车的位置换成了温度的数值或者飞行器的角度。理解了循迹小车里的PID其他领域的PID你也能触类旁通。3. PID三个环节在循迹场景里分别扮演什么角色PID公式长这样[ u(t) K_p \cdot e(t) K_i \cdot \int_0^t e(\tau)d\tau K_d \cdot \frac{de(t)}{dt} ]看着数学符号多拆成白话就好理解了。3.1 P比例现在偏了多少就修多少比例项的输出是当前误差乘以比例系数Kp。误差越大修正力度越强这是整个PID的主力输出。电磁三轮小车里误差一般定义为小车偏离赛道中心线的程度。假设你用电感采集电磁场强度计算出当前位置偏差是-30偏左P项就会给出一个正的控制量让车向右修正。Kp调得越大对偏差的反应越猛。但P有个天生的毛病它需要先有误差才有输出。也就是说小车从直道冲进弯道时一定是在已经偏离了一小段之后才开始被拉回来这就是响应滞后的来源。Kp调太高又会在目标位置附近来回震荡就像你开车打方向盘打得太多不停在车道里蛇形。3.2 I积分历史欠账慢慢还清积分项把过去所有误差累积起来再乘以积分系数Ki。它解决的是稳态误差问题——这是循迹里最容易被新手忽略的环节。什么叫稳态误差举个例子小车在过一个大弧弯时如果只靠P项车会始终偏向弯道外侧保持一个持续的偏差角度因为只有存在这个偏差P项才能产生足够的转向力维持过弯。这个维持性偏差就是稳态误差。这时候积分项就有用了误差一直存在积分项慢慢累积输出逐渐加大把车往内侧多拉一把直到误差归零。打个比方P是队长看到有人迟到就批评I是队长记考勤迟到的次数越积越多处罚也越重。但在循迹小车上I项要非常谨慎地使用。因为赛道一圈跑下来如果某段全是同一个方向的弯道积分可能会积累出一个很大的值导致出弯后车突然向内猛打方向。一般要么把I限幅要么只在低速场景下用。3.3 D微分提前预判刹车减速微分项关注的是误差的变化趋势——误差变化越快输出越大。它是阻尼器作用是抑制震荡、减小超调。继续用开车类比P是你看到车偏了往右打方向盘D是你同时感受到车正在快速往右偏转于是提前收一点方向避免车冲到右边去。D项相当于给系统加了一个预见性它不是在误差变大之后去追而是在误差刚开始变大的时候就出手压制。在电磁循迹里D项尤其重要。因为电磁传感器的采集频率、车体的惯性都决定了系统天然存在滞后D项可以在误差还没发展到很严重的时候提前介入修正。这也是为什么很多人调完PID之后发现P和I调了半天还是振荡一旦加上合适的D车一下子就稳了。但D项的副作用是放大了传感器噪声。电磁信号本身有波动计算出的误差值会抖动微分项对这些高频抖动特别敏感输出会变得毛躁。所以实际代码里通常都会对微分项做滤波处理或者用不完全微分结构。3.4 位置式PID和增量式PID选哪个循迹小车里常用的是增量式PID。它输出的是本次应该在上一次基础上调整多少而不是这次该给多大控制量。增量式的优点是输出只依赖最近几次误差不需要累加绝对误差积分饱和问题天然减轻即使算出的增量有问题也只是在原有基础上小幅抖动不会导致输出瞬间跳变具体实现时增量式PID的离散化公式采样周期T固定为[ \Delta u(k) K_p [e(k) - e(k-1)] K_i e(k) K_d [e(k) - 2e(k-1) e(k-2)] ]实际输出为[ u(k) u(k-1) \Delta u(k) ]用代码翻译一下就是#include stdint.h typedef struct { float Kp; float Ki; float Kd; float target; float last_error; float prev_error; float output; } PID_TypeDef; float PID_Calc(PID_TypeDef *pid, float feedback) { float error pid-target - feedback; float delta_u pid-Kp * (error - pid-last_error) pid-Ki * error pid-Kd * (error - 2.0f * pid-last_error pid-prev_error); pid-output delta_u; // 输出限幅 if (pid-output 100.0f) pid-output 100.0f; if (pid-output -100.0f) pid-output -100.0f; // 误差更新 pid-prev_error pid-last_error; pid-last_error error; return pid-output; }注意这个写法里Ki对应的是误差的即时项而不是传统位置式的累加项这是因为增量式推导后Ki项直接乘了当前误差。实际用的时候根据自己的公式配合好就行关键是三个系数的物理含义要清楚。4. 从传感器信号到转向指令的完整闭环4.1 电磁传感器的信号链路电磁循迹的核心部件是电感线圈。赛道中心线埋有通有20kHz交变电流的导线产生电磁场。小车上的水平电感感应到磁场后会产生一个与距离相关的电压信号。这个信号非常微弱通常只有几毫伏到几十毫伏需要经过放大、检波、滤波之后才能给单片机ADC采集。大致链路是电感线圈 - 谐振电路 - 运放放大 - 二极管检波 - RC滤波 - 单片机ADC谐振电路的作用是让电感对20kHz的特定频率信号有最大响应抑制环境中的其他电磁干扰。运放把信号放大到ADC量程内二极管检波把交流信号整流成直流电平RC滤波再把高频纹波滤掉最后得到一个平滑的、大小随距离变化的电压值。ADC采集到的是左右两个或更多电感的电压值。电压越大说明电感离导线越近。将左右电压归一化后可以用加权平均法计算车身相对于赛道的横向偏差[ error \frac{V_R - V_L}{V_R V_L} ]这个公式妙在它是归一化的不管信号整体强弱比如电池电压变化导致磁场强度变化只要左右对称误差就是0一旦车身偏向一侧对应的电感电压升高误差方向立刻体现出来。4.2 三轮结构的转向特性电磁三轮小车通常是前轮转向舵机 后轮驱动电机的结构和差速转向的两轮车不一样。三轮结构的转向由舵机直接控制前轮偏角响应快、模型简单和小轿车类似。转向控制量就是前轮的期望转角映射到舵机PWM占空比。舵机的PWM范围一般是1000us到2000us的高电平脉宽对应左右极限角度。PID输出的是是否需要向左或向右打多少角度的量最终要换算成PWM脉冲宽度。举个例子舵机中位是1500us左右极限分别是1250us和1750usPID输出范围为-100到100那么映射关系就是uint16_t pwm_width 1500 (int)(pid_output * 250 / 100);限幅一定要做否则PID输出超出范围后舵机会打死或者发出嗡嗡的堵转声长期工作会损坏舵机。4.3 编码器闭环与速度控制转向有了PID速度同样需要PID闭环。后轮电机上安装编码器实时测量实际转速与目标速度比较通过PID调节PWM占空比。这就是双闭环结构内环是速度环外环是转向环。比赛实践中速度环的输出直接影响电机驱动器的PWM转向环的输出影响舵机角度。两者互相影响高速过弯时如果速度环响应太慢车身会在弯中减速不足导致转向跟不上。我调试时习惯先调速度环再调转向环。速度环调好之后给转向环一个固定的测试速度逐步优化转向参数。如果两个环一起调出问题了根本分不清是速度没跟上还是转向不合适。5. 参数整定从瞎调到有章法5.1 手动整定的顺序和原则PID参数整定是个实践性很强的工作不同车的理想参数差异很大但有一套大致通用的流程。我的建议顺序是先P、再D、后II多数情况下可以不启用。整个过程搭配赛道调试Step by step来做。第一步把Ki和Kd都设为0只留Kp。从一个比较小的值开始比如电磁三轮车舵机控制Kp可以先从10左右起跳。把小车放上赛道观察它的响应。如果小车迟钝、明显追不上赛道变化加大Kp如果小车在直道上左右抖动、蛇形前进说明Kp太大需要回调。第二步加上Kd。Kd从小值开始加比如Kp的1/10到1/5。观察目标蛇形和振荡是否明显减小过弯是否比原来紧凑第三步如果直道存在持续单侧偏置排除机械、传感器问题后再加Ki。Ki同样从很小的值起跳而且一定要给积分项输出限幅否则积分饱和能把车带出赛道。一个比较典型的电磁三轮参数范围舵机控制量归一化为-100到100是参数建议初始值调整范围Kp105~30Kd20.5~8Ki0先用P/D0~0.55.2 临界比例度法如果你不想完全瞎试可以试试经典的临界比例度法Ziegler-Nichols整定法的简化版。步骤是把Ki和Kd设为0逐步增大Kp直到系统输出出现等幅振荡记下此时的临界增益Ku和振荡周期Tu按经验公式计算初值Kp 0.5KuKi 0.5Kp/TuKd 0.125Kp*Tu这个方法在工业温度控制上很经典放在小车上同样有效只是等幅震荡不太好观察。我的建议是把小车放在直道上让它在道上跑观察它是否开始在道中心两侧有规律地来回摆。如果摆动幅度稳定、节奏均匀就说明接近临界状态了。不过要注意临界比例度法得到的参数只是起点实际赛道上还需要微调。而且电磁循迹车系统的响应比温度控制系统快得多采样周期一般是10ms~20ms震荡周期很小观察时要用电脑上位机把误差曲线实时画出来单靠肉眼看车摆起来往往已经太晚了。5.3 我用上位机曲线调参的经验有条件的话强烈建议把误差数据和PID输出数据通过蓝牙或串口发到上位机画出实时曲线。这样能清楚地看到直道上的误差是否在零线附近小幅波动弯道入口处误差的变化斜率是否过陡PID输出是否有明显的过冲是否存在周期性的振荡我第一次调的时候没有上位机全凭肉眼追着车看结果只看到车甩尾完全不知道误差曲线长什么样。后来把数据发到串口用匿名上位机或者简单的Python脚本绘图一目了然。我自己用Python写了个简单的串口绘图脚本用的是pyserial加matplotlib适合现场快速分析import serial import matplotlib.pyplot as plt import matplotlib.animation as animation ser serial.Serial(COM5, 115200, timeout1) fig, (ax1, ax2) plt.subplots(2, 1, figsize(10, 6)) error_series, output_series [], [] time_series, t [], 0 def update_frame(frame): global t line ser.readline().decode(errorsignore).strip() parts line.split(,) if len(parts) 3: try: error, output, speed float(parts[0]), float(parts[1]), float(parts[2]) except ValueError: return time_series.append(t) error_series.append(error) output_series.append(output) t 0.01 if len(time_series) 300: time_series.pop(0) error_series.pop(0) output_series.pop(0) ax1.clear() ax2.clear() ax1.plot(time_series, error_series, b-, labelerror) ax1.axhline(0, colorr, linestyle--, linewidth0.8) ax1.legend() ax1.set_ylabel(error) ax2.plot(time_series, output_series, g-, labelpid output) ax2.axhline(0, colorr, linestyle--, linewidth0.8) ax2.legend() ax2.set_ylabel(output) ax2.set_xlabel(time (s)) ani animation.FuncAnimation(fig, update_frame, interval50) plt.show()对应单片机上每隔10ms发送一行数据printf(%.1f,%.1f,%.1f\r\n, error, pid_output, speed_measured);看到曲线之后调参效率能提升好几倍。P大不大、D够不够、有没有积分饱和曲线上一眼就能看出来。5.4 赛道分段与速度自适应进阶一点的玩法是速度自适应。既然小车在直道可以跑高速在弯道必须减速那就可以根据当前误差的绝对值或者误差变化率来动态调整目标速度。我的实现策略是误差绝对值小于阈值比如0.15认为是直道目标速度设为高速值误差绝对值较大比如大于0.5近似的认为正在过急弯目标速度降为低速值中间区域线性插值避免速度突变代码如下float compute_target_speed(float error) { float abs_error fabsf(error); float high_speed 2000.0f; // 编码器目标值 float low_speed 1200.0f; float threshold_high 0.5f; float threshold_low 0.15f; if (abs_error threshold_high) { return low_speed; } else if (abs_error threshold_low) { return high_speed; } else { float k (abs_error - threshold_low) / (threshold_high - threshold_low); return high_speed - k * (high_speed - low_speed); } }这套逻辑配合双闭环PID是我测试下来稳定性最高的方案。比起全程固定一个速度跑这套方案在综合弯道赛道上通常能快出2到3秒。6. 实战中的常见问题排查6.1 高频问题速查表现象可能原因排查方向直道上小车蛇形前进P过大或D过小导致阻尼不足降低Kp或适当增加Kd过弯冲出去转向响应太慢速度过快提高Kp/Kd或降低弯道目标速度某一段固定方向偏电感安装不对称或传感器基准偏移重新校准中位检查机械安装车在原地左右抖动PID输出振荡采样频率和系统响应不匹配增大采样周期检查传感器滤波舵机打死发出嗡嗡声PID输出值超出转向映射范围检查限幅代码确认映射关系速度环在直道上忽快忽慢编码器数据有毛刺或速度PID参数不合适先滤波编码器数据再调速度环参数换电池之后性能明显变差PID参数针对旧电压环境系统增益变化给舵机/电机驱动加稳压模块或重新整定6.2 高速发飘问题这个基本是每个做电磁小车的人都会遇到的坑。速度上来之后车在弯道外侧飘出去。一开始我以为单纯是打滑后来发现根本不是轮胎抓地力不够而是转向PID的输出饱和了。车速快的时候过弯需要的向心力随速度平方增长转向角必须很大。但舵机的极限角度是有限的PID输出超过映射范围后直接被限幅截断车就只能以最大转角过弯。最大转角还不足以提供足够向心力车自然就飘了。这种问题的解药不在PID公式里而在系统层面上要么入弯前提前减速速度自适应要么在转向环里加入误差变化率前馈在真正进入弯道前预打一点方向。我后来在转向环里加了一项前馈误差变化率越大提前给一个基础转角。这能显著减少高速入弯时的响应滞后。6.3 电磁信号漂移电磁信号受环境影响会出现漂移尤其是周围有金属物体或者电机产生强磁场干扰时。一个典型的坑是电机大电流通断瞬间左右电感电压同时跳变导致归一化后的error值出现毛刺转向舵机跟着猛抖一下。处理方法信号链路加硬件RC滤波截止频率放在500Hz到1kHz左右软件上对ADC值做滑动平均滤波或一阶低通滤波对最终error值做限幅变化率处理限制两次采样间误差值的最大变化幅度一阶低通滤波的代码很简单#define ALPHA 0.3f float lowpass_filter(float input, float last_output) { return ALPHA * input (1.0f - ALPHA) * last_output; }ALPHA越小滤波越强但同时滞后也越大。循迹车通常取0.2到0.4之间的值比较合适具体看你的赛道环境和采样频率。6.4 温度变化导致的参数漂移最后聊个有趣的现象。我有一次在实验室暖风环境下把参数调到完美第二天到室外冷风环境试车发现车完全变了个手感。原因是温度影响了电池内阻、电机的机械阻尼、轮胎的硬度系统整体模型发生了偏移。这个问题很难完全消除但有缓解手段电池尽量用稳压模块供电减少电压变化对系统的直接影响开跑之前先让车在赛道上跑两圈热身让电机和轮胎达到工作温度再调参条件允许的情况下在比赛场地现场微调参数这就是为什么比赛车队通常都有一本调参记录记录每次调参时的温度、湿度、电池电压、轮胎状态方便复现当天的最佳参数。7. 我的几个实操心得写到最后分享几条我踩过很多次坑之后总结出来的经验。PID参数不是越大越好或者越小越好它是一个匹配问题。Kp、Ki、Kd要和系统的采样周期、执行机构响应速度、传感器噪声水平匹配。你在网上抄来的参数跑得再好换一套采集频率、换一个舵机型号都得重新调。每次只改一个参数改完记录效果。很多新手喜欢同时调Kp和Kd发现车跑得不错但完全不知道是哪个改动起了作用下次遇到类似问题依然两眼一抹黑。我的习惯是写一个调参记录表每次只动一个参数跑一圈之后把表现写下来。软件限幅一定要做。不管是PID输出的限幅、误差变化率的限幅还是速度目标值的限幅都要在代码里明确写出来。没有限幅的PID一旦某个传感器瞬间跳变输出可能直接拉满舵机、电机驱动器都有损坏风险。能加前馈就不要全靠反馈。PID再好它本质上是在出错之后才做出反应。如果赛道信息可以在传感器上提前预判比如前方是急弯用前馈提前动作控制效果会有质的提升。最后是一条通用法则**基础机械结构决定了上限控制算法只是让你逼近这个上限。**电感安装不对称、轮胎磨损不均匀、底盘重心偏置这些问题再怎么调PID也救不回来。先把机械问题解决掉再谈参数优化。这一点无论在智能车竞赛还是工业调试中都是通用的。
返回列表