PID控制实验笔记:速度环、位置环与级联控制的完整实现

发布时间:2026/9/17 17:06:02
PID控制实验笔记:速度环、位置环与级联控制的完整实现 说起PID控制我估计不少朋友都经历过那种参数调了三天电机还是嗡嗡乱叫的阶段。这一篇是我的PID控制实验笔记第二篇基于上次搭好的硬件平台这次把重点放在了速度环、位置环以及级联控制的完整实现上。文章里没有教科书式的大段推导全部是我在实验台上实际跑过的流程、测过的数据以及踩过之后最想提前告诉你的那些坑。先交代一下这篇笔记的适用范围如果你手里有一块STM32或者类似的MCU板子驱动器用的TB6612或DRV8833电机是带霍尔编码器的直流减速电机那么这篇笔记的核心内容你几乎可以对照着直接复现。如果你用的是加热棒、云台舵机这类被控对象控制原理一样只是执行器和反馈量不同文末我会补充几句这部分的经验。1. 实验平台和信号链路决定你能调出什么参数的地基做PID控制实验第一步不是写PID函数而是把测量—控制—执行这条链路搞清楚。我这次用的是STM32F103C8T6最小系统板电机驱动模块是TB6612FNG电机选择了带霍尔编码器的JGA25-370减速电机减速比130编码器线数13线/相经过4倍频后每转脉冲数是13 * 30 * 4 1560个。这套组合在速度和位置控制里都很典型属于能跑明白参数也够看的配置。1.1 编码器信号怎么接才不出乱子霍尔编码器输出的是两路相位差90度的方波信号分别叫A相和B相。接线的关键点有两处编码器信号线要接在MCU支持正交解码的定时器通道上。比如STM32的TIM1、TIM2、TIM3、TIM4都有编码器模式我这边用的是TIM2的CH1和CH2对应PA0和PA5具体引脚请以你的核心板原理图为准。编码器供电电压通常接3.3V或者5V但信号电平可能不同。我手上这个JGA25-370是5V供电输出高电平接近5V直接进STM32的IO口是有风险的。稳妥做法是串一个1kΩ电阻再用3.3V稳压管或二极管钳位。如果手头没有这些也可以试一下内部上拉但信号边沿会变差高速转动时计数容易丢。量一下编码器的供电和输出电压再决定要不要加电平转换这步省不得。我在第一次实验里图省事直接接结果上电一分钟编码器计数就开始乱跳查了半天才意识到是电平不匹配。1.2 驱动器和电机之间的地一定要共TB6612的VM接电机电源VCC接3.3V或者5V逻辑电源GND必须和MCU的GND接在一起。这个共地问题看起来基础但实验中最容易犯的错就是MCU用USB供电驱动器用独立电源供电两个电源之间没共地导致PWM信号电平没有一个统一的参考点电机要么不转要么转速忽快忽慢。另外驱动器的STBY引脚要拉高否则芯片一直处于待机状态。这一点在检查为什么电机不动的时候经常被忽略——程序烧进去没问题PWM也输出了可电机就是纹丝不动查到最后发现STBY悬空。补上这个引脚之后一切恢复正常。1.3 PWM频率选择能稳住电流是前提TB6612和DRV8833这类芯片的PWM频率不是随便选的。频率太低电枢电流纹波大电机低速时会一顿一顿频率太高驱动芯片的开关损耗急剧增加芯片烫手甚至触发保护。我的经验是10kHz到20kHz之间最稳妥。这次实验我选了10kHz既能躲开人耳可闻的噪声又能保证电流纹波在一个可以接受的范围内。如果你用的是带减速箱的电机你还会在某个转速附近听到齿轮啮合的声音和PWM噪声叠加的效果这个不是故障但和PID参数调差时的啸叫是两回事不要混为一谈。2. 控制周期和测速速度值是整个PID的眼睛PID调节的前提是你得知道当前转速。对于带霍尔编码器的直流电机测速的底层逻辑是在一个固定的时间窗口里统计编码器正交解码后累加的脉冲数再换算成转/秒或者转/分。这块看起来不复杂但固定时间窗口的选取直接决定了速度反馈的质量。我在实验中使用的是STM32的编码器模式。初始化之后定时器的CNT寄存器会自动根据A、B相的相位关系加减计数CPU只需要周期性读取CNT的值并清零就能拿到脉冲增量。这个过程不需要外部中断也不会漏脉冲是工程上最常用的方案。具体配置代码如下基于STM32标准外设库如果你用HAL库逻辑是等价的void Encoder_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period 0xFFFF; TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); TIM_ICInitStructure.TIM_Channel TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 0x0F; TIM_ICInit(TIM2, TIM_ICInitStructure); TIM_ICInitStructure.TIM_Channel TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 0x0F; TIM_ICInit(TIM2, TIM_ICInitStructure); TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); TIM_SetCounter(TIM2, 0); TIM_Cmd(TIM2, ENABLE); }2.1 M法测速和速度环控制周期怎么配标准库里面TIM_EncoderMode_TI12代表1倍频还是4倍频取决于怎么看——实际上配置后会在两个通道的边沿都计数所以是4倍频效果。速度计算公式是speed (delta_cnt * 60) / (PPR * 4 * T)这里的PPR是编码器线数134倍频后每圈脉冲是13 * 30 * 4 1560个。T是测速周期我设置为5ms也就是0.005秒。那么如果每秒需要让电机转到300转/分5转/秒increment 1560 * 5 * 0.005 39个脉冲。这意味着什么呢如果速度环控制周期只有1ms那么每个周期里只能数到大约7~8个脉冲离散化误差非常大你会看到速度反馈值在±15%的范围内疯狂波动PID自然无法稳住。所以在速度环配置里控制周期宁长勿短5ms到10ms是霍尔编码器电机的常见区间。我最终选了5ms的测速和控制周期。每次进入定时器中断读取TIM2-CNT把这个值清零然后送入PID运算。清零操作是必要的不然累加的脉冲数一直涨速度算出来就永远是正数。2.2 速度换算与误差保护测速代码虽然简单但在实际实验中你要随时注意编码器计数方向。如果发现电机正转时速度为负可以交换A、B相接线或者在代码里取反。千万不要在PID运算里用符号来做方向切换那样很容易在零点附近产生死区。另外一个很容易被忽略的问题是编码器断线。霍尔编码器的线缆在反复弯折后内部容易断一旦信号断了CNT会停在某个值不动速度反馈瞬间变成0。这时候如果PID还在运行积分项会疯狂累积等到线又接触上的瞬间电机会突然猛转一下。这个现象我后面专门排查了好一阵子。为了避免烧东西我在代码里加了一个速度有效判断如果连续50ms读取到的编码器脉冲增量都为0且PWM输出不为0就认为反馈异常直接封锁输出。3. 位置式PID和速度环整定先解决转得稳再解决停得准讲完了测速接下来是最核心的部分——PID调节器的实现。为了给后面的级联控制打基础我这一版用了位置式PID输出直接是PWM占空比。位置式PID的公式是output Kp * error Ki * integral Kd * derivative其中error是目标速度与当前速度之差integral是误差累积值derivative是误差变化率。和增量式PID相比位置式的积分项需要额外处理上限。为什么因为如果电机堵转或者反馈出错误差持续存在积分项会一直增大到几十万而PWM占空比上限只有1000对应10kHz高电平时间。等故障解除后积分项还停留在高位电机会以一个非常高的初始占空比冲出去这就是积分饱和。为了防止这个问题我做了两个处理积分项限幅。我用的是一个经验值积分输出不允许超过控制量上限的30%。积分分离。当误差超过某个阈值比如目标速度的20%时直接不累加积分先靠比例把误差拉回来等误差变小再恢复积分作用。3.1 手动整定流程从比例到积分的顺序不能乱速度环整定我习惯从纯比例开始把Ki和Kd先设为0。这时候给一个目标速度观察电机的阶跃响应。Kp从0慢慢往上加如果电机转速逼近目标速度且响应速度尚可但存在静差说明Kp还有余量继续加。这个阶段很容易卡在再加上去就开始振荡的位置这时候记下临界比例增益。引入Ki。速度环的积分作用主要用来消除静差和抵抗负载扰动。Ki从小往大调直到稳态误差消失。需要注意的是积分作用加的太大会让系统低频段增益过高起步时超调量明显增大。最后才考虑Kd。在实际的电机速度环中微分项对编码器噪声特别敏感。因为霍尔编码器在低速时脉冲间隔较长测到的速度值本身就带锯齿状波动对这样的信号求导会被放大。所以速度环的Kd我通常设置的非常小甚至是0。如果系统能在纯PI下稳定就不必强求D项。这里有一个我实际测过的例子。目标速度设定300转/分经过逐步调整后我得到一组参数Kp 1.8Ki 0.15Kd 0.02。在这组参数下电机从静止到300转/分大概用了180ms超调量不到5%稳态误差在±5转/分以内。如果你手头的电机跟我不同参数值不具备直接移植性但整定的规律是一样的Kp决定响应速度Ki决定稳态精度Kd负责压超调但容易引噪声。3.2 阶跃响应的判断方法在调参的时候我的习惯是给一个方波目标速度比如让电机以200转/分运行2秒然后突变为300转/分运行2秒再跳回200转/分。这样可以在屏幕上看到上升时间、超调量、静差和扰动恢复情况。如果电机在目标速度切换时发出一声尖锐的啸叫然后转速过冲再回落说明Kp偏大或者微分项没有起到抑制作用。如果电机转速平稳但到达目标速度特别慢说明Kp太小。如果转速始终比目标低一点点比如目标是300转/分但实际只有285转/分这就是典型的静差你需要加Ki而不是继续加Kp。这套判断方法几乎适用于所有直流电机速度环实验无论你用的电机型号是什么先观察现象再动手改参数比盲目套用教程参数有效得多。4. 位置环与级联PID为什么内环和外环的快慢必须分开调好了速度环位置控制就变成了上层问题。单靠位置环直接输出PWM也能让电机转到一个指定角度但实验做下来你会发现两个问题一是到位后电机可能停不住有回差二是受负载扰动时位置恢复得特别慢。所以工程上几乎都会采用级联PID——外环是位置环输出的是目标速度内环是速度环负责让电机精确达到这个目标速度。这就是热搜词里反复出现的级联PID控制的实际应用场景。4.1 级联PID的物理意义你可以把外环想象成一位调度员它每隔一段时间看看当前位置和目标的差距然后丢给内环一个速度指令你现在需要以每秒多少转的速度往下走。内环这位执行者接到指令后通过调节PWM让实际转速贴合这个指令。为什么这样设计比单环更合理因为速度环已经在内部处理了电机惯性、摩擦力、死区等非线性因素。外环面对的是一个被净化过的被控对象位置控制会稳健得多。举个直观对比我单独用位置环直接输出PWM时让电机转到指定角度超调达到15度改成级联结构后同样条件下超调缩小到3度以内。4.2 内快外慢控制周期必须错开级联结构最核心的配置原则是内环控制周期必须明显短于外环。我的做法是速度环5ms一个周期。位置环20ms一个周期。为什么要这样隔开因为外环的每一个输出值目标速度要送到内环去执行如果外环太快位置误差的微小波动会直接变成内环的目标速度突变导致电机频繁加减速整个系统会瑟瑟发抖。反过来如果外环太慢位置误差的纠正就会迟钝表现为到位慢、受扰动后恢复时间长。在实际实验中我先让位置环以20ms进入PID运算每4个速度环周期才更新一次位置环输出。这样整个级联系统的时间基准是清晰的调参时也更容易定位问题。如果是纯软件仿真这个时间尺度也许没那么敏感但物理电机的机械时间常数摆在那里5ms和20ms的搭配是经过验证的合理起点。4.3 位置环的PID参数整定外环整定方式和速度环不同。位置环的比例项可以直接取大一些因为速度环已经起到了阻尼作用。但位置环的积分项要小心位置环积分是在纠正位置静差通常是因为齿轮间隙或者负载力矩导致目标位置无法完全到达积分太强会引起低频振荡表现为电机在目标位置附近来回蠕动。微分项在位置环中的作用是预测位置误差的变化趋势相当于给系统增加阻尼对抑制超调很有帮助。我实验中得到的一组位置环参考参数是Kp 4.5、Ki 0.05、Kd 0.5。从起点位置阶跃旋转90度电机在大约300ms内完成到位超调在2度以内稳态误差低于0.5度。这组参数不是为了照搬而是要让你感受一下位置环和速度环参数之间的数量级差异——位置环Kp可以比速度环大不少Ki则通常远小于速度环。4.4 级联结构的控制周期分配代码我用一个5ms定时器作为时基用软件计数器分频出20ms位置环周期。示例逻辑如下void TIM_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update); speed_measured Speed_Calculate(); speed_output Speed_PID(speed_target); position_tick; if (position_tick 4) { position_tick 0; speed_target Position_PID(position_setpoint, position_measured); } PWM_Update(speed_output); } }注意位置环的输出是速度环的目标值而非直接是PWM。这一步务必要在代码注释里写清楚否则过几天回看这段逻辑很容易把自己绕晕。5. 加热棒等效控制同一套PID换个对象也能用实验做完电机速度位置控制之后我还顺手验证了一下PID在加热棒温度控制上的应用。这个打算也不是临时起意是想验证一下PID参数会随对象不同而变化但整定方法论是通用的这条经验。很多新手会困惑一个问题我能调好电机但换成加热棒怎么原来的参数就不行了5.1 温度对象的大惯性本质加热棒的温度控制是一个典型的大惯性、大延迟系统。热电偶或者NTC热敏电阻采集到的温度变化相比PWM加热动作有显著的滞后。严格说这是一阶惯性加纯延迟对象远没有电机速度那么跟手。如果直接把速度环调好的PID参数搬过来最明显的现象是温度过冲非常大甚至会出现温度在目标值上下长期振荡、始终稳不下来的情况。我这次实验用的是一根24V/150W的加热棒温度传感器是PT100采样周期200ms。手动整定后的参数是Kp 25、Ki 0.8、Kd 60在60度目标温度下过冲在2度以内稳态波动±0.3度。如果把速度环的Kp 1.8这类参数拿过来加热功率会长期在满功率和零之间剧烈切换系统的行为接近开关控制而不是PID控制了。5.2 温度控制里D项的正确用法在温度控制中微分项的作用是提前感知温度的上升趋势在温度还没到目标值之前就开始回调加热功率。但温度采样值本身有噪声直接对原始采样值求微分结果会在正负之间来回跳。我的做法是先对温度做一阶低通滤波再进行微分运算。低通滤波系数取0.2左右也就是新值占20%旧值占80%太强的滤波会让温度反馈变钝反而出现滞后加大。给加热棒控制做一次伪阶跃测试的方法也分享一下你不需要真的让温度从室温跳到100度只需要在60度的稳态下把目标改成65度记录整个温度上升曲线然后从这条曲线上观察超调量、上升时间和稳态波动。这个方法对PID整定来说足够有效而且比每次都做全温程阶跃安全得多加热棒的余热效应在降温段的影响也可以避开。5.3 升温快、降温慢的处理技巧温度控制有个电机控制里很少遇到的现象升温由加热棒主动执行降温只能靠环境自然冷却两边的过程不对称。这就导致一个很实际的问题——如果目标温度从较高的值往下降PID会发现负误差持续存在积分项不断向负方向累积虽然加热棒已经没有输出但降温速度依然很慢。这时候积分项累积是无能为力的它不能让环境更快散热。针对这个情况我给温度PID加了一个双向积分限幅正向积分和负向积分的限幅值不同。正向需要升温的限幅可以大一点保证升温快速负向需要降温的限幅设得很小防止降温阶段积分项长时间饱和导致后续升温响应迟钝。这个技巧在保温控温类项目里非常实用你可以把它记下来。6. 实验中的数据记录曲线与现象回放这组数据告诉你系统到底稳不稳这一节我放几张文字版的曲线和数据片段用来复盘整个调整过程。模型里的目标速度是300转/分位置目标90度。以下是几个关键节点的记录阶段现象描述参数状态结论纯比例电机响应较快但速度稳定在270~280转/分之间有静差Kp1.8, Ki0, Kd0比例够用缺积分比例积分静差消除但启动时超调8%左右Kp1.8, Ki0.15, Kd0需要微分压超调完整PID超调约4%300ms内稳定Kp1.8, Ki0.15, Kd0.02参数可接受位置环验证90度阶跃超调1.5度约300ms到位级联结构启用级联方案有效曲线的形状就不过多描述了不同示波器或串口上位机的显示方式不同但它的共同特点是加入Ki后稳态线变平不再往下掉加入Kd后峰值变圆滑不再尖锐。6.1 串口上位机怎么记录数据最高效调试PID离不开数据记录。我的习惯是MCU通过串口输出时间戳、目标值、反馈值、输出占空比四列数据波特率115200每5ms输出一帧。一次记录3秒大约600帧放进Excel里画图就能看到非常清晰的阶跃响应曲线。这里有一个格式细节不要在字符串里混用多种分隔符统一用逗号或者制表符。输出时优先使用整数或者保留一到两位小数。否则Excel打开时列对齐会乱分析效率会低很多。另外建议加上一个简单的帧头比如DATA,当串口数据量大出现粘包、断包时可以通过帧头来剔除异常帧避免污染分析数据。6.2 电源纹波对曲线的污染有一次我在数据曲线里看到150Hz左右的高频波动这个频率跟电机转速对不上也跟PID控制周期对不上。排查了很久最后用示波器看电机电源发现是可调稳压电源的输出纹波在负载波动下明显增大再叠加TB6612续流时的尖峰MCU的ADC基准参考都被干扰了。虽然速度反馈来自编码器不受ADC影响但电源噪声会通过驱动器和电机之间的地线影响编码器信号。解决方式是给驱动器电源和逻辑电源之间加了磁珠和100µF电解电容并在每个编码器信号线上串联1kΩ电阻。处理之后曲线干净多了。所以如果你在实验中发现速度和位置反馈曲线有莫名其妙的周期性波动先别急着改PID参数先从电源和信号完整性入手。这部分经验换来的排查时间远超你想象。7. 不同控制变量的参数差异总结不要把电机经验直接搬给加热棒我这次实验同时涉及了电机速度、位置和加热棒温度三个对象是很好的横向对比素材。先看这个汇总表项目电机速度环电机位置环加热棒温度环采样控制周期5ms20ms200msKp范围1~33~620~40Ki范围0.05~0.30.01~0.10.5~2Kd范围0~0.050.3~0.840~80主要风险噪声放大积分振荡余热过冲从这个表里能看出几条规律控制周期越长的对象Kp、Ki、Kd的绝对数值往往越大。这不是因为系统变得更灵敏而是因为每个控制周期内对象的响应程度相对更小需要更大的增益来驱动。你拿温度环的Kp 25去驱动电机速度环电机会立刻振荡到飞起来。每个参数都和自己的对象绑定跨对象移植PID参数没有意义。另外噪声敏感度差异也很大。电机速度环里编码器脉冲间隔带来的测量噪声比加热棒温度采样的噪声更明显所以速度环的D项非常保守。而加热棒温度环的D项之所以能设得很大是因为它的采样周期足够长一阶低通滤波后微分值不会剧烈跳动。8. 整理几个必定会用到的经验清单实验做到这里除了参数整定之外工程细节的经验也非常重要。我踩过的坑比较多这里挑几个对新手最有杀伤力的点列一下。8.1 上电瞬间的PWM默认电平很多MCU的GPIO在复位瞬间是高阻态或者弱上拉状态如果在初始化完成之前驱动器就已经上电电机可能会在上电瞬间猛转一下。这不是PID的问题是GPIO默认电平的问题。我的处理办法是在main函数最开始就把PWMA和PWMB引脚初始化为推挽输出低电平再初始化驱动器STBY引脚为低电平先把输出封锁住。等所有外设初始化完成PID进入正常运行态之后再把STBY拉高使能驱动。8.2 位置环的软限位比硬限位重要如果你做位置控制实验的目标是让电机转到一个角度然后停住建议在代码里加一个软限位当位置环输出目标速度超过某个阈值时直接饱和到阈值而不是让级联系统无限加码。原因在于当位置偏差很大时比如手动把电机拨离目标位置位置环输出的目标速度可能达到几千转/分钟而减速电机本身可能根本达不到这个速度。速度环会持续满占空比输出电流增大驱动器发热加剧。长此以往电机寿命受损不说还容易烧驱动。8.3 空载和带载的参数必须区分我实验时先空载整定然后给电机轴加了一个塑料桨叶作为负载轻载。负载变化后系统增益特性发生了变化原参数下速度环出现了轻微振荡。解决方法是把Kp小幅降低到1.6Ki从0.15微调到0.12系统重新稳定。这说明PID参数是对象相关的如果你的机器人或者装置在实际负载下运行一定要在真实负载下重新整定一次不能只依赖空载数据。8.4 控制代码的版本管理最后一条偏工程习惯每次改PID参数后我建议把参数写入一个单独的配置头文件而不是直接在控制算法里改数值。然后在串口输出中附带当前的参数标识符。这样当你在好几组参数间来回切换时不会因为忘记当前是这组还是那组而浪费一整个下午。我用的做法是每组参数编一个编号比如PARAM_SET_5输出到串口数据曲线分析时可以以此作为分组的依据。这个习惯帮我省下了大量的对比时间。我在整个实验里最大的体会是PID控制并不神秘它的难点在于把采样、执行、反馈每个环节都做到可靠然后再谈参数收敛。如果测量链路里有一环是虚的再好的整定手法也无法让系统稳定。反过来只要你肯花时间先把数据测准剩下的整定过程其实是有章可循的。这篇实验笔记的记录中凡是能直接复现的步骤我都尽量写清了希望在你自己搭的平台上也能顺利跑出好看的阶跃曲线。