别人调参数调的是PID,我这次在P2阶段硬是把微分项给砍了,只留了P和I。一开始组里还有人质疑,说转速闭环不上D项,动态响应能看吗?结果实测数据出来,超调量、调节时间、稳态误差三项指标全在要求范围内,而且全程没被高频噪声折腾。这篇就把我这次做PI闭环控制的完整思路、实现过程和踩坑记录写下来,特别是为什么在噪声敏感的场合,PI反而比PID更合适。
1. 为什么这个阶段我只用PI,不加微分项
1.1 从开环到闭环,第一个拦路虎是静差
说句实话,如果只是让电机转起来,开环给个占空比就够了,根本用不上闭环。但P2这个项目的要求不是“转起来”,而是“给定转速和目标转速的误差维持在允许范围内”。这就必须要引入反馈,而反馈控制里最先遇到、也最容易理解的问题就是静差。
纯比例控制,也就是只有Kp的那一项,它的作用是“误差越大,输出修正越猛”。但你稍微推一下就会发现,纯P控制天生就留了一个尾巴:系统稳定后,控制器必须持续输出一个不为零的控制量来对抗负载、摩擦力这些扰动,可只要输出不为零,比例项就要求误差不为零,这个误差就是静差。静差大不大,取决于你Kp给的有多高,但Kp又不能无限加,加高了系统就开始振荡,电机转速会在目标值附近来回摆。
所以PI控制器里的积分项,本质上就是干一件事:把过去所有误差累积起来,用“历史欠账”来补足纯比例控制怎么都消不掉的那一点稳态偏差。只要误差长期存在,积分项的输出就会一直增大,直到控制量恰好抵消扰动,误差收敛到零。用大白话说,P负责“当下反应多快”,I负责“最终偏差能不能归零”。
1.2 微分项在数字控制里容易被噪声放大
很多教材在讲PID的时候,都会强调微分项可以预测误差趋势、抑制超调、加快响应。理论上没问题,但到了实际电路里,D项往往是第一个给你惹麻烦的。
P2这个项目的电流采样用的是一块分辨率有限、采样噪声偏大的板子,转速信号本身也有量化抖动。微分项算的是误差的变化率,也就是相邻两次误差的差值除以时间。本来微小的随机噪声,求一次差分之后幅度会被放大,再乘以Kd,最终叠加到输出上的就是一个剧烈波动的控制量。你会发现电机转速曲线看着挺平稳,但PWM占空比在疯狂跳动,驱动器发热也明显增加。
我测试的时候试过一次带D项的参数组合,比例不动,只加一点点微分,示波器上看输出波形,原本干净的控制量曲线直接变成毛刺状。这种毛刺不会让系统立刻崩溃,但它会显著加速功率器件的老化,还会在机械结构上引入高频振动噪音。既然P2的重点是先跑通转速闭环、摸清系统特性,我决定这一版干脆不上微分项,用PI先把稳态性能和基本动态性能做扎实。
1.3 PI的适用范围和边界条件
PI不是万能的,尤其对于纯滞后比较大的对象,比如长传输管道、大惯量负载、或者存在明显纯延时的数字通信链路,PI的积分项可能会因为“误差一直在,输出一直加”而导致严重的振荡甚至发散。P2这个项目控制的是电机转速,属于典型的二阶惯性对象,时间常数适中,滞后不明显,PI是非常成熟的经典方案。如果是温度控制那种大滞后对象,光靠PI整定会非常痛苦,那时候就得考虑Smith预估器或者更高级的算法了。
所以我理解PI闭环控制的定位是:适用于被控对象有一定惯性、但纯滞后不严重的场合,它用最小的计算开销解决稳态精度和基本动态响应问题。工程上不追求算法炫技,能稳定、可靠、可维护地满足指标,这才是关键。
2. 从传递函数到MCU中断:把PI算法真正写成能跑的代码
2.1 连续域里的PI长什么样
我们在纸面上讨论PI控制器,常用的形式是:
u(t) = Kp * e(t) + (Kp / Ti) * ∫ e(t) dt
这里的两个核心参数,一个是Kp,叫比例增益,另一个是Ti,叫积分时间常数。还有一个写法是:
u(t) = Kp * e(t) + Ki * ∫ e(t) dt
其中 Ki = Kp / Ti。这两种写法其实是等价的,但工程习惯上我更推荐用Kp和Ti来记录参数,因为Ti的量纲是时间,物理意义更直观——积分项“追上”纯比例项的修正效果需要多长时间。Ti越小,积分作用越强;Ti越大,积分作用越弱。
我这次在项目记录里用的是Kp + Ki的形式,因为代码里直接就是两个系数相乘累加,少一步除法运算,MCU主频不高的场景下能省一点算力。但调试笔记里我同时标注了对应的Ti值,方便后续跟理论模型对照。
2.2 离散化:位置式还是增量式
控制器最终要跑在数字芯片里,必须把连续的积分换成离散的累加。假设采样周期为T,第k个采样时刻的误差为e[k],那么离散化后:
积分项 ≈ Σ e[i] * T(对所有历史采样点求和)
位置式PI的输出:
u[k] = Kp * e[k] + Ki * Σ(e[i]) * T
这种算法实现非常简单,但有一个工程隐患:它输出的直接是完整控制量,一旦积分项把总和累得过大,输出就可能超过执行机构的物理限幅。而且位置式算法必须把历史误差全部累加在同一个变量里,如果前期误差一直很大,后来误差变小了,积分项要花很长时间才能“消化”掉那些堆积的旧账。
增量式PI的思路则完全不同,它不输出绝对控制量,而是输出“本次控制量相比上次增加的量”:
Δu[k] = Kp * (e[k] - e[k-1]) + Ki * T * e[k]
执行机构每次拿到的是增量,再在自己的变量里做累加。这样做有几个天然好处:第一,即使某次计算或者通讯出错,单次增量误差远小于绝对量误差,系统不容易直接被带飞;第二,从位置式切换到增量式时,不需要重新初始化历史误差,过渡非常平滑;第三,执行机构自带积分作用,控制器的积分项和机构的累加天然解耦。
我在P2里选择的是增量式PI。电机驱动系统有个特性,PWM占空比的绝对值不是最关键的,关键是相对上一周期的变化量。增量式天然适合这种“步进调节”的执行方式,就算采样周期偶尔抖动一下,输出也不会出现跳变。
2.3 采样周期T的选择原则和我的实测取值
采样周期T是PI控制器落地的第一个关键参数。T选得太大,控制器来不及响应快速变化,系统容易出现极限环振荡;T选得太小,CPU开销增大,而且误差差分会被量化噪声淹没,Kp稍高一点就抖。
工程上有两条经验路径。如果是纯软件实现的控制器,T通常取系统机械时间常数的1/10到1/20;如果是基于硬件中断的采样,T要跟编码器或电流采样的更新周期对齐。P2用的电机额定转速2000rpm,机械时间常数在50ms量级,我最后把速度环采样周期定在5ms,也就是200Hz的更新频率。这个值既避开了采样噪声的频段,又足够覆盖机械响应的带宽,实测下来毛刺很少,响应速度也够。
代码里我用一个定时器中断来触发采样和控制计算,中断周期就是5ms。这里提醒一个细节:中断服务函数里应该只做轻量级的计算和变量更新,不要把串口打印、浮点运算、显示刷新这类耗时操作放进去,否则中断执行时间超过采样周期,整个控制时序就乱了。
下面是P2里速度环增量式PI的核心代码片段,我用的是定点数,把浮点运算转成Q格式整数运算,让主频72MHz的MCU跑起来毫无压力:
typedef struct { int32_t kp_q12; // Kp, Q12格式定点数 int32_t ki_q12; // Ki, Q12格式定点数 int32_t err_last; // 上一次误差 int32_t out_inc; // 输出增量 int32_t out; // 实际输出控制量 } PI_Inc_T; void PI_Inc_Step(PI_Inc_T *pi, int32_t target, int32_t feedback) { int32_t err = target - feedback; // 增量式: du = Kp * (err - err_last) + Ki * T * err pi->out_inc = (pi->kp_q12 * (err - pi->err_last) >> 12) + (pi->ki_q12 * err >> 12); pi->err_last = err; pi->out += pi->out_inc; // 输出限幅 if (pi->out > PWM_PERIOD_MAX) pi->out = PWM_PERIOD_MAX; if (pi->out < 0) pi->out = 0; }这个结构体以后要扩展抗积分饱和逻辑,直接在PI_Inc_Step内部加判断就行,不用改动外部调用。实际跑起来,一次步进计算的执行时间不超过10微秒,对中断压力极小。
3. 参数整定不用背公式:我围着Kp和Ti转了两天的实战路线
3.1 我不想一上来就背整定公式
很多教材喜欢直接扔出齐格勒-尼古尔斯整定公式,告诉你先让系统振荡、测临界增益和临界周期、再套公式算出Kp和Ti。这个方法理论上没毛病,但放在P2的硬件上有个现实问题:让系统进入等幅振荡需要把Kp调到临界值,这个过程中电流冲击很大,有几次我甚至闻到了一点糊味。对于有硬件成本、不希望反复折腾控制对象本身的场景,我更推荐直接从响应波形出发的“手动逼近”路线,边看响应边调,先建立对参数的直觉。
3.2 我的调试步骤:先P后I,再联合微调
第一步,只用比例项。把Ki设为0,Kp从一个很小的值开始,比如0.5,然后给一个200rpm的阶跃目标,看转速响应曲线。这时候你会看到两种典型情况:超调大且振荡不断,说明Kp太大;响应慢、到达目标值的爬升过程很长,说明Kp太小。我的做法是每次Kp增加20%左右,每次调整后给系统1到2秒的稳定时间再记录曲线。
盯着波形迭代了大约十轮之后,我找到了一个Kp值让系统正好处于“轻度欠阻尼”的状态,也就是转速第一次冲到目标值上方3%-5%,然后很快回到目标值附近。有些教程要求把系统调到临界阻尼,但工程上轻度欠阻尼反而更常见,因为机械系统本身有一点阻尼,轻超调可以显著缩短上升时间。
第二步,加入积分项。固定刚才找到的Kp,把Ti从大往小调。这里有一个很多人反着来的误区:不是一开始就调成很小让积分作用很强,而是从大到小慢慢加强。我初始设Ti=1000ms,这时候转速曲线底部的稳态误差几乎没有改善,因为积分作用太弱了。然后每次把Ti减半,500ms、250ms、125ms,观察稳态误差的收敛速度。
有一个很明显的信号能说明积分作用合适了:转速进入稳态后,误差曲线不再有那个顽固的“平台期”,而是缓慢而稳定地趋于零。继续把Ti往下压,比如压到30ms,你会发现转速开始出现低频周期波动,这就是积分作用过强,系统进入极限环振荡的前兆。遇到这种情况,把Ti往回退一档,多留30%的安全余量。
第三步,联合微调。加好积分项之后,系统的阻尼特性会变,原来Kp合适的点会变得偏过阻尼还是欠阻尼,需要在Kp和Ti之间做两三次交叉微调。我的做法是固定一个感受基准,就是阶跃响应曲线的“第一波峰值”和“回归目标值的用时”。如果第一波峰值超过8%,我就降低Kp;如果回归目标值的用时占整个过渡过程的比例太大,我就稍微加大一点Ti(也就是减弱积分作用)。
最终P2的速度环参数落定在Kp=18.5,Ti=120ms(等效Ki≈154)。阶跃响应数据为超调量约4.5%,调节时间约220ms,稳态误差±2rpm以内,在200rpm的参考下大约是1%精度,满足项目要求。
3.3 参数整定的速查对照表
我把这次调试过程中出现过的典型现象和对应的调整方向整理成了下面这张表格,以后接手类似项目的同事可以直接拿来对照:
| 响应现象 | 原因分析 | 调整方向 |
|---|---|---|
| 转速到达目标值极慢,像“爬不动” | 比例增益不足 | 增大Kp |
| 轻微超调后快速稳定,稳态无静差 | 理想欠阻尼状态 | 保持 |
| 超调明显、来回振荡几次才稳定 | 比例增益偏大或积分偏强 | 先减Kp,再增大Ti |
| 持续等幅振荡、不衰减 | 增益过大或采样周期不合理 | 大幅减Kp,检查T是否过小 |
| 稳态存在固定偏差、长时间消不掉 | 积分作用太弱 | 减小Ti |
| 转速出现低频周期漂浮 | 积分作用过强 | 增大Ti |
| 波形毛刺多、高频抖动 | 测量噪声污染或Kp过高 | 先查采样滤波,再减Kp |
这张表的价值不在于替代理论,而是帮助你快速定位问题区间,减少盲目的参数试错。
3.4 用“扰动法”验证鲁棒性,而不是只看阶跃响应
阶跃响应只能说明系统能跟上指令变化,但实际运行时更多时候需要对抗的是外部扰动,比如负载突然增加、供电电压波动。这里我推荐一个很实用的测试方法:让系统稳定运行在某个转速上,然后手动给电机的输出轴施加一个短暂的阻力,比如用手轻捏一下联轴器(注意安全,转速别太高),同时记录转速恢复过程。PI控制器对扰动的恢复速度,最能体现参数到底合不合适。
P2的实测效果是,在500rpm稳定运行状态用手轻碰一下转轴,转速瞬时掉到420rpm左右,随后在150ms内回到目标值附近,恢复过程的超调控制在4%以内。这个数据说明比例项对瞬时扰动的抵抗能力足够,积分项又能以合理速度“补偿”残余偏差。如果恢复过程又慢又拖泥带水,那就是积分作用太懒了。
4. 实测中踩过的三个大坑:积分饱和、启动冲击和噪声抖动的完整排查链路
4.1 积分饱和:超调的隐形推手,尤其在大给定阶跃时
第一个坑发生在从200rpm直接切到1500rpm的时候。起初我用的还是从低速阶跃调试收敛的同一套参数,但转速曲线直接来了一个25%的大超调,然后花了很长时间才跌回目标值。问题不在Kp上,而是积分项“堆账”了。
积分饱和出现的机制是这样的:1500rpm阶跃导致的初始误差高达1300rpm,PI控制器一上来就疯狂增大积分,几毫秒内输出就冲到了PWM限幅值。实际上系统加速需要时间,误差不会瞬间消失,误差大→积分继续增大→输出长期饱和,这个状态持续了整整一截加速过程。等到转速终于接近目标值,误差开始变小时,积分项里已经积攒了一个巨大的“正数欠账”,哪怕误差已经变成了零,这个欠账仍然让输出维持在高位,结果就是转速冲过头。之后积分项才开始慢慢“还债”,转速才回落。
排查链路是这样的:先看输出曲线,发现PWM占空比很早就顶到了100%并且维持了很长时间;再看误差曲线,转速达到目标值附近时误差已经开始收窄了,但输出仍然不被松开;最后定位到积分累加器里积攒的数值远大于稳态需要的数值。这不是某个参数固定的问题,而是算法结构缺少了对积分饱和的约束。
解决这个问题我用了两个手段。第一是限幅积分累加值,把积分项的输出限制在一个比执行机构物理限幅更小的范围内,这样即使积分饱和发生,输出也不会卡死在最高位太久。第二是条件积分(也叫条件抑制积分),判断只有当前输出没达到执行机构限幅时才允许积分继续累加,输出在限幅饱和状态时积分就暂停。这两个手段叠在一起,1500rpm阶跃的超调量直接降到了7%以内。
代码里加上抗积分饱和逻辑之后的样子:
void PI_Inc_Step_AntiWindup(PI_Inc_T *pi, int32_t target, int32_t feedback) { int32_t err = target - feedback; int32_t saturated = (pi->out >= PWM_PERIOD_MAX)? 1 : (pi->out <= 0)? -1 : 0; if (saturated == 0) { // 未达到限幅,正常积分 pi->out_inc = (pi->kp_q12 * (err - pi->err_last) >> 12) + (pi->ki_q12 * err >> 12); } else { // 已进入限幅,仅保留比例项计算,积分项冻结 pi->out_inc = (pi->kp_q12 * (err - pi->err_last) >> 12); } pi->err_last = err; pi->out += pi->out_inc; if (pi->out > PWM_PERIOD_MAX) pi->out = PWM_PERIOD_MAX; if (pi->out < 0) pi->out = 0; }注意,增量式实现里虽然输出变量本身就带有一定“积分”属性,抗饱和处理同样不可省,因为限幅让输出变量和真实执行值脱节后,误差消失时输出还需要额外一步补偿才能回落。
4.2 启动瞬间的冲击电流:PI控制器的第一个敌人
第二个坑在冷启动时就暴露了。目标转速从0直接给到1000rpm,PI控制器在最初几个采样周期内把输出拉到最大,电机瞬间获得接近满占空比的电压,电流冲击很大,导致供电电压都出现了明显跌落。这对电池供电的便携设备来说完全不能接受。
这个问题的根源不是PI本身,而是阶跃指令对系统来说太“陡”了。解决方案我用了两点:一是把阶跃目标改成斜坡目标,也就是让目标转速以预设的斜率爬升,给电机一个渐进的加速过程,而不是瞬间拉满。二是在启动时限制输出增量,每次步进允许的最大变化量单独设一个上限,防止输出突变。
斜坡时间我根据机械结构的承受能力设为300ms,也就是0到1000rpm在300ms内线性爬升。实测电流冲击从原来的4.2A降到2.1A,降幅超过一半,机械连接的冲击感也明显变轻。这个斜坡参数可以做成可配置项,不同的负载状态可以动态调整。
提示:斜坡时间不是越长越好。太长的斜坡会增加工时和加速时间,对需要快速响应的设备来说是负面优化。一般取正常阶跃响应调节时间的1.0~1.5倍,实测比较合适。
4.3 噪声抖动和数据记录中的“假象”
第三个坑是排查难度最高的一个。有一段时间我盯着转速波形,总觉得输出曲线的规律性很强,没有毛刺,但用手摸电机外壳能感觉到细微的高频振动。用上位机记录下来的转速数据看不出异常,可振动就是存在。
排查链路是:先怀疑机械装配问题,检查联轴器、轴承座,确认没有明显的不同心。再怀疑PWM频率,换了几组频率问题依旧。最后把电流波形拉出来看,发现PWM占空比其实每几个周期就有一个小幅度的跳变,虽然幅度很小,但频率落在机械共振频段附近,电机壳体就开始振。
这个抖动不是传感器噪声直接引起的,而是编码器的量化误差在低转速下占比太高,导致误差值在相邻采样点之间反复横跳。转速越高,编码器一个脉冲对应的时间越短,量化误差的相对影响越小;转速低到一定程度,这种量化抖动就开始主导误差,PI控制器误以为转速在波动,于是输出跟着来回调整。
定位到原因后,我加了两个措施。一是对编码器测速做滑动平均滤波,窗口取3个采样点,也就是15ms,增加测速稳定性;二是在PI算法里对误差做死区处理,当误差绝对值小于3rpm时不再更新积分项,只保留比例项跟随时输出,这个死区大小需要根据控制精度要求调整,太小没效果,太大会让人感知到稳态偏差。两个措施加完后,高频振动消失,电流波形也干净了很多。
从这几个坑可以发现,很多PI调试过程中的异常,根源往往不在PI参数本身,而在采样链路、执行机构特性和外部干扰上。排查问题时,一定要先怀疑数据来源,再怀疑算法,最后才怀疑参数。
5. 交付前如何证明PI真的“闭环成功”:量化指标和边界测试
5.1 阶跃响应的四个核心指标怎么统计
做完了整定和避坑,还不能直接说项目完成。工程交付必须有无可争议的量化数据,否则后续交接或者评审时拿不出说服力的证据。
我在P2阶段用上位机自动记录了阶跃响应数据,并且写了一段简单的分析脚本,自动识别四个核心指标:
- 超调量:转速越过目标值的最大偏移除以目标值,百分比表示。
- 调节时间:从阶跃发出到转速进入并停留在目标值±5%两个设定阈值带内不再出去的时间。
- 稳态误差:进入稳态后100个连续采样点的平均误差,以及最大波动范围。
- 上升时间:从目标值的10%到达90%所需的时间。
实测数据记录如下(目标转速1000rpm):
| 指标 | 实测值 | 项目要求 | 结论 |
|---|---|---|---|
| 超调量 | 6.3% | ≤10% | 通过 |
| 调节时间 | 280ms | ≤500ms | 通过 |
| 稳态误差 | ±2rpm | ±5rpm | 通过 |
| 上升时间 | 145ms | ≤300ms | 通过 |
这组数据说明,PI参数在需求域内是达标的,而且留了一定的余量。这里要提醒一个细节,调节时间的统计基准是“进入误差带后不再次超出”,有些人在统计时只看第一次进入误差带的时间点,这样会把实际超调后回落的情况也算成已完成,数据会虚低,评审时容易被挑战。
5.2 打破常规工况的边界测试
除了指标,我还做了几组边界测试,重点看系统在最恶劣情况下的行为:
先从最低目标转速起步,100rpm给阶跃,观察PI是否还有能力稳定在低速区间。低速的问题是编码器脉冲间隔变大,量化误差突出,实测在这个转速下稳态误差变为±6rpm,波动相对偏大,但系统没有失去稳定。
再给最高目标转速,接近电机标称极限的1900rpm,此时PWM占空比已经接近饱和。PI的抗积分饱和机制在这里起了关键作用,转速最终稳定在1896rpm左右,没有出现超调后长时间回落不了的迹象。
然后是负载扰动测试,在稳定运行的状态下给转轴施加一个持续负载,记录转速跌落和重新恢复的过程。实测转速从800rpm跌到760rpm,恢复时间约180ms,稳态误差回到±2rpm以内,说明PI对这种持续扰动有很好的补偿能力。
5.3 长期运行的稳定性确认
短时间测试通过不意味着长时间稳定,尤其是积分项相关的漂移问题,需要让系统持续运行来暴露。我让电机连续以800rpm运行了10小时,每小时记录一次稳态误差、控制器输出均值和芯片温度。
10小时跑下来,稳态误差始终在±3rpm以内,没有出现积分项随温度漂移而越积越大的现象。控制器输出均值也非常稳定,只在±1%的占空比范围内缓慢变化。这说明温度变化对PI参数的影响在可接受范围内,不需要在线调整参数。
如果系统表现出明显的长时间漂移,比如稳态误差随时间逐渐增大,或者控制器输出均值单调上升,就需要考虑加入参数自适应或者定时重置积分项的机制。P2这个项目目前还没到这个复杂度,但留了接口,后续如果需要扩展,不需要推翻现有框架。
最后的调试体会
项目收尾之后我复盘了一下,PI闭环控制本身并不复杂,真正让人花时间的地方全在细节:采样周期和噪声的关系、积分饱和的隐蔽性、抗饱和逻辑和斜坡启动相互配合的时序、以及边界条件下的稳定性验证。P2阶段我把微分项去掉的这个决定,从结果看是完全正确的,项目指标的达成率高,而且省去了大量和噪声做斗争的调试时间。如果之后遇到需要使用D项的场合,我会先确认采样链路已经足够干净或者说滤波带来的滞后可以接受,再来评估是否值得引入微分项。对于电机类负载的控制需求,先用好PI,把P和I的行为吃透,很多时候就已经够了。