STM32直流电机PID控制实战:编码器测速与参数整定全记录

发布时间:2026/9/9 4:57:54
STM32直流电机PID控制实战:编码器测速与参数整定全记录 简介面向STM32微控制器与直流电机控制开发者的完整PID控制程序包以意法半导体STM32F103C8T6微控制器、L298N双H桥驱动芯片和MG513P30直流电机为核心构建速度闭环控制方案。程序包含GPIO、定时器、PWM输出等外设初始化配置以及PID算法的初始化、采样周期设定、误差计算、限幅处理与控制输出的完整流程同时梳理了比例、积分、微分系数整定的常见方法适用于智能车、机器人底盘、小型自动化设备等电机调速项目。压缩包为zip格式大小约47.69MB适合嵌入式爱好者、单片机初学者以及需要快速落地电机控制方案的工程师参考。已有5746人浏览学习说明该程序在实际项目中具有较高参考价值。下载后可重点阅读代码结构、驱动逻辑和PID参数调试要点有助于缩短从原理理解到实际运行的距离深入掌握嵌入式实时控制与电机系统稳定性的调试方法。 去年做智能车底盘的时候整套系统用的就是STM32F103C8T6最小系统板 L298N电机驱动模块 MG513P30直流减速电机这套经典组合。调通编码器测速、再叠上PID闭环之后小车跑直线的效果比开环控制强了不是一点半点。这篇就完整记录一下这套PID控制程序从硬件接线到代码实现、再到参数整定的全过程给正在做智能车、机器人小车底盘或者相关毕设的同学一个可以直接抄作业的参考。1. 项目整体设计与硬件选型思路1.1 为什么是这三件套先说结论这套配置几乎是入门直流电机PID控制最稳妥、资料最全、成本最低的组合之一。STM32F103C8T6是Cortex-M3内核72MHz主频片上定时器资源足够同时输出PWM和读取编码器性能完全够用L298N是经典的H桥驱动芯片驱动电流最大2A带逻辑电源输出虽然压降大了点但对于学习阶段的小车底盘完全能接受MG513P30是带霍尔编码器的直流减速电机30:1减速比自带AB相编码器输出可以直接接在单片机的定时器编码器模式上。这套组合最大的优势在于编码器反馈 定时器硬件资源刚好匹配。MG513P30自带霍尔编码器省去了外接测速模块的麻烦而STM32F103C8T6的定时器支持编码器接口模式直接把AB相输出接上去硬件自动完成4倍频计数CPU零开销。如果换成普通的直流电机自己想办法测速或者用IO口模拟编码器计数那PID闭环就完全不是这个难度了。1.2 控制方案选型为什么必须上PID开环控制下给电机一个固定PWM占空比转速会随着电池电压下降、负载变化而明显波动。最典型的问题就是刚充满电的电池和快没电的电池同一个占空比下转速能差20%以上小车跑不直速度也稳不住。PID闭环控制的核心思路就一个不断测量实际转速和目标转速做比较根据误差动态调整PWM输出。误差大了就加大输出误差接近零就减小输出通过比例、积分、微分三项的组合让实际转速快速且平稳地逼近目标值。这套方案不要求精确的电机模型不依赖电池电压的补偿属于经典的“无模型控制”在小车底盘、云台电机、传送带调速这类场景下非常实用。这次项目目标就是让STM32通过L298N驱动MG513P30实现“目标转速输入→实际转速快速收敛”的完整闭环。2. 硬件连接与STM32CubeMX初始化配置2.1 接线方案与电源分配接线之前先明确一点L298N的电机电源和单片机的逻辑电源必须共地。也就是L298N的GND要接到STM32的GND上否则控制信号没有参考地电机要么不转要么乱转这是新手最常见的问题。我的实际接线如下STM32F103C8T6引脚L298N模块功能说明PA0编码器A相左电机TIM2_CH1编码器输入PA1编码器B相左电机TIM2_CH2编码器输入PB6编码器A相右电机TIM4_CH1编码器输入PB7编码器B相右电机TIM4_CH2编码器输入PA6ENA左电机使能TIM3_CH1PWM输出PA7ENB右电机使能TIM3_CH2PWM输出PB0IN1左电机方向GPIO输出PB1IN2左电机方向GPIO输出PB10IN3右电机方向GPIO输出PB11IN4右电机方向GPIO输出霍尔编码器需要5V供电注意MG513P30的编码器供电和信号线是独立的一般红色VCC接5V黑色GND接GND白色和黄色分别为A相、B相。这里务必和电机的主电源分开避免电机启动时的压降把编码器电压拉垮。电源方面要注意L298N的电机供电建议用7.4V以上的2S锂电池或者12V电源适配器。L298N自身的饱和压降在2V左右如果用5V供电实际到电机两端只剩3V左右转速和扭矩都难看。逻辑侧的5V可以直接用L298N板载的5V输出只要不额外带大电流负载就行。2.2 CubeMX配置要点在STM32CubeMX里编码器接口模式配置如下对于TIM2和TIM4将Channel1和Channel2均设置为Encoder ModeCombined Channels选择Encoder Mode TI1 and TI2这样计数方向由AB相相位差自动决定不需要额外处理方向判断。分频器和自动重载寄存器设置为0xFFFF即65535让计数范围最大化方便后续软件处理。PWM输出端TIM3配置为PWM Generation CH1和PWM Generation CH2预分频71自动重载值设成1000或者2000。这里选值需要想一下预分频71配合72MHz主频得到1MHz的计数频率自动重载1000时PWM频率为1kHz自动重载2000时PWM频率为500Hz。对于直流电机PWM频率太低会有明显的“滋滋”声和转速波动一般建议至少10kHz以上。实测下来我把预分频改成了7自动重载1000即PWM频率10kHz这样电机运行更安静、更平稳。提示PWM频率并非越高越好太高的话L298N的开关损耗会增大电机电感滤波效果反而下降。对于L298N这种分立器件驱动10kHz到20kHz是比较合理的区间。2.3 编码器模式的原理解读定时器编码器模式本质上是一种硬件级的正交解码功能。MG513P30的霍尔编码器输出的AB相信号相位相差90度电机正转时A相超前B相反转时B相超前A相。定时器通过检测AB相的电平变化和相位关系自动判断旋转方向并控制递增或递减计数。这里有个关键的硬件细节编码器输出的AB相要接在定时器的T1和T2通道上不能接普通GPIO。软件轮询读AB相也能算转速但高速转动时会因为读取不及时而丢脉冲所以硬件解码是必须的。MG513P30减速比30:1编码器线数实测为13线经过4倍频后电机输出轴每转一圈产生13×30×41560个计数脉冲。这个参数后面计算转速时要用先记下。3. PID控制核心原理与程序实现3.1 位置式PID还是增量式PIDPID控制算法在软件实现上主要有两种形式位置式和增量式。位置式PID直接输出控制量比如PWM占空比的绝对值计算时需要累加历史误差一旦长时间无法到达目标积分项会变得很大出现“积分饱和”问题系统超调严重。增量式PID输出的则是控制量的增量即每次在原来PWM基础上增加或减少多少因为不涉及大范围的误差累加抗积分饱和能力更强而且输出变化平稳。从代码角度说增量式PID公式如下printf(增量式PID: 输出增量 Kp*(e - e_last) Ki*e Kd*(e - 2*e_last e_last2)\n);这次项目最终采用的是增量式PID。电机转速控制场景下PWM输出范围有限0到1000增量式PID天然适合这种受限输出系统不容易出现积分饱和导致的剧烈超调。3.2 核心代码实现整个控制程序分为三块PID结构体与计算函数、编码器测速包含速度换算、定时器中断服务函数中的控制逻辑。先看PID算法本身typedef struct { float Kp; float Ki; float Kd; float target_speed; // 目标转速 (rpm) float current_speed; // 当前转速 (rpm) float error; float last_error; float last_last_error; float output; // PWM输出增量 float pwm_out; // 实际PWM占空比 } PID_TypeDef; void PID_Init(PID_TypeDef *pid, float Kp, float Ki, float Kd) { pid-Kp Kp; pid-Ki Ki; pid-Kd Kd; pid-target_speed 0; pid-current_speed 0; pid-error 0; pid-last_error 0; pid-last_last_error 0; pid-output 0; pid-pwm_out 0; } float PID_Process(PID_TypeDef *pid, float target, float current) { pid-target_speed target; pid-current_speed current; pid-error target - current; // 增量式PID核心计算 float delta pid-Kp * (pid-error - pid-last_error) pid-Ki * pid-error pid-Kd * (pid-error - 2.0f * pid-last_error pid-last_last_error); // 更新误差历史 pid-last_last_error pid-last_error; pid-last_error pid-error; // 累加PWM输出并做限幅处理 pid-pwm_out delta; if (pid-pwm_out 1000) pid-pwm_out 1000; if (pid-pwm_out 0) pid-pwm_out 0; return pid-pwm_out; }这段程序的要点在于输出限幅必须放在PWM累加之后防止转速还没到目标PWM已经打满的情况。至于限幅上下限取决于CubeMX里设置的自动重载值如果PWM周期是1000那上限就是1000上限之外就是100%占空比再加已经没有意义了。再看测速部分。测速是通过定时器编码器模式读取计数值来实现的核心是用两次读取的差值除以时间间隔算出转速int16_t encoder_count (int16_t)__HAL_TIM_GET_COUNTER(htim2); __HAL_TIM_SET_COUNTER(htim2, 0); // 编码器线数13减速比304倍频每转一圈1560个脉冲 // 计数值单位是“脉冲数/定时器周期”需要换算成rpm float encoder_speed_rpm ((float)encoder_count / 1560.0f) * (60000.0f / CONTROL_PERIOD_MS);其中CONTROL_PERIOD_MS是PID控制周期单位为毫秒。这段换算的逻辑是先用计数值除以每转脉冲数得到“转了多少圈”再除以控制周期时间得到“每秒转多少圈”最后乘以60换算成rpm。这里有个小坑__HAL_TIM_GET_COUNTER返回的是32位无符号值但如果计数超过32767需要转换为int16_t否则正反转判断会出错。最后是控制周期的实现。我用的是TIM7的定时中断每10ms触发一次PID计算。中断服务函数void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM7) { // 左电机读取编码器、计算转速、PID计算、更新PWM int16_t left_count (int16_t)__HAL_TIM_GET_COUNTER(htim2); __HAL_TIM_SET_COUNTER(htim2, 0); left_speed_rpm ((float)left_count / 1560.0f) * (60000.0f / 10.0f); left_pwm (uint16_t)PID_Process(left_pid, left_target_rpm, left_speed_rpm); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, left_pwm); } }方向控制则在主循环里根据误差方向去切换if (left_pid.error 0) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); // IN11 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); // IN20 } else { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); }注意方向切换时要加死区延时也就是先让两个方向引脚都输出低电平延时几毫秒再切换方向否则L298N的H桥可能直通产生很大的冲击电流。编码器测速时还要加一点软件滤波最简单的方式是取最近三次计数值的平均值能明显减小转速波动。4. 参数整定过程与常见问题排查4.1 PID参数整定的完整步骤参数整定是整个项目里最考验耐心的一步。我调试时的方法是“先开环、再比例、后积分、最后微分”四步走第一步开环标定。把PID去掉直接给定一个固定的PWM占空比比如500观察并记录电机稳态转速。这样做的目的是确认整个硬件链路正常同时了解PWM占空比和转速的大致比例关系方便后面预估PID参数的初始值。第二步只调Kp。从Kp0.1开始逐步增大。观察实际转速到达目标转速的响应速度。Kp太小转速爬升慢Kp太大转速会围绕目标值震荡能听到电机周期性的“呜-呜”声。记下刚刚出现震荡的Kp值然后回退到它的60%到70%作为最终Kp。第三步加Ki消除稳态误差。只在Kp作用下转速通常无法稳定在目标值的精确位置上会有一个偏差这对需要精确控速的场景是不能接受的。从Ki0.01开始缓慢增大直到稳态误差消失。注意Ki过大会导致转速出现低频振荡表现为周期性的加速减速。第四步加Kd抑制超调。如果转速超过目标然后又回落到目标之下说明超调明显加入Kd后一般能改善。实际项目中我调出来的参数是Kp0.35, Ki0.08, Kd0.0210ms控制周期PWM频率10kHzMG513P30电机空载目标转速100rpm时能在0.3秒左右稳定进入±2rpm误差范围效果符合预期。4.2 常见问题排查速查表整个调试过程中踩过的坑整理成了速查表方便对应排查故障现象可能原因排查方法电机完全不转未共地、使能跳线帽没插、PWM通道配置错误检查GND是否连通ENA/ENB跳线帽是否接好用示波器看PWM引脚输出电机只能朝一个方向转IN1/IN2逻辑反了或代码方向判断有误检查GPIO初始化或调换IN1/IN2接线编码器数值跳动剧烈编码器电源不稳、AB相接反、信号受电机干扰编码器独立5V供电AB相对调测试电机线用双绞线远离信号线电机“嗡嗡”响不转PWM频率太低或占空比太小无法克服静摩擦力调大PWM频率到10kHz以上尝试加大给定占空比转速围绕目标值高频波动Kp过大减小Kp增大Kd检查编码器数据是否平滑L298N发热严重电机电流过大、散热不足、PWM频率过高降低电机负载或电源电压加散热片降低PWM开关频率有一个我特别想强调的细节编码器计数方向要和控制方向逻辑匹配。也就是说当IN1控制电机正转时定时器计数值要递增如果发现编码器计数方向反了要么调换编码器AB两相接线要么在代码里取反千万别只在PID算法里做正负号处理那样会让部分区间出现正反馈导致电机猛加速刹不住。4.3 从转速环到位置环的扩展思路这次做的实际上是最基本的转速单环PID。如果把控制周期拉长不控制转速而是去累加编码器脉冲数来当作电机输出轴转过的角度那就能很自然地扩展出位置环PID。智能车底盘做直线行驶时通常会用内环转速、外环位置的双环PID结构。这个项目做完转速环后我直接把目标转速换成了位置误差乘以一个比例系数实现了小车开固定距离停车效果已经比开环好了非常多。双环PID的调试顺序是先把内环转速环调稳再调外环位置环外环输出是内环的目标值内环的响应速度要比外环快3到5倍否则整个系统容易耦合振荡。这些都是在转速环的基础上叠加出来的所以这次把转速环PID吃透后续做更复杂的控制都会顺畅很多。4.4 调试辅助技巧PID调试过程中串口打印目标转速和实际转速是必不可少的。我习惯用串口以目标值,实际值的CSV格式输出然后在电脑上用串口示波器或者直接将数据丢进Excel里画曲线。观察曲线形状比看一串数字直觉得多Kp太大曲线会上下震荡Ki太大会出现低频喘振Kd太大会引入高频噪声。另外编码器数据建议在PID进入闭环之前先单独开环测量并打印足够长时间的转速数据确认编码器读数在固定占空比下是稳定且平滑的。这一步能帮你提前分清楚“编码器本身波动”和“PID参数不当造成的波动”。如果编码器原始数据就有大幅抖动先解决滤波和干扰问题再谈PID整定否则后续所有调试都会被带偏。最后再分享一个我踩过多次的坑当系统刚上电时PID输出初始值永远是0此时如果目标转速不为0误差会非常大PID输出增量会迅速拉满产生过冲。我的做法是启动时让目标转速从0起步以斜坡函数形式逐步增加到目标值这样既能减小启动冲击又能让曲线更平缓。处理好这一步整个系统的稳定性和使用寿命都会明显提升。本文还有配套的精品资源点击获取