做直流电机调速最绕不开的一环就是PID参数整定。我之前在一个直流电机调速平台上折腾闭环速度控制,前前后后花了不少时间,中间踩过坑,也总结出一套比较实用的整定步骤。这篇文章就把整个过程拆开讲:平台怎么搭、参数怎么起步、怎么一步步把稳态和动态都调到位、最后在代码里怎么落地。内容适合刚接触闭环控制的朋友,也适合做小车底盘、云台、风机这类项目的开发者参考,哪怕你之前只做过开环PWM调速,看完也能直接上手试。
我手里这套平台其实很朴素:一块常见单片机开发板,一个H桥电机驱动模块,一台带霍尔编码器的直流减速电机。硬件成本不高,但凡是带上编码器的直流电机,都能套用这套思路。关键不在于设备多贵,而在于你对PID每个参数在物理上到底干了什么有感觉。网上讲PID原理的文章很多,真正把“怎么起整”讲透的却很少,这篇主要补的就是这部分实操经验。
1. 平台搭建与硬件选型:先让闭环有个可靠的根
1.1 闭环三要素:电机、驱动、反馈
很多人一开始调PID,手边只有电机和一个可调电源,以为靠肉眼观察转速就能调参,结果调了半天也不知道当前误差到底是多少。原因很简单:速度闭环的本质是“测量-比较-修正”,没有可靠的转速反馈,后面一切都是盲调。
一个完整的直流电机调速平台至少要有三个部分:被控对象(直流电机)、执行机构(PWM驱动模块)、测量反馈(旋转编码器)。我用的电机是24V直流减速电机,自带霍尔编码器。霍尔编码器的好处是抗干扰强、便宜、寿命长,缺点是分辨率通常不如光电编码器,但做速度环足够用了。
驱动模块我选的是常见的H桥驱动模块,这类模块内部有四个功率管,通过PWM控制平均电压,从而控制电机转速。选驱动时别只看“能驱动几安”,还要看有没有续流二极管(很多集成模块已经内置,但外置驱动板的散件就得自己确认)。电机是感性负载,PWM关断瞬间会产生反向电动势,没有续流回路,驱动管和主控都可能被打坏。这件事是硬件层面最常见的坑。
主控的选择反而没那么讲究,只要是能输出PWM、带定时器输入捕获接口的单片机都行。关键在于编码器信号要用硬件的输入捕获或者编码器接口来读,不要在主循环里用软件去读引脚跳变,否则转速高一点就丢脉冲。
1.2 编码器线数、减速比和转速测量的换算
选编码器也好、写测速代码也好,先要把分辨率这件事算明白。以我用的13线霍尔编码器为例,霍尔编码器在电机轴转一圈的过程中会产生13个脉冲,如果单片机定时器做4倍频计数(上升沿和下降沿都计数),那么电机轴一圈对应52个计数。
我用的减速电机减速比是20:1,也就是说电机轴转20圈,输出轴转1圈。如果测的是输出轴转速,那么输出轴转一圈对应52×20=1040个计数。这套数字直接决定你的测速下限:如果10ms采样一次,测到1个脉冲,那么这1个脉冲对应的转速是多少?1个脉冲/10ms = 100脉冲/秒,换算成输出轴转速就是100÷1040转/秒,大约96转/分钟。也就是说,低于这个转速时,一个采样周期里可能只有0到1个脉冲,测速精度会急剧恶化,表现为速度在低速时一抖一抖的。
这是很多项目低速抖动的根源,不是PID没调好,而是测速分辨率不够。遇到这种问题,通常有三个解法:提高编码器线数、改用T法测速(测量单个脉冲的时间宽度)、或者把减速比加大。在整定参数之前先把这部分算清楚,能省掉后面一大半的幻觉问题。
2. PID参数初值怎么给:先搞清楚自己在调什么
2.1 位置式和增量式,调速场景该用哪个
PID公式大家都很熟了,位置式写法下,控制量等于三项之和:
u(k) = Kp × e(k) + Ki × Σe(j) + Kd × (e(k) - e(k-1))
其中e(k)是当前误差,Σe(j)从启动到现在累积的误差,最后一项是本次误差相对上次的变化量。位置式的特点是输出直接对应PWM占空比,代码直观,但积分项需要单独做限幅,不然电机卡住时误差一直累积,输出直接冲到限幅值,等障碍物消失后系统要花很长时间才能恢复正常,这就是常说的积分饱和。
增量式PID输出的是控制量的变化量Δu(k):
Δu(k) = Kp×(e(k)-e(k-1)) + Ki×e(k) + Kd×(e(k)-2e(k-1)+e(k-2))
每次算出来的是一个增量,然后在上一次输出上累加。增量式的天然优势是即使输出饱和,等误差反向时,增量能很快把输出拉回来,不会出现严重的积分饱和。所以我的直流电机调速代码里用的是位置式PID,但单独做了积分限幅和输出限幅。如果你习惯增量式,逻辑也一样,核心判断顺序、整定方法完全通用。
2.2 用一个数量级起步,免去盲目试参
很多人拿到PID第一反应是问“Kp应该设多少”。其实Kp可以先估个数量级再细调,完全不用瞎试。方法很简单:想一想你最大允许的PWM占空比变化是多少,再想一想最大可能的速度误差是多少。
比如我的平台,PWM占空比物理范围是0到100,速度误差最大大约是500转/分钟。如果我期望最极端情况下输出变化30个百分点的占空比,那么Kp的数量级就是30÷500,约等于0.06。这是一个非常粗略的估算,但至少给了你一个起点。我一直强调“数量级”三个字,是因为Kp=0.6和Kp=0.06差了10倍,一开始如果直接用0.6大概率就是剧烈振荡,而0.06可能只是反应偏慢,学习成本和排查成本都低得多。
Ki和Kd的初值,我一般先给0。这么做不是跳过积分和微分,而是为了让系统先能稳定运行,再用后面的步骤逐个加入。还有控制周期这个参数,它不叫“PID参数”,但对整定结果影响极大,我默认取10ms,也就是每秒算100次速度环。这个值对绝大多数直流电机调速平台都够用,采样太慢会错过速度变化,采样太快会把编码器低速时的量化噪声当成真实波动,反而让系统乱跳。
3. 整定实操:从纯比例调到三环微调
3.1 第一轮:只动Kp,摸清系统脾气
Kp是PID里唯一一个“直面当前误差”的参数,它的作用可以理解为:误差有多大,我就立刻用多大力气去纠正。Kp越大,系统对误差的响应越猛,但过大会让系统“用力过猛”,产生超调,甚至等幅振荡。
我的实操方法是:Ki和Kd都设为0,Kp从估算值的一半开始,给定一个固定目标速度,用串口把目标速度、实测速度、PWM占空比一起记录打点。观察曲线你会发现,Kp很小的阶段,速度缓慢爬升,要很久才接近目标,而且离目标总差一点,这就是纯比例控制的结构性缺陷,它需要误差本身来维持输出,所以永远消不掉稳态误差,后面要靠积分来解决。
接着Kp一挡一挡往上加,比如0.03、0.06、0.09这样。每加一挡都重复同样的阶跃测试,直到曲线出现明显的超调或等幅振荡。这个时刻对应的Kp就是“临界比例”,实际使用的Kp取它的60%到80%就好。为什么还要留余量?因为临界比例是在空载、单一工况下测出来的,实际负载变化和机械磨损都会改变系统特性,留20%到40%的余量才能保住稳定。
这一步是整定过程里最花时间的,但也是最值得花时间的。我的经验是每次只允许自己改一个参数,其余保持不动,并且用同一组目标速度来做对比,否则你根本分不清曲线变化到底是因为Kp改了还是因为测试条件变了。
3.2 第二轮:加入Ki,处理静差
Kp调好之后,系统最大的毛病通常就是静差:速度稳定在目标值下面,差那么十几转到几十转,怎么都上不去。这是摩擦力、死区等系统性扰动造成的。积分项的作用就是把这些微小且持续的误差一点点累积起来,逐步补足控制量。
加到这一步,很多人习惯直接拍脑袋给一个积分系数,结果就是速度在目标值附近上下震荡,周而复始。我给Ki的建议是从很小的值开始,比如Kp的十分之一到五分之一,然后慢慢加,直到静差被消除。判断Ki是否合适有个很直观的标准:给定稳态目标后,速度应该先快速逼近目标,再平滑咬住目标,不能在终点附近来回绕圈。
积分项还有一个很容易爆的雷:电机低速时PWM占空比有死区。很多电机在占空比低于某个阈值时根本转不动,比如我手里这台,低于12%的占空比力矩不够,轮子纹丝不动。这时候误差一直存在,积分一直累积,一旦越过死区,电机猛地一冲,速度又超了,然后积分往回退,周而复始,形成“爬行”。解决办法有两个,一是给积分项单独限幅,把积分输出限制在一个合理范围内;二是做死区补偿,在PWM占空比低于阈值时直接跳到阈值附近,让电机先动起来。第二个办法更治本,但需要你实测自己平台的死区值,每台电机都不一样。
3.3 第三轮:Kd救场,抑制超调
Kp和Ki调完之后,系统通常已经能跑,但你要面对的是两个让人头疼的问题:超调量和稳定时间。速度冲过目标再跌回来,或者来回穿几次,看着就不舒服。微分项的作用是看误差的变化趋势:如果误差在快速减小,说明速度正在冲向目标,微分项会提前减小输出,起到“刹车”作用。
不过微分项在速度环上特别容易被噪声坑。编码器在低速时的脉冲量化很严重,相邻两次采样之间的速度跳变可能是±10转,微分项会把这种虚假的剧烈变化当成真实趋势,产生高频抖动。在这种平台上,如果直接用差分形式的微分项,Kd稍微调大一点就会听到电机嗡嗡响,转速越快响声越明显。
我后来不直接在给定速度上做微分,而是只对“实测速度”做微分,这就是常说的“微分先行”思路。虽然写代码时也就是把微分的输入对象换一下,效果却天差地别。Kd的整定也是从小往大调,直到超调被压下去,系统能在一次或最多两次过冲之内稳定下来。如果Kd加得太大,系统的趋势响应太敏感,会出现高频振铃,这是微分过量的典型信号。
3.4 第四轮:综合微调和负载扰动测试
三轮单独调完,系统基本能稳定,但要真正投入使用,还得做一次完整的联合调试。先把刚才调好的Kp、Ki、Kd组合起来,给一个从低速到高速的大范围阶跃,看看在全速段内是不是都能稳定。你大概率会发现:低速段表现不错,中高速段可能又出现轻微振荡,或者高速时静差又冒出来了。这类问题往往是积分项和比例项在动态响应上的耦合,需要微调,幅度控制在小范围,比如Kp上下动不超过20%,每次只动一个。
接下来是负载扰动测试。我习惯用手轻轻捏住电机输出轴,模拟负载突然增大,然后松手模拟负载突减,观察速度偏离和恢复时间。这个过程最能暴露积分项的不足:负载增大时,如果Ki太小,速度会掉下去一大截然后才慢慢爬回来;如果Ki太大,恢复过程会伴随明显往返振荡。真正的目标不是让对方“永远不动”,而是让速度偏差尽量小、恢复尽量快且不超调,三者做不到同时最优,必须根据应用场景取舍。
如果你想做长期跟踪的对比实验,还可以在每次统一测试之后把整条速度曲线保存为文本文件,最后比对不同参数组的上升时间、超调量、稳态误差和稳定时间。我个人的体会是,记录数据比手感可靠得多,很多看似玄学的整定差异,一看数据就清清楚楚了。
4. 代码落地:别让细节把参数白调
4.1 速度环主循环:一个能跑的骨架
不管PID调得多好,代码实现有问题都白搭。下面这段是用在直流电机调速平台上的位置式PID骨架,逻辑非常直白:定时器每10ms进入一次速度环,测速、算PID、限幅、更新占空比。
typedef struct { float Kp; float Ki; float Kd; float integral; float last_error; } PID_t; void PID_Init(PID_t *pid, float kp, float ki, float kd) { pid->Kp = kp; pid->Ki = ki; pid->Kd = kd; pid->integral = 0; pid->last_error = 0; } float PID_Calc(PID_t *pid, float setpoint, float measurement, float dt) { float error = setpoint - measurement; // 误差积分,并做积分限幅 pid->integral += error * dt; if (pid->integral > 50) pid->integral = 50; if (pid->integral < -50) pid->integral = -50; // 误差微分,这里直接做差再除以dt float derivative = (error - pid->last_error) / dt; float output = pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative; pid->last_error = error; return output; }配套的主循环逻辑大概是这样的:10ms定时标志置位后,读编码器计数、算当前转速、调用PID_Calc得到占空比,然后限幅到0到90之间(留10个点不占满,给PWM死区恢复留余地),最后写入PWM模块。代码本身没什么高深的,但有几个细节值得强调。
第一,dt必须是实际的时间间隔,不要写死一个假值。如果你把10ms当成1s来算,积分项和微分项的单位全都会错,参数整定过程会变得莫名其妙。第二,积分限幅和输出限幅是两个独立的东西,积分限幅是防止积分项爆炸,输出限幅是给执行机构留物理余量,两者都写才算完整。第三,PWM占空比在启动瞬间不要直接跳到PID算出来的值,我习惯做一段斜坡加速,让目标速度以每秒200到300转的速率逐步爬升,这样能避免启动冲击电流和机械冲击。
4.2 测速精度、PWM频率和方向处理
测速代码同样有讲究。我采用的是M法测速:固定每10ms读一次定时器的编码器计数值,减去上一次的计数值,得到这段采样周期内的脉冲个数,再换算成转速。这个方法实现简单,中高速很准,但前面说过的低速量化问题会暴露出来。如果项目有低速高精度需求,可以把T法测速加进去:记录相邻两个脉冲之间的时间间隔,用这个间隔直接算转速,低速时反而更准,但中高速会因为脉冲间隔太短而变得噪声很多。成熟的方案是低速用T法、中高速用M法自动切换,代码量会大一些,但效果确实是实打实的。
PWM频率的选择也是一个经常被忽视的问题。频率太低的PWM,比如几十Hz,电机运转时会发出明显的“哒哒哒”的声音,整个控制系统还会出现间歇性的转速波动,因为PWM周期和10ms控制周期之间会产生拍频振荡。我在这类直流电机平台上一律用10kHz以上的PWM频率,避开听觉敏感区,也让电流波形足够平滑。但要注意一点:PWM频率不是越高越好,频率太高时驱动模块的开关损耗会上升,发热明显增加,普通H桥模块到20kHz左右已经很烫了,需要留意散热。
最后是方向问题。如果是单向调速,比如风机,PID输出限幅到0到100之间就行。如果是双向调速,比如小车底盘,PID输出为负时要让H桥反转,这时候编码器的方向判断必须同步跟上,转速要带正负号。我的做法是用单片机的正交编码器接口同时读A相和B相的相位关系,硬件直接给出方向,软件里把计数存成有符号数,转速计算出来是负数就代表反转方向。这样PWM输出和编码器计数的符号始终对齐,闭环才不会在过零时刻打架。
5. 常见问题排查:抖、冲、爬、偏怎么治
调参过程中踩过的坑,我整理成了一张问题速查表,每种现象对应几个最可能的病因。先看现象,再按表格从上到下排查,比“随机调参”高效得多。
| 现象 | 可能原因 | 排查顺序 |
|---|---|---|
| 电机低频抖动、声响明显 | Kp过大 / PWM频率过低 / 采样周期过长 | 先看Kp是否处于振荡边界,其次检查PWM频率是否低于1kHz,最后缩短控制周期 |
| 转速冲到顶下不来 | 输出饱和 / 目标速度突变太快 | 检查输出是否长时间顶在限幅值,给目标速度加斜坡 |
| 转速和目标差一截,怎么都补不上 | Ki太小 / 积分限幅太紧 / 死区问题 | 逐步加大Ki,看积分项是否频繁触顶,实测死区并做补偿 |
| 超调明显、来回振荡几次才稳定 | Kd不足或没有 / 目标速度阶跃过猛 | 增加Kd,或采用微分先行写法 |
| 低速爬行、一冲一冲 | 编码器分辨率不足 / 机械静摩擦 / 积分累积死区 | 换更高线数编码器或改T法测速,做死区补偿 |
| 带负载后速度骤降,恢复很慢 | Ki太小 / 控制周期太长 | 加大Ki并观察恢复曲线,缩短控制周期 |
| 电流声高频刺耳 | PWM频率落在听觉敏感区 / 占空比变化过快 | 把PWM频率提高到15kHz以上,同时降低Kd的噪声放大 |
最常出问题的是第一个:低频抖动。很多人以为是PID调得不好,拼命去动Kp和Ki,实际上可能是PWM频率只有几百Hz,电机根本还没进入稳定的连续电流模式,转速本身就是波动的,比你调任何参数的影响都大。遇到抖动,我建议先看硬件,再看软件,最后才怪PID。
“转速冲到顶下不来”也很有迷惑性。之前我碰到一次,现象是目标200转,电机直接飙到全速,占空比顶到上限,怎么降Kp都没用。后来一查,是测速程序里编码器计数换算转速时忘了除以系数,实测速度算出来只有实际的一半,PID以为误差很大,输出满占空比去追。这种问题属于“反馈失真”,比PID参数错误更隐蔽,排查时优先检查测速值是否可信,比如用手持转速表或者另一路传感器对比一下,别一上来就怀疑Kp不对。
还有一个让我印象很深的场景:系统换了电池供电以后,原来调好的参数突然不稳定了,速度波动变大。原因是电池电压随负载波动,PWM占空比对应的平均电压跟着波动,等效于系统增益变了。这种场合我一般在主控端做一次母线电压采样,对PWM输出做归一化补偿,或者在整定时预留更多稳定裕度。很多量产设备会这样做,但个人DIY项目里经常漏掉。
最后再分享一个小技巧
整定PID时,我非常推荐把目标速度、实测速度、PID三项各自的输出值和最终占空比,通过串口实时画成曲线。不用买昂贵的示波器,PC端随便找一个串口绘图小工具就能把数据点画出来。我在这套直流电机调速平台上的实际体会是:光有表格数据和代码不够,曲线图会告诉你很多数字看不出来的信息,比如微分项的微小振铃、积分项的缓慢爬升、死区导致的阶梯状扭曲,这些特征只有看着曲线对照着现象才能迅速定位问题。
最后再补一条关于工作流程的建议:每次调参只动一个参数,并且记录下改之前和改之后的完整曲线。这个习惯看起来简单,实际坚持下来的人却不多。我自己的经验是,凡是乱调一气、恨不得一次改三个参数的时候,系统状态总是越调越烂;老老实实“改一个、测一轮、存数据、再改一个”,反而能快速收敛到满意的参数组。PID整定本质上就是在跟系统对话,每次只问一个问题,它才能给你一个明确的答案。