☰
直流电机闭环控制实战:从PID原理到编码器调参
2026/10/5 7:25:46 网站建设 项目流程

刚从开环切到闭环那会儿,我踩了不少坑。电机接上电就能转,但一转起来就“放飞自我”,带点负载转速就掉,空载又飞车,想让它稳稳停在某个速度,光靠PWM占空比根本压不住。后来把直流电机闭环和PID调通之后,我才算真正搞明白——所谓“闭环”,就是用测量值不断修正输出,而PID就是那个修正算法。

这篇东西写给谁?给那些跟我一样,手里有带编码器的直流减速电机、想把它做成转速闭环,但被一堆“比例积分微分”、“传递函数”、“采样周期”术语劝退的朋友。我会把这套东西拆成最白话的逻辑,配上我实测过的代码框架和调参顺序,你照着做,大概率能跑起来。里面涉及的硬件、代码思路,也适用于STM32、Arduino或任何能输出PWM、能读编码器脉冲的单片机平台。

1. 内容整体设计与思路拆解

1.1 为什么直流电机必须闭环

直流电机本身是“傻”的。你给它一个电压,它就转;电压越高,空载转速越高。但问题是,电机一旦带上负载(比如轮子压到地毯、机械臂抬重物),转速立刻往下掉,而你给它的电压并没有变。这就是典型的开环控制缺陷:你无法知道电机当前实际转速,只能“盲调”输入量。

我在做一台小巡检车底盘时试过,两路PWM直接怼58:1的带编码器减速电机,空载时左右轮转速还能勉强一致,一放到粗糙地面上,左轮被绊一下转速掉20%,车就会往左偏。解决这个问题的唯一办法,就是加一个测量环节,实时“看”转速,再通过算法把转速拉回目标值。这就是闭环控制。而PID是控制算法里最简单、最通用、最适合入门的一种。

1.2 闭环控制系统的四个基本环节

一个标准的直流电机转速闭环系统,包含四个环节:

  • 被控对象:直流电机和它的负载(车轮、风扇叶、机械臂关节都可视为负载)。
  • 执行器:电机驱动芯片(如DRV8833、TB6612、L298N),它接收PWM信号并输出对应的平均电压。
  • 测量环节:编码器、霍尔传感器、光电测速盘等,用于把电机轴转动的物理量变成脉冲信号或数字量。它是闭环的“眼睛”。
  • 控制器:你的MCU里的PID代码。它拿测量值v.s.目标值做差,算出误差,再根据PID公式得出控制量(PWM占空比)。

这四样东西缺一不可。尤其是测量环节,很多新手觉得“我不就是让电机转嘛,要什么编码器”——这话当然也没错,但如果没有测量环节,你就永远不知道系统实际状态,PID完全无从谈起。我自己设计时,编码器的安装位置也很有讲究:最好是直接装在电机轴上测,而不是装在轮子外侧,轮子打滑时极容易造成转速测量偏差与真实转速不一致,导致控制失控甚至来回震荡。

1.3 控制量与误差的物理含义

PID的核心对象是误差,定义是:

error = target_speed - current_speed

这里的target_speed是期望转速,current_speed是编码器测到的实际转速。如果有两个轮子,就针对左右轮各算各的PID,分别给出左右PWM,这样就能做到转弯时的速度随动——这是“小车差速转向”的基础。

我一开始总有个误区,以为PID是在“让电流稳定”或者“让电压稳定”,其实都不是,它只负责“让误差收敛到零”。至于误差能不能快速收敛、会不会过冲、会不会有静差,就看P、I、D三个参数配得对不对。这也是为什么很多人说PID调参是一门“手感活”。

2. 核心细节解析与实操要点

2.1 PID三个字母到底各自在干什么

先说最经典的连续PID公式,它表达的是控制器输出u(t)对误差e(t)的响应关系:

u(t) = Kp * e(t) + Ki * ∫e(τ)dτ + Kd * de(t)/dt

其中:

  • Kp(比例系数):看到当前的误差有多大,就给多大的修正。误差越大,输出变化越猛。但比例控制永远都会有静态误差,因为当误差小到一定程度时,输出的修正量也变小,可能不足以补偿负载的扰动。
  • Ki(积分系数):把过去所有误差累加起来。哪怕误差只有一点点,只要持续存在,积分项会越来越大,最终把误差“顶”到零。所以积分项是消灭静差的功臣,但也容易引发“积分饱和”。
  • Kd(微分系数):根据误差变化的速度来“刹车”。误差快速增大时,微分项会抑制这种趋势;误差快速减小时,微分项也会阻止输出冲过头。它起到阻尼作用,让系统更稳。

举个例子。你要求电机转速是1000rpm,但当前只有800rpm,P项会立刻把电压往上顶,比如顶到70%占空比;转速到了900rpm,P项输出回落,I项还在那里慢慢累积,把剩余的100rpm误差一点点消掉;如果电机惯性大,冲到1050rpm,D项会感应到误差变化率为负(误差由正转负),输出一个反向的“拉扯”,避免转速冲太高。

2.2 位置式PID和增量式PID怎么选

软件代码里,PID最终要离散化到单片机里运算。最常见的有两种形式:位置式PID和增量式PID。

位置式PID(也叫“直接式”)公式:

u(k) = Kp * e(k) + Ki * Σe(i) + Kd * (e(k) - e(k-1))

这种形式算出来的是“当前应该输出多少”的绝对占空比或绝对控制量,它需要不断累积误差和。好处是直观,但一旦积分饱和(误差一直存在,累积值涨到极大),输出可能会超出PWM允许范围,需要额外做限幅处理。

增量式PID公式:

Δu(k) = Kp * (e(k) - e(k-1)) + Ki * e(k) + Kd * (e(k) - 2*e(k-1) + e(k-2))

这种形式算出来的是“相比上一次,输出变化多少”,每次把Δu叠加到当前控制量上。增量式的好处是不需要累积误差总和,不容易发生积分饱和问题,对执行器是增量型对象(比如步进电机、电调信号)特别友好。而且即使算错了,误差也只是“偏了一点点”,不会直接导致驱动猛冲。

我在直流电机调速上用的就是增量式。核心代码如下:

typedef struct { float Kp; float Ki; float Kd; float target; float last_error; // e(k-1) float prev_error; // e(k-2) float output; // 当前控制量输出 } PID_TypeDef; float PID_Calc(PID_TypeDef *pid, float current) { float error = pid->target - current; float delta = pid->Kp * (error - pid->last_error) + pid->Ki * error + pid->Kd * (error - 2.0f * pid->last_error + pid->prev_error); pid->output += delta; pid->prev_error = pid->last_error; pid->last_error = error; return pid->output; }

这里有三个关键点:

  • pid->output要加限幅,通常在0到PWM_MAX之间。如果电机是双向转的,限幅可以设在负的PWM_MAX到正的PWM_MAX之间。
  • 注意输出量纲要和PWM占空比匹配。比如PWM是0~999,那么output就需要映射到这个区间,PID里的Kp、Ki、Kd数值也要基于这个量纲去调节。
  • 如果目标转速是0(停止),PID输出可能不为零,要额外加一个判断,防止电机在停止状态“嗡嗡”抖动。

2.3 采样周期和限幅的工程意义

很多人调PID时忽略采样周期,其实采样周期对控制效果影响非常大。我的经验是,采样周期必须与电机响应速度匹配。直流电机转速环的典型采样周期在5ms~20ms之间,太短了微分项容易被噪声放大,太长了控制系统会反应迟钝,负载突然变化时转速掉下去很大一截才反应过来。

你应该在代码里固定采样周期,用定时器中断或操作系统的tick来实现,而不是在主循环里“能跑多快跑多快”。因为不同的计时方式会导致PID运算间隔忽长忽短,微分项和积分项的计算就会不稳。

限幅分为输出限幅和积分限幅。输出限幅最简单,就是限制PID最终输出的PWM最大值,防止上电瞬间电机猛转。积分限幅则是只限I项本身的累积值,这在位置式PID里特别重要。比如误差一直是10rpm,I项持续累加,到了某个时刻占空比已经叠到100%,但误差还没消,如果继续累加,等误差反向后,积分项还得花很长时间回落,系统就会出现很明显的“退出饱和延迟”。所以我会在代码里给积分项设一个上限,比如±500,这样即使超调,恢复也很快。

3. 实操过程与核心环节实现

3.1 硬件选型与连接方案

先交代我这次用的硬件:

  • 直流减速电机带霍尔编码器(常见的MG310、GA12-N20之类都可以,注意选带A、B两相编码器的型号)
  • 电机驱动:TB6612FNG(比L298N压降小,发热小,适合电池供电)
  • 主控:STM32F103C8T6最小系统板
  • 电源:7.4V锂电池(驱动板取电5V给MCU和编码器供电,避免共地干扰)

接线清单:

电机端驱动板端MCU端
电机电源线M+、M-TB6612的AO1、AO2无
编码器VCC(通常5V/3.3V)无3.3V/5V正极
编码器GND无GND
编码器A相无PA0(TIM2_CH1)
编码器B相无PA1(TIM2_CH2)
PWM控制(VM/IN1之一侧)TB6612的PWMAPA8(TIM1_CH1)

注意编码器供电电压不能超规格,很多N20电机编码器是5V供电,但输出信号是5V高电平,STM32的GPIO承受不了5V直接输入,最好加电平转换或者选用3.3V兼容的型号。我第一版没注意,直接把5V输出接到PA0,结果把引脚烧了,后面换了电平转换模块才解决。

3.2 编码器测速的四倍频原理

编码器输出的是A、B两相相差90°的方波脉冲。通过检测两相的电平组合变化,可以判断电机正反转,还能实现四倍频测速:也就是说,电机转一圈编码器有N个脉冲,但通过检测A相和B相的上升沿和下降沿,你可以得到4N个计数脉冲,分辨率直接提高4倍。

举个例子,我用的编码器是11线(即电机轴每转一圈输出11个AB相脉冲),加上四倍频后每圈计数就是44。我电机减速比是30:1,那么输出轴每转一圈的总计数就是44×30=1320个。测速公式:

speed = (pulse_count_in_sample_time / 1320) / sample_time * 60 // 单位:rpm,这是输出轴转速

如果你直接测电机轴的转速(减速器前),那就用44作为每圈脉冲数,但一般来说我们控制的是输出轴转速,所以要把减速比也算进去。

用STM32的编码器接口模式(Encoder Mode)可以自动统计AB相脉冲,不需要外部中断频繁响应。核心配置:

TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);

这种方式硬件自动处理“加减法”,读TIM2->CNT的值就是当前脉冲计数,非常省心。每10ms读一次,然后把CNT清零重新计,速度和方向一次搞定。

3.3 PID计算与输出核心代码

我把速度控制放在定时器中断里,10ms执行一次。主循环只负责修改目标转速和监控数据,不参与PID计算,这样可以保证计算周期稳定。

// 定时器3,10ms中断回调 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM3) { // 读取编码器计数 int16_t cnt = (int16_t)TIM2->CNT; // 有符号数才能表示正反方向 TIM2->CNT = 0; // 计算当前转速,单位rpm float speed = (float)cnt / 1320.0f / 0.01f * 60.0f; // 调用增量式PID int32_t pwm = (int32_t)PID_Calc(&pid_motor, speed); // 输出限幅 if (pwm > 999) pwm = 999; if (pwm < -999) pwm = -999; // 控制电机方向 if (pwm >= 0) { __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwm); // 设置TB6612的IN1=1, IN2=0,电机正转 } else { __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, -pwm); // 设置TB6612的IN1=0, IN2=1,电机反转 } } }

这里有个细节:cnt用有符号int16_t读取,如果DIR反了,转速就是负数,PID也能自动处理,但你要注意电机在正转时编码器计数是递增的,否则闭环会变成“正反馈”——这一点我在最开始接线时踩过坑,换上轮子后发现转速越控越大,最后才发现是A、B相接反了。

3.4 目标转速斜坡处理

直接给一个突变的目标转速,比如从0直接到1000rpm,PID的输出会突然拉满,电机可能瞬间冲击电流很大,机械结构也容易受损。我的做法是给目标转速加一个“斜坡发生器”,让目标值以固定速率上升,比如每10ms加5rpm,400ms才能从0爬到200rpm左右。这样PID不用面对阶跃信号,压力小很多,调节过程也平缓。

斜坡处理虽然看起来是“软件小技巧”,但对于调PID参数非常关键。你在判断一个参数好不好时,应该针对“匀速爬坡”和“阶跃跟踪”分别测试。如果以阶跃信号去猛冲,任何一组参数都可能在初期触发限幅,让你误以为“参数太激进”,实际上是被斜坡目标掩盖了真实问题。

4. 常见问题与排查技巧实录

4.1 “电机启动时猛冲一下再回到目标转速”

这个问题我遇到好几次,原因是PID的积分项在上电时可能残留了一个较大的历史值,或者初始误差过大导致P项瞬间饱和输出。解决办法三个思路:

  • 在系统启动时把PID状态清零,包括last_error、prev_error和output。
  • 设置目标转速从当前转速开始爬坡,而不是从0到目标值。
  • 加上PWM启动占空比限制,比如最大50%,防止上电瞬间电压过高。

还有一个可能是编码器测到转速为0时,PID认为误差很大,输出直接飙到限幅值。这时候我会在目标值设置前先等编码器“安静”下来,并做一个上电自检,确认编码器读数正常再切入闭环。

4.2 “转速始终达不到目标值,差几十rpm”

这种情况通常是积分项太弱,或者系统存在持续的负载力矩,P项已经不足以补偿。我当时就是Kp调得比较大,误差存在但输出已经接近上限,但是转速始终差那么一点。

排查顺序:

  1. 先确认PWM输出是不是真的在PID输出值附近,比如示波器看波形是否存在异常限幅。
  2. 调大Ki,每次乘2左右,观察静差是否缩小。
  3. 如果调大Ki后出现低频振荡,说明Ki过大,折中取原值的1.5倍左右,再加一点Kd抑制过冲。

也可以考虑“前馈补偿”:既然电机在一个基础占空比下才能维持目标转速,就直接在PID输出上加一个参考占空比,相当于PID只负责修正偏差,而不负责“扛基础负载”。这个做法我一般在负载波动大的场景下才用,简单应用上不必须。

4.3 “转速在目标值附近来回震荡,电机发烫”

震荡有几种可能。第一是采样周期太短或太长,导致PID计算频率和系统响应周期恰好形成共振。试着把采样周期改为原来的1.5倍或2倍看看。第二是D项过大,对噪声特别敏感,编码器信号稍微抖动一下,D项就给你一个很大的反向输出,电机就会抖。第三是机械结构有旷量,比如齿轮间隙太大,会导致“输出有迟滞”,PID总觉得误差没消失,一直在加码,于是周期性过冲。

处理策略是:

  • 在编码器测速时做低通滤波,比如滑动平均4次,让速度数据更平滑。
  • 适当减少Kd,不要追求“一点不超调”。
  • 如果机械旷量明显,考虑把目标转速的斜坡速率降得更低,别让PID频繁在旷量区间来回试探。

4.4 “负载变化后转速恢复太慢”

这其实是PID的“扰动抑制”能力不足。如果只追求空载调参效果好,带负载后你会明显感觉到“软的没力气”。我一般测试时直接用手去捏电机轴,模拟突加负载,观察转速下跌量和恢复时间。如果恢复时间超过1秒,就说明Kp偏小,需要加大Kp;如果下跌量过大,说明Kp和Ki都要同时加大;如果恢复过程中有来回振荡,说明Kd不够,需轻微增加。

这张表可以当你排查时候的速查参考:

现象可能原因处理方向
稳态静差大积分不足增大Ki
调节过程长、响应慢比例不足增大Kp
目标值附近反复振荡比例/微分失衡先减小Kp,再适当增加Kd
电机抖动、发热严重微分项受噪声干扰平滑转速测量、降低Kd
上电猛冲初始误差过大/积分残留清零PID状态、斜坡给目标值

4.5 用好上位机调试工具,不要盲调

最初一年我调PID都是盲调,改参数、下载、示波器看PWM波形,效率很低。后来用了VOFA+这类上位机,把转速反馈值、PID输出、目标速度通过串口发到电脑,就能实时看到波形曲线。我强烈建议你也这样做,因为“看出波形变化”比“猜现象原因”要快得多。

具体做法很简单,就是每隔10ms往串口打印一行带标签的数据:

target:1000.0, current:986.5, pwm_out:532.1

VOFA+之类的浮点绘图工具会用不同的颜色曲线显示每个字段,你就能很直观地看到转速爬坡波形是“阶跃上冲再回落”还是“平滑逼近”,也能看到PID输出在那一瞬间做了什么。

5. 调参顺序和我的实操心得

5.1 三步调参法:先P,再I,最后D

网上各种调参口诀很多,但对我最有效的是三步走:

  1. 只用P,Ki和Kd全部设为0,给一个相对小的目标转速斜坡。从小到大慢慢增大Kp,直到系统开始出现持续等幅振荡。记下这个Kp值,把它除以2到3,作为你实际的Kp初值。这一步找的是“系统不振荡的最大比例增益”。
  2. 加入I,保持Kp不变,从小到大增加Ki。观察静差能否消失。如果出现低频振荡(波形一高一低缓慢摆),就减少Ki到振荡消失。我一般从Ki = Kp/100作为起点。
  3. 加入D,只有遇到明显超调才用Kd。从Kd = Kp/10开始试,观察超调是否收小。如果电机开始抖动,说明Kd太大,降到之前的一半。

一个可以参考的起点参数是:Kp = 3.0,Ki = 0.05,Kd = 0.2,采样周期10ms。这个组合在我的31:1减速电机、999级PWM的配置下表现稳定。但注意这个数值不通用,尤其是PWM量纲不同时,Kp的物理意义会随之改变。

5.2 为什么不能盲目照抄参数

很多人喜欢从网上抄一组参数就直接用,结果发现系统要么疯狂震荡,要么反应迟钝。原因很简单:PID参数没有绝对标准,它跟这几个因素强相关:

  • 电机的额定电压和扭矩特性
  • 减速比和编码器分辨率
  • PWM频率和占空比分辨率
  • 采样周期
  • 负载惯量

也就是说,你把同一组PID参数从3.3V供电的玩具电机换到12V供电的强磁电机上,系统行为会截然不同。正确的姿势不是“抄参数”,而是“抄方法”——按调参顺序和方法自己在当前系统上跑一遍,得出的参数才真正适合你。

5.3 从转速环到位置环、串级PID的扩展思路

当你把直流电机转速闭环调通后,再往上扩展就顺理成章了。比如你想让电机转固定的圈数(位置控制),可以在外环再加一个位置环,内环是速度环,这叫串级PID。外环位置环的输出作为内环速度环的目标值,内环速度环的输出再去控制PWM。这种结构在机械臂关节、云台、无人机控制里很常见。

我之前在无人机飞控的学习中体会过串级PID的内外环时间间隔设计:外环(角度环)运行频率可以低一些,比如100Hz;内环(角速度环)至少要200Hz以上。对应到电机位置控制上,位置环可以10~20ms执行一次,速度环5~10ms执行一次,这样内外环的控制周期比大约是2:1到4:1,既能保证稳定性,又不会让MCU算力吃紧。

5.4 还有哪些“小问题”要格外注意

再分享几个我实际调试中踩过的小坑,可能不大会写在文档里:

  • 共地问题:MCU和驱动板必须共地,否则编码器信号会受干扰飘忽,速度测出来跳来跳去。
  • PWM频率:直流电机PWM频率常用10kHz~20kHz,太低会有可听见的电流噪声,太高会增大开关损耗。我一般选10kHz。
  • 编码器线长:如果编码器线和电机电源线扎在一起走,电机转动时会产生电磁干扰,导致编码器脉冲异常。我后来把编码器信号线远离电机电源线,并加了磁环或绞合线,现象明显好转。
  • 电池电压跌落:锂电池放电末期电压会下降,开环PWM会显得“无力”,但闭环PID会自动增大占空比去补偿,所以你会发现闭环系统对电源电压变化有天然的自适应能力。

6. 结语与个人体会

回头再看“如何使你的直流电机闭环”这个问题,其实核心就三件事:把转速测准(编码器),把误差算对(PID算法公式),把输出限制好(PWM限幅与参数匹配)。没有哪一步是玄学,每一步背后都有清晰的物理逻辑。我最大的体会是,要善用上位机把系统“透明化”,不要盲调。看到误差曲线和PID输出曲线的那一刻,你会对比例、积分、微分各自的行为有非常直观的理解,这比死记一万句理论都有用。

另外,如果你手头的电机没有编码器,也没关系。你可以先尝试用反电动势法测转速,或者用红外对管加码盘做一个简易测速装置,控制思路是完全一样的。等到闭环转速控制跑通了,你再去搞位置闭环、串级PID,会顺畅得多。调参路上多试几次、多炸几次车,都是好东西——我也正是在这些“翻车”经历里,才真正摸清了PID的脾气。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询