ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32电机PID闭环调速实验:从开环失控到精确控速

STM32电机PID闭环调速实验:从开环失控到精确控速 PID控制实验02做完了先交代一下背景实验01结束的时候我给自己留了个很闹心的尾巴——给电机一个固定的PWM占空比转速根本稳不住。电池电压稍微掉一点或者用手轻轻捏一下轮子转速肉眼可见地往下掉。当时我测过一组数据占空比50%不变空载转速约4800 RPM给轮子加一点阻力直接掉到4200电源从12V跌到11V时甚至只有3900。开环就是这种“只发指令、不管结果”的状态代码再怎么写也没法让转速自动回到目标值。所以实验02我就把霍尔编码器装上了用PID闭环控制把转速钉在目标值上。这篇文章是这次实验的完整工作记录内容包括硬件接线、STM32定时器编码器模式配置、位置式与增量式PID的离散化实现、串口上位机波形调试以及完整的临界比例法调参过程最后是这次踩过的几个大坑。如果你也想把手头那台小电机从“能转”做到“转得准”这篇文章应该能帮你省不少时间。1. 实验01为什么留下烂摊子三个必须上闭环的理由1.1 负载变化是最直观的失控场景实验01的程序其实很简单定时器输出固定占空比的PWM电机转起来就完事了。但只要你让电机带负载麻烦立刻出现。我用手轻捏轮子模拟负载转速当场掉了600多转这还没有施加什么剧烈阻力只是轻轻一碰。原因不复杂直流电机本身有一个特性负载转矩增大时电枢电流增大转速会沿着机械特性曲线下滑这是电机固有的物理特性和程序怎么写关系不大。关键是如果系统没有任何反馈控制器根本不知道转速已经变了。一个只输出不读取的系统本质上和“拧了一个电位器然后就不管了”没有任何区别。这正是开环控制最典型的局限它对被控对象的实际状态一无所知。1.2 电源电压波动是另一个躲不掉的干扰第二个问题来自电源。我用的是一个12V适配器给电机供电按理说电压应该很稳但实际测量发现电机启动瞬间电源电压会跌到10.8V左右堵转时甚至能到9V多。电压一跌电机转速跟着跌整个系统处在一种“供电电压决定一切”的状态里。这在实际项目中是个致命问题电池供电的设备电量从满到枯电压是一条缓慢下降的曲线如果用开环PWM就意味着同一个占空比在不同电量下对应的转速完全不同。做产品的时候用户不会关心你占空比设了多少他们只关心轮子转得快不快、跑得直不直。所以电源波动这个干扰只能靠闭环反馈去抵消。1.3 闭环控制回路的组成闭环控制的核心思路也很简单把输出结果测出来和目标值比较得到误差再用某种算法根据误差修正输出如此循环往复。以我这次做的电机转速控制为例回路结构是这样的目标转速 → 计算误差 → PID控制器 → 输出占空比 → 电机驱动器 → 电机 → 编码器 → 测出实际转速 → 回到误差计算这个循环就是负反馈。注意“负”这个字很关键误差的定义是“目标值减去实际值”当实际转速低于目标时误差为正控制器加大输出当实际转速超过目标时误差为负控制器减小输出。回路永远是朝着“消除误差”的方向走这就是闭环稳定工作的前提。1.4 实验02的量化目标做实验不能光说“要稳定”得有可验证的指标。我在实验开始前定了四条考核标准稳态精度目标转速300 RPM时稳态误差不超过5 RPM即1.7%以内动态性能目标从300 RPM阶跃到450 RPM超调量不超过10%调节时间不超过0.5秒抗扰动能力稳定运行中用手短暂捏住轮子制造扰动转速偏差不超过30 RPM且在1秒内恢复到目标值可复现性整套调参过程要能记录参数和波形换一台电机后可以照着重复。这四条标准看着简单实际做起来每个环节都有坑。下面按实验推进的顺序把每一步的关键细节拆开讲。2. 硬件与信号链路编码器反馈是怎么进单片机的2.1 器件选型与理由这次实验用的平台是STM32F103C8T6俗称“Blue Pill”板子很便宜但该有的外设一个不少。之所以选它而不是Arduino最重要的原因是STM32的定时器原生支持编码器模式可以用硬件完成正交解码CPU不需要额外去判断旋转方向这对后续做更复杂的控制非常友好。整套硬件的清单和作用如下表器件型号/规格作用主控STM32F103C8T6运行PID算法产生PWM读取编码器电机12V直流减速电机带霍尔编码器被控对象编码器参数13线/圈电机轴减速比30:1输出轴每转一圈产生1560个计数电机驱动DRV8833双H桥模块把PWM信号放大成功率信号驱动电机电源12V/2A电机电源 3.3V逻辑电源动力和逻辑分开供电调试USB转TTL串口模块把PID运行数据发给上位机选DRV8833是因为它比L298N体积小、压降低逻辑电压3.3V可以直接兼容STM32。电机选择上面编码器线数不需要太高13线配合30倍减速比输出轴每转1560个计数对300 RPM级别的调速精度已经足够。真要是用500线光栅编码器配高转速电机反而是杀鸡用牛刀还要处理一大堆信号完整性问题。2.2 编码器接线与定时器编码器模式配置霍尔编码器输出两路方波分别叫A相和B相。两相信号之间相差90度相位电机正转时A相超前B相反转时B相超前A相。定时器的编码器模式就是根据这个相位关系自动完成计数方向判断正转时计数器向上累加反转时向下累加。这一点非常关键因为CPU完全不需要去轮询判断方向硬件已经处理好了。接线很简单编码器的A相接TIM3的CH1B相接TIM3的CH2VCC和GND分别接3.3V和地。在STM32CubeMX里把TIM3配成Encoder Mode预分频设为0计数值范围65535两个通道的输入滤波可以设到10档左右用来滤掉电机电刷产生的毛刺。htim3.Instance TIM3; htim3.Init.Prescaler 0; htim3.Init.CounterMode TIM_COUNTERMODE_UP; htim3.Init.Period 65535; HAL_TIM_Encoder_Start(htim3, TIM_CHANNEL_ALL);启动编码器计数后每次控制周期里读取一次计数器值和上一次的值做差就得到这个周期内的计数增量。注意这里有个工程技巧用(int16_t)把32位计数器强转成16位有符号数再做差。正常情况下计数增量远小于32767所以这样的强转不会出错还能自动处理计数器回绕的问题非常省事。int16_t enc_count (int16_t)__HAL_TIM_GET_COUNTER(htim3); int16_t delta enc_count - last_count; last_count enc_count;2.3 转速换算公式与验证编码器计数换算成转速的公式不复杂。输出轴每转一圈产生1560个计数13线 × 减速比30 × 正交解码4倍频控制周期Ts取20ms那么转速的计算式是转速(RPM) 计数增量 × 60 ÷ (1560 × 0.02)算一下常数项60 ÷ (1560 × 0.02) ≈ 1.923也就是说每个计数值大约对应1.9 RPM。目标300 RPM对应的计数增量大约是156分辨率完全够用。我把它定义成宏换电机或改减速比时只改一处就行。#define ENC_PPR 13 #define GEAR_RATIO 30 #define ENC_COUNTS_REV (ENC_PPR * GEAR_RATIO * 4) #define TS_S 0.02f #define COUNT_TO_RPM (60.0f / (ENC_COUNTS_REV * TS_S))接线完成后先别急着写PID第一件事是手动验证编码器读数。我用手慢慢转动轮子在串口打印计数变化正转时计数应该增加反转时计数应该减少。这一步花五分钟做掉能避免后面整个调参过程被“编码器方向反了”这种低级问题带偏。另外建议大家顺便验证一下转速数值是否合理用手机秒表计时让轮子转10圈对比自己计算的RPM和串口打印出来的RPM差太多就说明换算系数有问题。3. 算法选型与离散化位置式PID和增量式PID到底怎么选3.1 连续PID公式到差分方程的离散化PID这个名字大家都熟但真正动手写代码时最常犯的错就是把连续公式直接搬过来完全不理解离散化是怎么回事。连续域的PID公式长这样u(t) Kp × e(t) Ki × ∫e(t)dt Kd × de(t)/dt单片机是数字系统没法做真正意义上的积分和微分只能用采样周期Ts把连续量离散化。积分用求和近似微分用前后两次误差的差分近似∫e(t)dt ≈ Ts × Σe(k) de(t)/dt ≈ (e(k) - e(k-1)) / Ts代入之后得到位置式PID的离散形式u(k) Kp × e(k) Ki_code × Σe(j) Kd_code × (e(k) - e(k-1))这里有个容易混淆的地方公式里的Ki_code和Kd_code是把采样周期折进去之后的等效增益分别等于Ki×Ts和Kd/Ts。也就是说你实际在代码里填的Ki和Kd数值是依赖Ts的。同一个物理控制器Ts从20ms改成50ms代码里的数值必须跟着变否则控制效果会完全不同。很多调参调不明白的人问题就出在这里——他根本没意识到Ts一变增益含义就变了。顺带说一句“纲量”的事PID的输出量纲取决于你把它接到什么执行器上。在这个实验里输出是PWM占空比百分比-100到100不是电压也不是电流。Kp的单位是“输出百分比 ÷ 转速误差”Ki是“输出百分比 ÷转速误差×采样次数”Kd是“输出百分比 ÷ 转速误差变化率”。搞清楚这个你才不会问出“为什么我的Kp是1.2别人的电机Kp是12”这种问题——那是输出量纲不同的结果。3.2 位置式PID代码与积分饱和隐患位置式PID的实现很直接每次算一个完整的输出值u(k)。但直接用会踩一个著名的大坑——积分饱和。想象一下电机从静止启动到目标300 RPM的过程启动瞬间误差是300如果Ki是0.12积分项每个周期就往里加300×0.1236累计10个周期就到360远超输出限幅100。结果就是电机还没转起来积分已经满贯了。等到转速真正接近目标时这个“早已膨胀起来”的积分还会持续推高输出电机冲过头超调非常严重。我处理这个问题的办法是积分分离只有误差小于一定阈值时才允许积分累加误差大了就先让比例项和微分项干活。typedef struct { float Kp; float Ki; float Kd; float integral; float prev_error; float out_limit; float i_threshold; } PositionPID; float PositionPID_Calc(PositionPID *pid, float target, float actual) { float error target - actual; float output; if (fabsf(error) pid-i_threshold) { pid-integral error; if (pid-integral pid-out_limit) pid-integral pid-out_limit; if (pid-integral -pid-out_limit) pid-integral -pid-out_limit; } output pid-Kp * error pid-Ki * pid-integral pid-Kd * (error - pid-prev_error); pid-prev_error error; if (output pid-out_limit) output pid-out_limit; if (output -pid-out_limit) output -pid-out_limit; return output; }这是位置式PID比较标准的写法积分限幅、输出限幅、积分分离缺一不可。后面调参时我可以随时通过串口把integral这个内部变量发上来观察这是位置式PID在调试阶段的优势——每个环节的贡献都能直观看到。3.3 增量式PID从位置式推导出来增量式PID的输出不是绝对的控制量而是“当前控制量相对上一次的变化量”。从位置式出发用u(k)减去u(k-1)经过整理可以得到Δu(k) Kp × (e(k)-e(k-1)) Ki × e(k) Kd × (e(k) - 2e(k-1) e(k-2))代码实现比位置式更简单因为不需要维护积分累加项取而代之的是维护一个“累积输出”变量typedef struct { float Kp; float Ki; float Kd; float prev_error; float prev_prev_error; float output; float out_limit; } IncPID; float IncPID_Calc(IncPID *pid, float target, float actual) { float error target - actual; float delta; delta pid-Kp * (error - pid-prev_error) pid-Ki * error pid-Kd * (error - 2.0f * pid-prev_error pid-prev_prev_error); pid-prev_prev_error pid-prev_error; pid-prev_error error; pid-output delta; if (pid-output pid-out_limit) pid-output pid-out_limit; if (pid-output -pid-out_limit) pid-output -pid-out_limit; return pid-output; }增量式的第一个优点即使目标值跳变很大Δu是有限增量输出不会瞬间跳到满量程系统行为要温和得多。第二个优点积分饱和问题在结构上被绕开了输出累积器的限幅天然约束了积分的增长不需要额外写积分分离。第三个优点增量式天然适合需要“手动/自动无扰切换”的场合——反正输出是叠加出来的切换时只需把累积值同步一下就行。3.4 最终选择两个版本我都实现了也都在这次实验里跑了对比。位置式胜在内部变量直观适合学习和调试增量式胜在防饱和结构好、输出平滑更适合作为最终交付的控制代码。所以本次实验后续调参用的是增量式PID位置式PID的代码留作对比参考。两种方案的对比整理成下面这张表对比项位置式PID增量式PID输出含义绝对控制量控制量增量积分饱和需要显式处理累计输出限幅即可目标突变响应输出易冲击输出平滑调试可观测性可看P/I/D分量只能看最终输出手动/自动切换麻烦简单代码量略多略少4. 调试链路搭建把PID的状态实时画出来看4.1 需要观察哪些数据用什么样的协议上传调PID靠串口打几个数字是不够的你必须同时看到目标值、实际值和控制器输出三条曲线才能判断系统当前处于什么状态。三条线分别说明什么目标值和实际值的对比能判断超调、调节时间、稳态误差输出曲线能暴露限幅、振荡、积分异常等问题。没有波形调参就等于闭着眼拧旋钮。数据量其实很小控制周期20ms也就是每秒50次每次3个浮点数总共才600字节每秒。115200波特率的串口轻轻松松扛住。我选择用上位机软件VOFA来画波形它的“JustFloat”协议很简单连续发3个4字节浮点数再加上一个固定的帧尾0x00 0x00 0x80 0x7F上位机收到帧尾就把前面12个字节解析成3个通道。uint8_t vofa_buf[16]; void vofa_send_3f(float d0, float d1, float d2) { memcpy(vofa_buf, d0, 4); memcpy(vofa_buf 4, d1, 4); memcpy(vofa_buf 8, d2, 4); vofa_buf[12] 0x00; vofa_buf[13] 0x00; vofa_buf[14] 0x80; vofa_buf[15] 0x7F; HAL_UART_Transmit_DMA(huart1, vofa_buf, 16); }注意这里用的是DMA发送而不是阻塞发送。阻塞发送在串口波特率不高时还好但一旦你把打印频率提高或者一次发很多数据就会严重拖慢控制周期。这个问题我踩过一次后面第6节专门讲。4.2 VOFA的上位机配置步骤VOFA这个软件是免费的下载安装后配置几步就能用。第一步在设备列表里找到USB转串口对应的COM口波特率选115200数据位8、停止位1、无校验第二步把协议类型选成JustFloat第三步打开一个空白波形页把三个通道分别绑定通道0、通道1、通道2第四步在数据发送区域设置一个按钮用来发送目标转速的修改指令。我在代码里专门做了个简单的串口命令解析上位机发“S450”这样的字符串单片机解析后把目标转速改成450。这样就不用每次重新烧程序才能测阶跃响应了。这一步强烈推荐它能让你在几分钟内完成几十次阶跃实验调参效率完全不在一个档次。4.3 从波形里能读出什么信息波形调试阶段我总结了几条很实用的判读经验写在这里供参考一上电输出很快撞到限幅值并且来回撞首先怀疑反馈方向反了或者Kp已经大到离谱稳态时目标值和实际值之间始终有个固定偏差而输出不为零首先检查积分环节是否生效比如积分分离阈值设得太小导致积分一直没参与输出呈现等幅振荡说明正处在临界稳定状态这就是第5节找临界增益的依据输出高频抖动而转速曲线看起来比较平滑多半是微分项在放大编码器的量化噪声实际转速波形出现随机毛刺但输出没有对应变化基本可以判断是编码器信号受干扰先查供电共地。这些经验看起来是小事但每条背后都对应一次真实的排错过程。提前知道“波形长这样问题大概率在这”能帮你省下大量瞎猜的时间。5. 调参过程实录用临界比例法从0到稳定5.1 为什么先整定P再整定I最后才动DPID三个参数不能一上来一起调这是很多人最容易犯的错误。三个参数相互耦合一起改的话出了问题你根本不知道是哪个参数的问题。业界通用的思路是“先P后I再D”P决定基本响应速度先把P调到系统稳定且响应合格然后加I消除稳态误差最后按需加D抑制超调。这个顺序是有逻辑的因为P是系统稳定性的基础I和D都是在P的基础上做修正。我这次用的是经典的Ziegler-Nichols临界比例法它的思路也遵循这个顺序只保留比例项逐步增加Kp直到系统出现等幅振荡记录当时的临界增益Kcr和振荡周期Tcr然后套用经验公式得到一组初始参数。这组参数不一定是最好的但一定在合理的起点附近比瞎猜强太多。5.2 寻找临界振荡的实测数据实验条件固定为控制周期Ts20ms输出限幅±100对应PWM占空比百分比目标转速300 RPM。先把Ki和Kd全部置0从很小的Kp开始每次增加后观察阶跃响应波形。每改一次参数重启一次系统给一个目标阶跃记录波形和关键指标。实测的四组数据如下Kp响应行为关键现象0.5转速爬升慢最终停在270左右稳态误差约30 RPM1.0接近目标无振荡稳态误差约8 RPM1.8较快接近目标首次出现超调超调约8%2.4持续等幅振荡振幅约±40 RPM周期0.36秒当Kp到2.4时我让系统跑了20多秒振荡幅度始终保持不变既没有衰减的趋势也没有继续发散。这就是临界振荡状态。记录下临界增益Kcr2.4临界振荡周期Tcr0.36秒。这里有个细节值得说明为什么Kp1.0时还有稳态误差但没有继续往上调到误差为零因为纯比例控制天生无法完全消除稳态误差误差越小需要的输出增量越小的负载特性决定了这个误差无法归零。这是正常现象后面加I环节会解决。5.3 Ziegler-Nichols公式计算与参数纲量换算查到临界增益和临界周期后用Ziegler-Nichols的PID整定表计算控制器KpTiTdP0.5 × Kcr--PI0.45 × KcrTcr / 1.2-PID0.6 × Kcr0.5 × Tcr0.125 × Tcr套Kcr2.4、Tcr0.36秒进去Kp 0.6 × 2.4 1.44Ti 0.5 × 0.36 0.18秒则Ki_code Kp × Ts / Ti 1.44 × 0.02 / 0.18 ≈ 0.16Td 0.125 × 0.36 0.045秒则Kd_code Kp × Td / Ts 1.44 × 0.045 / 0.02 3.24如果你接触过工业仪表或者PLC的PID功能块可能会看到PB、TI、TD这组参数命名。这里顺便解释一下PB叫比例带单位是百分比和Kp的关系是PB 100 / Kp。算一下就知道这个实验里Kp1.44对应的PB约为69%。TI是积分时间单位秒TD是微分时间单位秒。不同的厂商标定方式不统一但换算关系就是上面这些公式搞清楚一次以后碰到任何品牌的控制器都不会懵。5.4 初始参数实测与手工细调把计算出来的Kp1.44、Ki0.16、Kd3.24烧进去目标300 RPM阶跃。实测波形很有代表性超调约28%来回振荡了两三个周期才稳定下来调节时间约0.8秒。这个结果完全符合Ziegler-Nichols公式的特性——它生成的参数本来就偏“激进”追求快速响应代价就是超调大。我在这个基础上做了四轮手工细调每一轮只改一个参数记录现象调整轮次参数变化现象第1轮Kp降到1.2超调降到15%左右但D项带来的输出抖动变明显第2轮Ki降到0.12Kd降到2.5超调约8%稳态误差1-2 RPM输出较平滑第3轮Kp升到1.5响应变快但超调回到15%退回第4轮Kd升到3.0阻尼增强但高频抖动加重退回最终确定的参数是Kp1.2、Ki0.12、Kd2.5积分分离阈值50 RPM。这个细调过程充分说明了一个道理Ziegler-Nichols给的是一个可靠的“起点”不是终点。实际系统中编码器量化噪声、PWM死区、电源波动这些因素都会限制参数的可用范围。尤其Kd理论上越大阻尼越强但代价是放大噪声最后必须在一个“够用”的位置停下来。5.5 阶跃响应与抗扰动验证参数定下来后按实验目标做最终验证。第一步是阶跃测试目标从300阶跃到450 RPM实测超调约5%0.4秒内进入稳态稳态误差在±2 RPM内。第二步是抗扰动测试系统稳定在300 RPM时用手捏住轮子施加阻力转速瞬间掉了大约40 RPM松手后0.8秒内回到目标值。四项指标全部达标。这次验证过程中我重点观察了输出波形。它全程没有撞限幅说明参数在合理的线性工作区扰动出现时输出快速补偿恢复后输出平稳回落到维持300 RPM所需的小幅值整条曲线没有毛刺这个状态就算合格了。6. 踩坑总结这次实验最值得记下来的五个问题6.1 编码器AB相接反闭环直接变成正反馈这是我在实验中最先踩的坑也最具迷惑性。现象是只要一启动PID电机就朝一个方向猛冲转速冲到限制保护才停下来回抖。我一度以为是Kp设太大但把Kp减到0.2还是同样疯狂。排查后发现问题很简单编码器的A、B两相线接反了。AB相反会导致定时器计数方向判断错误电机实际正转时计数器在减小。由于误差是“目标减实际”实际值变成负数后误差不减反增控制器输出的修正方向完全反了负反馈变成了正反馈系统当然飞掉。这种问题在波形上特别有辨识度——输出满幅、系统发散、不管怎么调参数都无济于事。修复办法有三个一是把A、B两根线物理对调二是在CubeMX里把通道极性反过来三是在软件里取计数的负值。推荐前两种硬件层面解决最省心。关键是上闭环之前一定要先开环验证编码器方向这是原则问题。6.2 输出限幅缺失导致的启动失控第二坑出现在我刚写位置式PID的时候。启动瞬间目标300、实际转速0比例项输出本来应该是1.2×300360超出限幅100多倍。如果代码里没有输出限幅PWM直接满占空比电机瞬间冲击电流很大电源电压被拉低甚至能把单片机复位。这个问题的本质是控制器输出超过了执行器的物理能力但执行器只能老老实实输出最大能力中间的差值如果由积分项持续累积就会形成更隐蔽的积分饱和。两个措施必须同时做输出限幅是底线积分限幅和积分分离是优化。增量式PID因为输出天然受限幅约束这个坑会自动弱化但位置式PID必须显式处理。6.3 串口阻塞打印打乱控制周期第三坑是调参过程中的“慢性病”。一开始我用HAL_UART_Transmit阻塞发送的方式往串口打印数据打印频率设为50Hz每个包大概60个字符。115200波特率下每个字节传输约87微秒60个字符就要5.2毫秒。这5.2毫秒挤在20毫秒的控制周期里直接占掉26%的时间而且期间控制循环被卡住PID计算没法按时执行。后果就是波形呈锯齿状实际转速响应看起来非常不稳定误导我怀疑是参数问题。后来换成DMA发送把数据丢给外设就让出CPU控制周期的抖动立刻从±3毫秒降到±0.2毫秒锯齿消失所有现象都能用参数解释了。这个坑给所有做嵌入式控制的人一个提醒实时控制任务里任何阻塞式IO都是敌人。6.4 电机和逻辑共用电源导致编码器毛刺第四坑发生在我图省事、让12V电源同时给电机和降压模块供电的时候。电机一启动电源电压波动直接反映到编码器信号上波形上实际转速出现随机毛刺PID输出跟着抖严重时直接单片机复位。原因不复杂电机堵转时电流很大电源内阻上压降骤增编码器的供电电压和信号电平被严重干扰。解决办法是动力电源和逻辑电源彻底分开两者只在参考地这一端相连编码器线尽量短条件允许时用屏蔽线定时器输入滤波等级设高一些吃掉窄毛刺。电源设计问题在低速小电机上不明显但一旦换大电机或者需要精密控制迟早要面对。6.5 控制周期Ts不能想当然乱设最后一坑是关于Ts选择的。刚开始我以为控制周期越快越好试过1毫秒。结果因为编码器在低速时每个周期只有几个计数值量化噪声被微分项放大输出高频抖动。反过来如果Ts设到100毫秒响应就变得迟钝阶跃之后好几秒才稳定。经验是Ts的选择要匹配传感器分辨率和被控对象响应速度。对于我这个13线编码器、减速比30的电机20毫秒是合理的折中低速时每个周期至少有十几个计数微分项不会放大到不可接受。另一个关键点是控制周期必须稳定最好用硬件定时器产生固定节拍来触发PID计算而不是在while(1)里靠HAL_Delay凑合后者的时间误差会导致整个调参过程失去意义。7. 实验02的遗留问题与串级PID的引入7.1 单环速度PID的边界在哪里实验02做完电机的转速控制算是稳了。但如果你稍微多想一步就会发现这套系统有一个天然的边界它只能控制转速不能控制位置。比如你想做一个两轮小车跑1米停住如果只用速度PID你会发现很难让车精确停在指定位置。原因很简单速度PID保证的是“当前转速等于目标转速”但这个目标转速本身是由谁决定的如果你直接给一个速度然后到了就清零车会因为惯性滑出一大截或者因为提前减速而走不满距离。这其实是做小车、机械臂时最经典的痛点——单环速度控制永远解决不了位置精度问题。7.2 串级PID的时间尺度分离解决位置控制问题的主流方案是串级PID外环是位置环内环是速度环内环的输出作为外环的目标。日常交流中大家也叫它“级联PID”。结构上就是这样的目标位置 → 位置环PID → 目标速度 → 速度环PID → PWM → 电机 → 编码器 → 实际速度反馈回速度环实际位置反馈回位置环很多人第一次听到“内外环”时会问为什么要绕一层直接做一个位置PID不行吗我的理解是内环速度环的职责是“快速对抗扰动”外环位置环的职责是“稳步收敛到目标”。如果只有单环位置PID扰动进入后会先影响位置位置环再来修正相当于扰动穿过整个控制链路而串级结构里扰动一出现在速度反馈里内环立刻压住根本来不及传到位置环。这就是为什么很多工程系统里内外环的采样时间要拉开差距内环跑得快比如10毫秒外环跑得慢比如50到100毫秒外环输出一个目标速度作为内环的参考整个系统既稳又准。这也是无人机、云台、小车转向这些场景都用串级PID的原因。7.3 给准备复现实验02的人三条建议最后说三条基于实际操作的建议都是这次实验里用真金白银换来的经验。第一条上闭环前一定先用开环验证编码器方向和转速换算这一步能省掉后面至少两小时的排错时间第二条第一次调参时把输出限幅设小一点比如±50宁可让系统响应慢一点也不能让参数爆炸把驱动烧了第三条每次改参数都要记录参数值和对应的波形截图没有记录的调参等于白调——你永远不会记得上一版参数到底好在哪、差在哪。实验02到这里就算完整收尾了。下一步我准备在同一个平台上把串级PID的位置控制跑通到时候继续记录实验笔记。这套从速度环到位置环的推进路线对做小车、机械臂、云台这些项目的人来说是一条值得完整走一遍的学习路径。
返回列表