ARTICLE DETAIL

资讯详情

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

直流电机PID闭环控制实战:从原理到STM32代码与调参

直流电机PID闭环控制实战:从原理到STM32代码与调参 把直流电机从“能转”做到“想转多少转多少”是我见过不少入门者卡壳的地方。很多人第一次接触PID是在某个小车或云台项目上PWM给大了飞起给小了纹丝不动负载一变转速就飘这时候才意识到光靠开环给占空比根本不够用。这篇文章就围绕“直流电机闭环”这件事把PID控制器的原理、代码实现和现场调参的完整路径讲清楚。先声明一下我默认你已经有一点单片机基础至少知道PWM是怎么回事用过编码器或者旋钮电位器之类的东西。如果你纯零基础建议先把GPIO、定时器、PWM这几个外设跑通再回来看这篇会更顺。1. 为什么直流电机需要“闭环”1.1 开环控制的一个大坑开环控制就是“只发令、不检查”。你给电机PWM一个固定占空比比如50%就默认它是半速转。但实际情况根本不是这样。直流电机转速受负载、电压、摩擦、温度多重影响同一个占空比空载时可能3000转带上去一个轮子立刻掉到2000转以下。我举一个最常见的例子。用普通TT马达做小车电池满电4.2V和快没电3.7V的情况下同样50%占空比轮子转速能差出去20%。如果两路电机本身机械参数不完全一致这差值会更明显结果就是小车不走直线跑着跑着就开始划圈。这就是开环控制的本质问题输出和实际之间没有信息回传系统不知道偏差自然也不会修正。1.2 闭环到底在“闭”什么闭环控制的思路很简单——加一个“测量-比较-执行”的回路。电机轴上装编码器实时测量转速拿目标转速和实际转速做差得到偏差控制器根据偏差计算控制量调整PWM输出。这个回路里的关键动作是“负反馈”。测到慢了就多给点力测到快了就少给点力方向总跟偏差相反这样才能稳定在目标点附近。用大白话说闭环系统就是“一边干活一边看着干得怎么样干偏了就自己掰回来”。直流电机闭环最常见的是速度闭环也就是让转速稳定在设定值。再往上加一个位置环还能精确控制电机转到某个角度机械臂、云台、打印机里用的都是这套逻辑。1.3 你需要哪些硬件基础做直流电机速度闭环硬件上绕不开这几样直流电机。最好带编码器常见的有霍尔编码器电机如MG513、GA25系列和光电编码器电机。如果手头只有普通电机也可以加装一个旋转编码器或者测速发电机但结构上要费点事。电机驱动模块。常用的有TB6612、DRV8833、L298N。TB6612体积小、压降低是我比较推荐的一款。它的STBY、AIN1、AIN2、PWMA这几个脚接单片机输出端接电机。单片机。STM32、ESP32、Arduino都行。STM32对编码器定时器的支持比较友好这里我后续示例就用STM32F103来写但逻辑是完全通用的。电源和降压模块。电机启动瞬间电流很大尽量用单独的电源给驱动供电控制板和驱动之间做好共地。信息流大概是编码器输出A/B两路脉冲 → 单片机的编码器模式定时器计数 → 程序算出实际转速 → PID计算得到新的PWM占空比 → 驱动模块输出对应电压 → 电机转速被调整。2. PID控制器的核心原理2.1 P——比例现在错了多少就反向补多少PID三个字母里P是比例它做的是“跟着当前的偏差走”。偏差大就给大输出偏差小就给小输出偏差为零就不额外出力。数学上只有P的控制器是这样输出 Kp * 偏差举个例子。目标转速是3000转当前实测是2500转偏差是500。如果Kp是0.2那输出就增加100PWM往大了调。随着转速接近目标偏差变小输出增量也越来越小。P反应的优点是快但缺点也很明显。你想想看当偏差等于0时输出是0而电机要维持转动本身就需要一个基本电压尤其是有摩擦的情况下。这就导致纯P控制时转速永远到不了目标值总会差一截这一截就是所谓的“稳态误差”。另外Kp如果调太大输出会来回冲过头系统就震荡起来了电机转速忽快忽慢你甚至能听到电机发出嗡嗡的抖动声。2.2 I——积分把之前的欠账补回来积分项解决的就是纯P控制剩下的那“一截偏差”。它把过去一小段时间内所有的偏差都累加起来只要有历史欠账就一直给你补输出直到偏差被完全消掉。比例项是看当下积分项是记旧账。两者放在一起输出公式变成输出 Kp * 偏差 Ki * 偏差积分积分项的直观效果就是“慢慢顶上去”。比如目标3000转纯P只能做到2850差了150转。偏差虽然不大但一直在积分器每秒都往输出上加一点PWM被一点一点顶高转速也跟着往上爬直到逼近3000转偏差接近0积分器停止增长系统稳定。但积分项有它的坑。“积分饱和”是新手非常容易踩的当电机堵转、或者目标突然调高一大截时偏差长时间很大积分项很快累到一个离谱的值即使后来偏差归零积分器还需要相当长时间才能“吐”出来表现就是电机超调很大或者过冲之后半天回不来。2.3 D——微分提前预判踩刹车微分项看的是偏差的变化速度。偏差增长快说明系统正在快速偏离目标D项就反向输出帮你“踩刹车”偏差在快速减小说明快要到达目标了D项也反向出力避免冲过头。完整PID公式就是输出 Kp * 偏差 Ki * 偏差积分 Kd * 偏差微分D的关键作用是抑制超调。举个例子启动时目标3000转转速从0往上冲快到3000的时候如果只靠P和I惯性大概率会让转速冲到3200再掉回来来回震荡。D项检测到“偏差正在快速缩小”这个趋势提前减小控制输出让曲线平滑地落到目标线上。不过微分项有一个大问题它会把噪声放大。编码器测速本身就有波动如果转速测量没有做滤波微分项会对这些噪声特别敏感表现出来就是PWM输出在抖电机声音发闷。所以实际做电机速度环时Kd往往用得比较小甚至很多情况下干脆不用D只用PI就足够稳了。3. 把PID写进单片机从公式到代码3.1 位置式PID与增量式PID写代码之前先搞清楚两种最常见的数字PID算法。它们在处理输出时思路不一样使用场景也有明显区别。位置式PID直接算一个“绝对值”也就是目标输出值。它的公式是output Kp*error Ki*integral Kd*(error - last_error)算出来的output直接用比如直接赋值给PWM寄存器。这种写法直观但积分项必须做好限幅否则容易积分饱和。温度控制、舵机控制这类执行器本身需要绝对位置的用位置式比较多。增量式PID算的是“这一轮输出比上一轮增加多少”实际输出要累加。公式是delta_output Kp*(error - last_error) Ki*error Kd*(error - 2*last_error last2_error) output delta_output增量式的优势是没有积分饱和问题因为每次只加一个小增量即使算错了输出也不会突变。对直流电机PWM调速来说增量式控制起来手感更自然启动和停机都比较柔和。代价是需要记住上一轮和上上轮的偏差代码稍微多存两个变量而已。3.2 控制周期怎么选PID是周期性执行的这个周期叫控制周期也是采样周期。周期长短直接影响控制效果选多少需要结合系统时间常数来看。直流电机的电气时间常数一般在几毫秒到十几毫秒机械时间常数在几十到几百毫秒。经验上速度环控制周期选1到10毫秒是合理的我做小车速度环一般用5毫秒200Hz或者10毫秒100Hz。位置环可以更慢20毫秒左右就够。控制周期太短单片机每个周期读到的转速变化量太小甚至编码器一个周期内只计数几个脉冲测量噪声很大微分项会特别容易爆。控制周期太长系统响应迟钝负载突变时半天修正不过来感觉整个系统“黏糊糊”的。3.3 输出限幅、积分分离与抗饱和控制代码里至少要做两层防护。第一层是输出限幅电机PWM只能在0到100%之间直接对PID输出做钳位。第二层是关键——积分限幅。位置式PID里积分项累加到一定大小后必须封顶不然启动瞬间积分能冲到几千后面输出全被积分项带跑系统要好半天才能缓过来。很多实战方案还会加积分分离偏差大于某个阈值时禁用积分只用P和D快速逼近偏差缩小到阈值以内再启用积分来消除静差。这样既保留了启动的快速性又不至于积分饱和算是工程上的常规操作。3.4 一个可以直接跑的电机速度闭环代码下面给一段简化的位置式PID速度闭环伪代码逻辑足够跑通整个控制流程实际工程里可以在这个框架上继续加滤波和保护逻辑。typedef struct { float Kp; float Ki; float Kd; float integral; float last_error; float integral_limit; float output_limit; } 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.0f; pid-last_error 0.0f; pid-integral_limit 500.0f; // 积分限幅 pid-output_limit 1000.0f; // 输出限幅对应PWM有效范围 } float PID_Calculate(PID_t *pid, float target, float actual) { float error target - actual; // 积分分离偏差太大时不累计积分 if (fabs(error) 100.0f) { pid-integral error; if (pid-integral pid-integral_limit) pid-integral pid-integral_limit; else if (pid-integral -pid-integral_limit) pid-integral -pid-integral_limit; } else { pid-integral 0.0f; } float output pid-Kp * error pid-Ki * pid-integral pid-Kd * (error - pid-last_error); pid-last_error error; if (output pid-output_limit) output pid-output_limit; if (output -pid-output_limit) output -pid-output_limit; return output; }主循环里每5毫秒定时器中断触发一次采样和控制void TIM_ISR_5ms(void) { int16_t cnt Encoder_GetCount(); // 读取编码器增量或绝对值 float speed Speed_Calc(cnt); // 换算成分钟转速或rad/s float output PID_Calculate(pid_speed, target_rpm, speed); Motor_SetSpeed(output); // 输出到PWM和方向引脚 }这里只写了控制部分测速换算是另一个容易出错的地方我放在下一节详细说。4. 实操让一个直流电机转起来并稳住转速4.1 硬件选型与接线我手头最长用的组合是STM32F103C8T6最小系统板加TB6612驱动模块电机是带霍尔编码器的MG513P30。编码器是AB两相输出转速直接和脉冲频率挂钩。接线比较简单TB6612的PWMA接STM32的TIM1_CH1PA8AIN1接PA4AIN2接PA5STBY接PA6。这里AIN1和AIN2决定电机方向PWMA决定占空比。编码器A相接TIM2_CH1PA0B相接TIM2_CH2PA1。把TIM2配置成编码器模式直接四倍频计数。共地不能忘驱动模块的GND和STM32的GND必须连在一起否则信号电平参考点不一致编码器读数完全不可信。很多人卡在编码器计数不稳定十有八九是共地问题或者信号线上没接上拉。霍尔编码器一般是开漏输出必须在每个通道对VCC接一个10k左右的上拉电阻板载没有的话要自己加。4.2 编码器读数与测速STM32的定时器编码器模式非常好用配置为Encoder Mode TI1 and TI2后定时器硬件自动根据AB相相位差加计数或减计数同时给出四倍频分辨率和方向信息。你只需要定期读出计数值然后清零即可。编码器转速换算的公式要能理解而不是死记。MG513的霍尔编码器常见参数是13线/圈四倍频后每圈脉冲是13乘4也就是52个脉冲。如果电机减速比是30那么输出轴转一圈需要52乘30等于1560个脉冲。假设你每10毫秒读一次计数值10毫秒内计数为N那么输出轴转速为rpm N * 100 / 1560 * 60这里的100是每秒的采样次数60是把每秒转数换算成每分转数。如果嫌整数运算溢出可以直接用浮点float rpm (float)cnt / (13.0f * 4.0f * 30.0f) * (1000.0f / dt_ms) * 60.0f;测速数据要想稳可以做一次简单的一阶低通滤波。滤波系数a取0.3到0.6之间公式是filtered a * current (1 - a) * filtered_last;注意滤波系数不要太小否则PID看到的速度变化极度滞后调出来的参数现场没法用。4.3 联调与加负载观察接线完成后先不要接PID直接手动给定一个固定PWM去跑确认电机方向、编码器计数方向和你预期的方向一致。常见问题是编码器方向反了导致闭环反馈变成正反馈电机越控越飞。我习惯先跑一个“标定”流程依次给20%、40%、60%、80%占空比记录每种情况下的稳定转速。这个数据有两个作用一是粗略判断电机和驱动的基本性能二是给后续PID输出限幅提供一个参考范围不至于调参时输出单位和转速目标对不上。都确认没问题后再把PID关掉把目标转速从0开始一点点往上加观察实际转速曲线。做完这些才正式进入闭环调试。4.4 用上位机看PID波形调PID凭眼睛看万用表或者凭手感基本等于盲人摸象。把数据传出来在上位机画曲线才能看清楚系统到底在震荡、在静差、还是稳定收敛。我一直比较推荐VOFA这个上位机协议用JustFloat格式把目标值、实际转速、PID输出量三个变量打包发出来上位机直接画三条曲线。串口数据格式很简单4字节float加一个0x00 0x00 0x80 0x7F尾部帧头。单片机端发送示例void SendFloatToPC(float target, float actual, float output) { uint8_t data[12]; float buf[3] {target, actual, output}; memcpy(data, buf, 12); uint8_t tail[4] {0x00, 0x00, 0x80, 0x7F}; HAL_UART_Transmit(huart1, data, 12, 10); HAL_UART_Transmit(huart1, tail, 4, 10); }调试时建议控制周期内只发一次也就是每5或10毫秒发一帧。9600波特率算下来一帧16字节足够用115200更宽裕。5. PID调参从震荡到稳定5.1 先P后I再D的试凑流程PID调参的教科书方法有很多但现场最实用的永远是“先P后I再D”的试凑法。我自己的标准流程是先把Ki和Kd全部清零Kp从一个很小的值开始比如0.05让系统跑起来观察波形。Kp太小系统反应慢实际转速和目标转速差很大而且发现电机“没力”。Kp慢慢往上加会发现响应变快曲线开始逼近目标线。继续加到某一个值附近曲线开始出现等幅震荡转速来回穿越目标线。记下这个临界Kp然后取它的60%到70%作为最终KpP环节就调完了。第二步加积分项。Ki从很小的值开始比如Kp的1/10或者1/20。加进来之后观察静差是否消失同时盯紧超调量。如果目标转速到达时冲过了一大截再回来说明Ki偏大适当减小。如果长时间消不掉最后的几十转静差再稍微加一点Ki。积分的作用就是把曲线最后那点误差“舔干净”所以调整尺度要非常细。第三步看是否需要D。多数减速直流电机速度环只靠PI就能做得不错。只有当你发现系统静差消除得很好但响应超调比较大、调整时间偏长时才加一点Kd。Kd从非常小的值开始加比如0.01量级并同时观察转速曲线的噪声水平。一旦PWM输出开始高频抖动说明Kd已经过头了必须往回退。5.2 常见问题速查表实操中遇到的现象就那么几种我整理了一个速查表对号入座就能快速定位大部分问题。现象可能原因处理办法电机转速来回震荡忽快忽慢Kp过大或者控制周期过慢降低Kp或加快控制周期到10ms以内目标转速始终差一截怎么都到不了缺积分项或Ki太小增加Ki确认积分限幅没有卡死输出启动瞬间猛冲一大截才回来积分饱和或输出限幅太大加积分分离缩小积分限幅和输出限幅转速慢慢上下飘像呼吸一样编码器数据滤波过度或I项偏大调大滤波系数或降低滤波强度减小KiPWM输出高频抖动电机声音发闷Kd过大或测速噪声大减小Kd给测速加一阶低通滤波正反馈速度越来越快直到满转编码器方向或电机方向接反反转编码器A/B或交换电机线让反馈变成负反馈带负载转速掉下来回不去控制周期太长或Ki太小加快控制周期适当提高Ki5.3 进阶给串级PID留个口子调好了一个速度环很多项目到这里就够用了。但如果你后期要做精确位置控制比如云台锁定目标、机械臂点到点运动单靠一个位置环控制PWM是玩不转的因为位置变化时系统阻尼太低位置环自己调会震荡。工程上常见的解法是串级PID外环是位置环输出是目标转速内环是速度环输出是PWM。位置环计算“离目标位置还有多远”输出一个期望转速速度环再根据这个期望转速去动态调PWM。外环响应慢、内环响应快两层各司其职。这也就是常说的“位置外环加速度内环”。串级PID的调参顺序和单环相反先调内环速度环稳定之后再调外环位置环。内环相当于整个系统的执行基础基础不稳外环全是白搭。实际调的时候可以把内环的Ki调得偏大一些减小位置稳定后的微小爬行。之前调好的速度环代码不要扔把它封装成一个函数串级时位置环每20毫秒算一次期望转速速度环每5毫秒跑一次两个不同频率的控制任务错开调度就可以了。我个人在实际操作中的一个体会是调PID千万别着急更不要靠“感觉”去挨个试参数每次只改一个量改完跑一次记录波形参数再多也绕不出这个流程。还有一个小技巧调试阶段把输出限幅调低一点比如限制在最大PWM的70%这样即使在参数激进的瞬间系统也不会一路狂飙导致电机过流或者把东西撞坏。稳中求进这算是闭环调试里最实用的一条心得。
返回列表