尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

STM32与PID算法实现直流电机转速闭环控制全解析

STM32与PID算法实现直流电机转速闭环控制全解析 1. 项目概述从零构建一个电机转速闭环控制系统最近在做一个智能小车的项目核心需求是让两个轮子的转速保持稳定且一致无论负载怎么变化。这听起来简单但实际动手才发现从“电机能转”到“电机转得又快又稳”中间隔着一道名为“闭环控制”的鸿沟。我选择了最经典、最实用的PID算法搭配STM32和CubeMX这套黄金组合成功实现了对直流有刷电机转速的精准控制。整个过程就像给一个毛躁的年轻人电机请了一位经验丰富的教练PID控制器通过不断观察编码器反馈、对比与目标值、调整PWM占空比最终让它变得沉稳可靠。这篇文章我就来详细拆解这个“教练”是如何工作的以及我是如何用CubeMX快速搭建起整个控制框架的。这个项目非常适合已经玩转STM32点灯、串口通信想要向电机控制、自动控制等更硬核领域迈进的开发者。你将学到如何将抽象的PID控制理论转化为实实在在的、能在微控制器上运行的代码并亲眼看到算法参数如何影响电机的动态响应。无论你是做机器人、无人机还是任何需要精密运动控制的设备这套方法都是基石。2. 核心控制思路与系统架构设计2.1 为什么选择PID闭环控制开环控制就像蒙着眼睛开车你给电机一个固定的PWM信号相当于踩下固定深度的油门希望它达到某个转速。但一旦遇到上坡负载增加转速就会掉下来下坡时负载减轻转速又会飙升。你无法感知实际速度也就无法做出调整。闭环控制则装上了“眼睛”和“大脑”。眼睛是编码器实时测量电机的实际转速大脑是STM32中的PID控制算法它持续比较“期望转速”目标值和“实际转速”反馈值计算出两者的偏差误差然后根据PID公式动态调整输出给电机的PWM信号强度从而消除误差让实际转速紧紧跟随目标转速。PID中的P比例、I积分、D微分分别对应着对当前误差、过去累积误差和未来误差变化趋势的纠正三者结合能应对绝大多数控制场景。2.2 系统整体架构与信号流整个系统的信号流是单向闭环的理解这个流至关重要设定目标在代码中设定一个目标转速例如1000 RPM。测量反馈电机旋转带动编码器编码器输出脉冲信号。STM32的定时器捕获这些脉冲通过计算单位时间内的脉冲数换算出当前的实际转速。计算误差PID控制器接收目标转速和实际转速计算误差e(t) 目标 - 实际。PID运算根据误差e(t)进行PID运算输出 Kp * e(t) Ki * ∫e(t)dt Kd * de(t)/dt。这个运算在STM32中每个控制周期执行一次。输出控制PID运算的结果是一个控制量通常映射到PWM的占空比0-100%。STM32的定时器输出对应占空比的PWM波驱动电机驱动芯片如L298N、TB6612。驱动执行电机驱动芯片根据PWM信号调整施加在电机两端的平均电压从而改变电机转速。回到第2步形成闭环。这个循环以固定的频率如1kHz高速运行使得系统能够对外部干扰做出快速反应。2.3 硬件选型要点与考量主控芯片STM32我选用的是STM32F103C8T6蓝色药丸核心板资源足够72MHz主频多个定时器性价比高。关键是它拥有高级定时器如TIM1/TIM8和通用定时器如TIM2/3/4前者可以生成带死区控制的互补PWM非常适合驱动H桥电路后者可以配置为编码器接口模式硬件自动计数极大减轻CPU负担。电机与编码器我用的是一款带增量式正交编码器的直流减速电机。编码器线数如11线指电机转一圈输出11个脉冲决定了测速分辨率。线数越高分辨率越高但也会对定时器计数频率提出要求。电机驱动芯片对于小型直流电机TB6612是比经典L298N更好的选择效率高、发热小、内置保护电路。它需要两路PWM信号控制方向和速度。电源务必注意电机驱动电路驱动芯片和电机的电源必须与STM32的MCU数字电源隔离或使用独立电源并确保功率足够。电机启停会产生巨大的电流尖峰和电压毛刺是干扰单片机稳定运行甚至损坏芯片的主要元凶。注意电源隔离是硬件设计的第一要务。最简单的做法是使用两个独立的稳压电源模块一个给STM325V或3.3V一个给电机驱动根据电机额定电压如12V。如果必须共用也一定要在电机电源入口处加入大容量电解电容如470uF和多个小容量瓷片电容如0.1uF进行退耦滤波。3. CubeMX工程配置详解STM32CubeMX极大地简化了外设初始化让我们能聚焦于控制逻辑本身。以下是关键配置步骤。3.1 时钟树与系统基础配置首先在RCC中设置高速外部时钟HSE为晶振源。然后在时钟树配置界面将系统时钟SYSCLK设置到芯片允许的最高频率对于F103通常是72MHz。更高的主频意味着更快的控制周期和更精确的定时。3.2 定时器配置PWM生成与编码器接口这是配置的核心需要两个定时器。定时器A用于生成PWM驱动电机 我使用高级定时器TIM1的通道1和通道2来生成两路PWM分别控制电机的两个输入AIN1, AIN2实现正反转和调速。选择TIM1将Channel1和Channel2设置为“PWM Generation CHx”。在参数配置中Prescaler预分频器设置为71。因为系统时钟72MHz72MHz / (711) 1MHz定时器计数频率为1MHz。Counter Period自动重装载值ARR设置为999。这样PWM的频率 1MHz / (9991) 1kHz。1kHz的PWM频率对于普通直流电机是合适的既能避免可闻噪音又有不错的响应速度。Pulse初始脉冲宽度先设为0。CH Polarity通道极性保持默认High表示占空比高电平有效。在NVIC Settings中可以启用TIM1 update interrupt如果你计划在定时器更新中断中执行PID计算的话。另一种更常见的做法是使用另一个定时器来触发PID计算。定时器B用于编码器接口模式 我使用通用定时器TIM2来读取编码器。选择TIM2将Combined Channels设置为“Encoder Mode”。在参数配置中Encoder Mode选择“Encoder Mode TI1 and TI2”。这意味着同时使用编码器的A相TI1和B相TI2进行计数可以实现4倍频每个脉冲的上升沿和下降沿都计数提高分辨率。Polarity根据编码器实际信号调整通常保持默认Rising Edge。Counter PeriodARR设置为最大值6553516位定时器。这个值要足够大避免在采样周期内溢出。例如编码器线数114倍频后为44计数/转假设电机最高转速3000 RPM50转/秒则最大计数变化率为44 * 50 2200 计数/秒。即使我们每10ms100Hz读取一次计数器值变化量也只有22远小于65535安全。无需开启定时器中断因为我们采用主循环定期读取计数器值的方案。3.3 GPIO与串口配置GPIO配置连接电机驱动芯片方向控制引脚如STBY, IN1, IN2的GPIO为输出模式。串口配置一个USART如USART1用于通过串口助手发送目标转速、接收实际转速和PID输出等数据方便调试和参数整定。波特率设为115200。生成代码前在Project Manager中设置好工程名称、路径、IDEMDK-ARM V5并将HAL库的版本选择为当前稳定版。4. PID算法实现与代码集成4.1 离散PID的位置式与增量式在微控制器中我们实现的是离散化的PID。有两种常见形式位置式PIDu(k) Kp * e(k) Ki * ∑e(j) Kd * [e(k) - e(k-1)]输出u(k)直接对应PWM的占空比。积分项∑e(j)需要一直累加误差可能导致积分饱和积分 windup即误差长期存在时积分项变得非常大系统响应迟钝甚至失控。增量式PIDΔu(k) Kp * [e(k)-e(k-1)] Ki * e(k) Kd * [e(k) - 2e(k-1) e(k-2)]u(k) u(k-1) Δu(k)输出是控制量的增量。它计算的是本次应该增加或减少多少控制量而不是控制量的绝对值。优点是对系统冲击小且天然具备抗积分饱和的特性因为输出不会无限累积更安全在电机控制中更为常用。我选择实现了增量式PID因为它更鲁棒。4.2 PID结构体与初始化在工程中新建pid.c和pid.h文件。首先定义PID结构体用于管理一个PID控制器的所有数据和参数// pid.h typedef struct { float Target; // 目标值 float Measured; // 测量值反馈值 float Err; // 当前误差 float Err_Last; // 上一次误差 float Err_Prev; // 上上次误差 (用于增量式D项) float Kp, Ki, Kd; // PID参数 float Integral; // 积分项位置式用 float Output; // 控制器输出 float OutputMax; // 输出上限 float OutputMin; // 输出下限 } PID_Controller;然后编写初始化函数将参数清零并设置初始值// pid.c void PID_Init(PID_Controller *pid, float kp, float ki, float kd, float out_max, float out_min) { pid-Kp kp; pid-Ki ki; pid-Kd kd; pid-Target 0.0f; pid-Measured 0.0f; pid-Err 0.0f; pid-Err_Last 0.0f; pid-Err_Prev 0.0f; pid-Integral 0.0f; pid-Output 0.0f; pid-OutputMax out_max; pid-OutputMin out_min; }4.3 增量式PID计算函数这是核心算法函数// pid.c float PID_Calculate_Incremental(PID_Controller *pid, float target, float measured) { float increment 0.0f; pid-Target target; pid-Measured measured; pid-Err_Last pid-Err; // 保存上上次误差到Err_Prev pid-Err_Prev pid-Err_Last; // 保存上次误差到Err_Last pid-Err pid-Target - pid-Measured; // 计算新误差 // 增量式PID公式: Δu Kp*(e(k)-e(k-1)) Ki*e(k) Kd*(e(k)-2e(k-1)e(k-2)) increment (pid-Kp * (pid-Err - pid-Err_Last)) (pid-Ki * pid-Err) (pid-Kd * (pid-Err - 2.0f * pid-Err_Last pid-Err_Prev)); // 更新输出并限制在最大最小值之间 pid-Output increment; if (pid-Output pid-OutputMax) { pid-Output pid-OutputMax; } else if (pid-Output pid-OutputMin) { pid-Output pid-OutputMin; } return pid-Output; }4.4 主循环中的集成与调用在main.c的主循环或一个定时器中断服务程序中我们需要按固定周期执行以下步骤读取编码器值计算转速通过HAL_TIM_ReadEncoder()读取TIM2的计数器值CNT。注意编码器模式下滑定时器是双向计数的。计算差值delta_cnt current_cnt - last_cnt。同时需要处理计数器溢出超过65535或低于0的情况将其转换为有符号的32位整数差值。然后根据采样时间T秒和编码器分辨率PulsePerRev每转脉冲数已考虑4倍频计算转速RPM (delta_cnt / PulsePerRev) / T * 60。调用PID计算将目标转速和计算出的实际转速传入PID_Calculate_Incremental函数。应用PID输出PID的输出值映射到PWM的占空比。例如如果PWM的ARR是999输出范围是0-999那么可以直接用__HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, (uint32_t)pid.Output)来设置比较寄存器。处理方向如果目标转速是负的反转则需要改变电机驱动芯片的方向控制引脚电平并取转速的绝对值进行PID计算。实操心得计算转速时的采样时间T必须尽可能准确。最好的方法是使用一个硬件定时器产生固定周期如10ms的中断在这个中断里触发上述步骤1-3。使用HAL_GetTick()软件计时在高速控制中误差较大不推荐。我使用了TIM3配置为10ms中断专门用于触发PID控制循环保证了控制的周期性。5. PID参数整定与系统调试实战PID控制器的性能完全取决于三个参数Kp,Ki,Kd的取值。整定过程是一个“理论指导实验为主”的工程艺术。5.1 参数整定步骤从零到稳定我采用经典的试凑法遵循“先比例后积分再微分”的原则。初始化将Ki和Kd设为0OutputMax/Min设为PWM的允许范围如0和999。目标转速设为一个中等值如500 RPM。整定比例系数 Kp逐渐增大Kp直到系统出现持续、小幅度的振荡即转速在目标值上下有规律地波动。此时系统处于“临界振荡”状态记下此时的Kp值为Ku并测量振荡周期Tu相邻波峰的时间差。根据齐格勒-尼科尔斯Ziegler-Nichols经验公式初始参数可设为Kp 0.6 * KuKi 2 * Kp / Tu注意这里的Ki是离散公式中的系数需要乘以采样时间T才是我们代码里的Ki即Ki_code Ki * TKd Kp * Tu / 8同样Kd_code Kd / T这是一个起点通常需要在此基础上微调。整定积分系数 Ki在Kp的基础上逐渐增加Ki。积分的作用是消除静态误差稳态误差。如果系统达到目标值后存在固定偏差就需要加大Ki。但务必小心积分饱和如果Ki太大积分项累积过快会导致系统超调严重甚至振荡发散。一个实用的技巧是积分分离当误差较大时如启动阶段不进行积分或限制积分只有当误差进入较小范围时才引入积分作用。整定微分系数 Kd微分项能预测误差变化趋势抑制超调提高系统稳定性。逐渐增加Kd观察系统响应曲线超调量应减小调节时间变短。微分项对噪声极其敏感编码器测速的微小跳动会被微分放大导致输出剧烈抖动。因此在实际应用中往往需要对测量值转速进行低通滤波或者使用“不完全微分”来平滑微分项。5.2 利用串口可视化调试在调试阶段将关键数据通过串口发送到电脑用串口绘图工具如Serial Plotter, Vofa可视化是最高效的方法。我在控制循环中每计算一次PID就通过串口发送一帧数据// 假设使用 printf 重定向到了串口 printf(%.2f,%.2f,%.2f\n, pid.Target, pid.Measured, pid.Output);在电脑端可以看到三条曲线黄色的目标值一条水平直线蓝色的实际转速以及红色的PID输出。通过观察蓝色曲线如何跟随黄色曲线以及红色曲线的变化可以直观地判断参数是否合适响应慢增大Kp。静态误差增大Ki。超调大、振荡减小Kp或增大Kd。高频抖动减小Kd或检查编码器噪声。5.3 进阶技巧抗积分饱和与输出限幅这是工程实现中必须考虑的两个问题直接关系到系统的安全性和鲁棒性。抗积分饱和Anti-windup 当输出已经达到限幅值如PWM已到100%但误差依然存在如负载极重积分项会继续累积到一个非常大的值。当误差反向时负载减轻积分项需要很长时间才能“消化”掉这个巨大的累积值导致系统反应迟钝。解决方法是在积分项累加前进行判断float new_output pid-Output increment; if (new_output pid-OutputMax || new_output pid-OutputMin) { // 如果计算出的新输出已经饱和则停止积分或只进行反向积分 // 可以简单地跳过 Ki*Err 项的累加或者使用更复杂的反计算饱和算法 } else { pid-Output new_output; }输出限幅 PID的输出必须限制在PWM占空比的有效范围内0-ARR。这在上面的PID计算函数中已经体现。限幅保护了电机和驱动电路避免过驱动。6. 常见问题排查与实战经验录在实际焊接、接线、编程、调试的过程中我踩过不少坑。这里把典型问题和解决方法列出来希望能帮你节省时间。6.1 电机完全不转或抖动现象可能原因排查方法电机完全不转无声音电源未接通或电压不足用万用表测量电机驱动板电源输入端电压。检查使能引脚如TB6612的STBY是否拉高。电机不转但有“滋滋”声PWM频率过高电机驱动无法响应降低PWM频率至1kHz-10kHz范围试试。检查电机驱动芯片的逻辑电平是否与STM32匹配3.3V vs 5V。电机剧烈抖动转速不稳编码器接线错误或接触不良检查编码器A、B相是否接反电源和地线是否接好。用逻辑分析仪或示波器观察编码器脉冲波形。电机单向转不能反转电机驱动方向控制逻辑错误检查控制电机方向的两个GPIO引脚电平组合是否正确。例如TB6612的IN1H, IN2L为正转反之反转。6.2 转速测量不准或不稳定问题计算出的转速跳动很大或者与用转速表测量的值相差甚远。排查编码器计数错误确认在CubeMX中编码器模式是否设置为“TI1 and TI2”并检查Polarity。用示波器看A、B相信号是否正常正交。采样时间不准确保用于触发速度计算的定时器中断周期是精确的。不要在while(1)循环中用HAL_Delay要用硬件定时器中断。数值计算溢出delta_cnt、转速计算涉及乘除确保使用足够大的数据类型如int32_t并注意单位换算。转速公式RPM (delta_cnt * 60) / (PulsePerRev * T)其中T是采样时间秒。机械振动电机本身的振动可能导致编码盘抖动产生噪声脉冲。可以考虑对读取的delta_cnt进行软件滤波如取多次平均或使用一阶低通滤波。6.3 PID控制效果不佳问题转速要么跟不上要么来回振荡超调严重。排查与解决控制周期过长PID运算和输出的频率控制频率必须远高于系统响应速度。对于普通直流电机控制周期建议在1-10ms即100-1000Hz。如果周期太长如100ms系统延迟太大PID很难调稳。参数整定不当回到第5章用串口绘图工具从小参数开始慢慢调整。记住口诀“参数整定找最佳从小到大顺序查。先是比例后积分最后再把微分加。”输出限幅不合理如果OutputMax设置得太小即使误差很大PID输出也达不到让电机启动的PWM值系统永远无法响应。可以先将限幅设宽调好参数后再根据电机特性收窄。负载突变过大PID是线性控制器对于非常大且突然的负载变化如小车突然撞墙可能无法快速恢复。可以考虑加入前馈控制或者在检测到巨大误差时切换控制模式如全功率输出。6.4 系统运行一段时间后异常问题刚开始运行正常几分钟后电机失控或单片机复位。排查电源发热或压降电机长时间运行特别是启停频繁时电流很大可能导致电源模块过热或输出电压下降进而影响单片机稳定工作。检查电源模块的额定电流是否足够并触摸其温度。电机干扰这是最常见的原因。确保电机电源线和信号线编码器线、PWM线分开走线避免平行长距离走线。在电机两端并接一个104瓷片电容和一个续流二极管吸收电刷火花产生的高频噪声。STM32的复位引脚、晶振引脚等关键信号线要远离电机驱动部分。看门狗复位如果程序陷入死循环看门狗会使系统复位。检查是否在长时间循环或中断中进行了阻塞操作。可以在主循环中适时喂狗。经过这一整套从硬件选型、CubeMX配置、算法实现到调试整定的流程当看到电机转速能够精准、平稳地锁定在你设定的任意值时那种成就感是无与伦比的。PID闭环控制是自动控制的基石掌握了它你就打开了机器人、智能车、平衡系统等无数应用的大门。最关键的是不要怕失败参数调乱了就从头再来用科学的方法观察曲线代替盲目试错每一次调试都会让你对系统的理解更深一层。最后务必做好电路隔离和滤波稳定的硬件是优秀控制算法的前提。
返回列表