尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

STM32直流有刷电机PID速度闭环控制:从编码器测速到算法实现

发布时间:2026/9/4 13:43:42

资讯中心
01
ARTICLE

STM32直流有刷电机PID速度闭环控制:从编码器测速到算法实现

STM32直流有刷电机PID速度闭环控制:从编码器测速到算法实现
简介本资源是一套基于STM32F10x系列微控制器实现直流有刷电机闭环速度控制的完整嵌入式开发工程面向嵌入式初学者、电机控制实践者及高校电类专业课程设计学生解决直流电机转速测量不稳、PID参数整定困难、软硬件协同调试复杂等典型问题。压缩包共135个文件含31个头文件.h定义外设与PID结构体、29个C源文件.c涵盖TIM编码器测速、ADC采样、PWM驱动及核心PID算法实现另有编译中间文件.o/.d、调试配置.dbgconf、链接脚本.sct及可执行镜像.hex/.axf整体体积仅1.35MB轻量易部署。已有555人学习下载工程采用标准STM32固件库架构模块清晰——从stm32f10x_tim.c等底层驱动到自定义speed_pid.c控制逻辑附带Bak备份文件与多版本工程配置便于对比调试与参数迭代是理解电机测速原理、PID离散化实现及STM32实时控制流程的优质实操范例。1. 项目背景与核心目标最近在做一个两轮差速小车的项目核心任务之一就是让小车能稳定地跑直线或者按照设定的速度巡航。这听起来简单但实际一上手就发现用STM32驱动直流有刷电机想让它的转速精准可控真不是给个PWM信号就完事了。电机负载一变或者电池电压波动速度马上就飘了。为了解决这个问题我决定从最基础的测速开始一步步搭建一个完整的PID速度闭环控制系统。这个“直流有刷电机测速 -PID控制”项目本质上就是解决嵌入式运动控制中最经典的问题如何让电机“听话”。它非常适合正在学习STM32、想从点灯进阶到实际控制应用的朋友或者那些在做智能车、机器人底盘但被电机速度波动困扰的开发者。整个过程会涉及硬件选型编码器、定时器的高级应用编码器模式、输入捕获、PID算法的原理与实现以及如何将这三者有机整合形成一个稳定的控制回路。通过这个项目你不仅能学会让电机转起来更能理解如何让它转得又快又稳。2. 硬件方案选型与电路设计要点要让电机“听话”首先得知道它“现在转得多快”。这就离不开速度反馈传感器。对于直流有刷电机最常用、性价比最高的方案就是增量式光电编码器。市面上常见的编码器有100线、200线、500线甚至更高线数越高测速分辨率越高但价格也越贵对MCU的计数能力要求也越高。对于一般的智能小车应用200线或500线的编码器是完全足够的。编码器一般输出A、B两相正交脉冲信号有些还会带一个Z相零位信号。A、B两相信号相位差90度通过判断两者的相位关系我们可以知道电机的旋转方向通过计算单位时间内的脉冲数我们就可以算出转速。STM32的定时器有个非常强大的“编码器接口模式”可以硬件自动识别A、B相的边沿并据此自动增减计数器的值大大减轻了CPU的负担。硬件连接上编码器的A、B相需要接到STM32的定时器通道上并且这两个引脚必须属于同一个定时器的两个特定通道例如TIM2的CH1和CH2。电机的驱动我选用的是经典的TB6612FNG驱动芯片它比L298N效率高、发热小。STM32通过PWM信号控制TB6612的输入引脚从而控制电机的速度和方向。这里有一个细节给电机供电的电源一定要和给STM32及编码器供电的电源做好隔离至少要通过磁珠或电感进行滤波否则电机启停时产生的大电流波动会严重影响MCU和编码器信号的稳定性导致系统异常复位或测速不准。我的做法是使用两路独立的DC-DC降压模块一路给电机驱动供电另一路给控制板供电。注意编码器的信号线建议使用双绞线并且靠近MCU的一端最好加上拉电阻通常10kΩ即可和滤波电容几十到一百皮法可以有效抑制长线传输带来的振铃和毛刺提高计数可靠性。3. STM32定时器编码器模式配置详解配置编码器接口是测速准确与否的关键。我们以STM32F103系列使用TIM2的CH1和CH2为例。在STM32CubeMX中配置非常简单但理解背后的寄存器操作更有助于排查问题。首先将连接到编码器A、B相的GPIO口配置为复用功能模式Alternate Function并选择对应的定时器通道。然后进入定时器配置编码器模式Encoder Mode选择“Encoder Mode TI1 and TI2”。这个模式意味着定时器会在TI1CH1和TI2CH2的每个边沿都计数。极性Polarity通常选择“Rising Edge”但具体要看你的编码器输出信号在空闲状态是高电平还是低电平。确保电机正转时计数器是增加的。预分频器Prescaler设置为0即不对计数时钟进行分频。计数周期Counter Period设置为最大值65535对于16位定时器。这个值决定了计数器的溢出上限在测速计算中很重要。自动重装载值AutoReload同样设置为65535。当计数器达到此值时会产生更新事件并清零或重装载同时会触发溢出中断。配置完成后启动定时器调用HAL_TIM_Encoder_Start()。此时定时器的计数器CNT寄存器就会随着编码器的脉冲实时变化。正转时CNT增加反转时CNT减少。但是这里有一个巨大的“坑”计数器溢出处理。编码器高速旋转时16位的计数器很容易就计满了。如果只是简单地读取CNT值做差一旦发生溢出计算出的脉冲数就是错误的。例如本次读数为65000下次读数为1000看似减少了64000个脉冲实际可能是正向溢出了增加了2000个脉冲。为了解决这个问题我们必须扩展计数器的位数。一个经典的做法是在定时器的更新中断溢出中断服务函数中用一个有符号的32位甚至64位全局变量如g_encoder_total_ticks来记录溢出的次数。当正向溢出时这个变量加上65536反向溢出下溢时减去65536。这样我们获取的总脉冲数就是total_ticks g_encoder_total_ticks TIMx-CNT。这个total_ticks是一个可以连续变化的大范围有符号数真实反映了电机累计转过的角度。// 在定时器更新中断回调函数中 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { if (__HAL_TIM_IS_TIM_COUNTING_DOWN(htim2)) { // 判断计数方向向下计数代表正转取决于编码器接线 g_encoder_total_ticks 65536; // 正向溢出 } else { g_encoder_total_ticks - 65536; // 反向溢出 } } }4. 速度计算从脉冲数到转速RPM有了精确的累计脉冲数下一步就是在固定时间间隔内计算速度。我们定义一个速度计算周期比如10ms100Hz。在每个周期开始时记录当前的total_ticks为ticks_last周期结束时再次读取total_ticks为ticks_now。那么在这个周期内产生的脉冲数delta_ticks ticks_now - ticks_last。转速的计算公式为速度 (RPM) (delta_ticks / (编码器线数 * 4)) / (采样周期(秒)) * 60这里需要解释几个关键点编码器线数 * 4这是因为我们使用了定时器的编码器模式在A、B相每个边沿都计数。对于一个200线的编码器电机转一圈A相会产生200个脉冲B相也会产生200个脉冲且边沿数是脉冲数的2倍。在“Encoder Mode TI1 and TI2”模式下定时器会对两个信号的所有边沿上升沿和下降沿进行计数因此电机转一圈计数器会计数200线 * 4倍频 800个计数。这就是所谓的“四倍频”它大大提高了测角分辨率。采样周期单位是秒。如果我们的计算周期是10ms那么采样周期就是0.01秒。乘以60将“转每秒”转换为更常用的“转每分钟”RPM。所以对于一个200线编码器10ms采样周期公式简化为速度 (RPM) delta_ticks / (200 * 4) / 0.01 * 60 delta_ticks * 0.75速度 (RPM) delta_ticks * (60 / (200*4*0.01)) delta_ticks * (60 / 8) delta_ticks * 7.5这个计算过程最好放在一个定时中断服务函数中完成以确保采样周期的绝对准确。我通常使用一个基本定时器如TIM6来产生精确的10ms中断在这个中断里进行速度采样和计算并将计算出的速度值存入一个全局变量供PID控制器读取。实操心得速度值最好进行一下软件滤波。因为机械振动或偶尔的脉冲丢失/干扰会导致单次计算的速度值有毛刺。一个简单有效的一阶低通滤波或称指数加权平均就能让速度曲线平滑很多filtered_speed 0.8 * filtered_speed_last 0.2 * current_raw_speed。系数可以根据实际效果调整。5. PID控制算法原理与离散化实现得到了电机的实时速度我们就可以用PID控制器来调节PWM输出使速度趋近于我们设定的目标值。PID是比例Proportional、积分Integral、微分Derivative控制的缩写。它的思想非常直观比例P当前误差有多大我就按比例给多大的“力”。误差越大输出越大。它决定了系统的响应速度但纯比例控制会有静差稳态误差。积分I把历史上所有的误差累积起来。它能消除静差因为只要误差不为零积分项就会不断累积直到输出足够大来抵消这个误差。但积分太强会导致系统超调甚至振荡。微分D预测误差未来的变化趋势。如果误差正在快速减小我就提前减小输出防止“冲过头”。它能增加系统稳定性抑制超调但对噪声非常敏感。在数字系统中我们需要离散化的PID算法。最常用的是位置式PID和增量式PID。位置式PID直接计算控制量的绝对大小而增量式PID计算的是控制量的增量。对于电机PWM控制我更喜欢用位置式PID因为PWM占空比本身就是一个绝对量。离散化的位置式PID公式如下u(k) Kp * e(k) Ki * sum(e(j)) Kd * [e(k) - e(k-1)]其中u(k)是当前时刻的控制输出即PWM占空比。e(k)是当前时刻的误差e(k) 目标速度 - 当前测量速度。sum(e(j))是误差的累加和即积分项。[e(k) - e(k-1)]是本次误差与上次误差的差值近似为微分。在代码中我们需要维护几个全局变量上次误差error_last、误差累计和error_sum。同时必须对积分项进行抗饱和处理Integral Anti-windup这是PID调参稳定与否的关键。当输出已经达到极限比如PWM最大占空比100%时如果误差依然存在积分项会无限制地累加“饱和”导致系统退出饱和区时产生巨大的超调。简单的处理方法是在计算输出后如果输出饱和了则停止积分项的累加。typedef struct { float target; // 目标值 float measure; // 测量值 float error; // 当前误差 float error_last; // 上次误差 float error_sum; // 误差积分和 float kp, ki, kd; // PID参数 float output; // 输出值 float output_max; // 输出上限 float output_min; // 输出下限 float integral_max; // 积分限幅 } PID_Controller; float PID_Calculate(PID_Controller *pid) { pid-error pid-target - pid-measure; // 比例项 float p_out pid-kp * pid-error; // 积分项带抗饱和 pid-error_sum pid-error; // 积分限幅 if (pid-error_sum pid-integral_max) pid-error_sum pid-integral_max; if (pid-error_sum -pid-integral_max) pid-error_sum -pid-integral_max; float i_out pid-ki * pid-error_sum; // 微分项 float d_out pid-kd * (pid-error - pid-error_last); // 计算总输出 pid-output p_out i_out d_out; // 输出限幅 if (pid-output pid-output_max) pid-output pid-output_max; if (pid-output pid-output_min) pid-output pid-output_min; // 更新上次误差 pid-error_last pid-error; return pid-output; }6. PID参数整定从理论到实践的调参过程PID参数整定是个经验活但遵循一定步骤可以少走弯路。我的调参顺序是先P再I最后D。初始化将Ki和Kd设为0输出限幅设置为PWM的有效范围如0-1000对应0%-100%占空比。设定一个较低的目标速度例如50 RPM。调节比例系数Kp逐渐增大Kp直到系统开始出现持续的、小幅度的振荡。此时系统的响应很快但无法稳定在目标值。记下这个使系统开始振荡的Kp值称为“临界Kp”。引入积分系数Ki将Kp设置为“临界Kp”的60%左右。然后逐渐增加Ki。积分的作用是消除静差。你会发现随着Ki增大系统能慢慢稳定到目标值但响应可能会变慢或者出现超调后缓慢回落的现象。调整Ki使系统既能消除静差又不会引起明显的超调或振荡。如果出现振荡说明Ki太大了。引入微分系数Kd可选如果系统在加入PI后达到稳定的速度较慢或者超调量比较大可以尝试加入微分。微分项对噪声敏感所以实际使用时微分项常常会和一个低通滤波器一起使用不完全微分。从很小的Kd值开始比如Kp的十分之一观察系统响应。合适的Kd能有效抑制超调让曲线更平滑地接近目标值。但Kd过大反而会引入高频抖动。现场微调上述是在固定负载下的调试。实际小车运行中负载如爬坡、地面摩擦都会变。因此需要在各种典型场景下空载、负重、启动、急停测试对参数进行微调找到一个在大多数情况下都表现稳健的折中值。一个实用的技巧是动态调整积分限幅。在电机启动初期误差很大积分项会快速累积容易导致启动过冲。我们可以设置一个较小的初始积分限幅或者在一开始将积分项清零等速度接近目标值后再开启积分这被称为“积分分离”。7. 系统整合与闭环控制流程搭建现在我们把测速和PID控制两个模块串起来形成一个完整的闭环。整个系统的运行基于两个定时器中断定时器A高优先级负责高速、精确的电机PWM生成。通常使用高级定时器如TIM1的PWM输出模式频率设置在10kHz-20kHz之间这个频率远超电机响应频率且人耳听不到噪音。定时器B低优先级负责速度采样与PID计算。我使用TIM6基本定时器产生10ms的周期性中断。在这个中断服务函数中依次执行以下步骤速度采样调用速度计算函数获取当前滤波后的电机转速current_speed。PID计算将current_speed和target_speed传入PID计算函数得到本次的控制量pwm_duty。输出限幅与更新确保pwm_duty在有效范围内然后更新PWM比较寄存器的值如__HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, pwm_duty)从而改变电机驱动芯片的输入信号。可选数据记录将目标速度、实际速度、PWM占空比等数据存入数组方便后续通过串口发送到上位机进行波形分析。这是调参的利器。整个流程形成了一个负反馈闭环速度低了 - 误差为正 - PID输出增大 - PWM占空比增大 - 电机加速 - 速度升高 - 误差减小。如此循环最终将速度稳定在目标值附近。踩坑实录中断优先级设置不当会导致系统卡死。务必确保PID计算中断TIM6的优先级低于编码器计数中断TIM2更新中断。因为编码器计数需要及时响应如果被PID计算长时间阻塞会导致脉冲丢失测速不准。同时PID计算中断本身不能执行太长时间的计算避免影响其他任务。如果发现系统反应迟钝可以用逻辑分析仪或示波器检查中断是否被意外阻塞。8. 上位机调试与波形分析让调参可视化“盲调”PID参数是非常痛苦的。我们必须让数据“说话”。一个简单的方法是使用STM32的串口将每个控制周期10ms的目标速度、实际速度、PWM输出值打包发送到电脑。在电脑端我们可以用任何能绘制曲线的工具来接收并显示比如SerialPlot、MATLAB或者自己用Pythonmatplotlib库写一个简单的绘图程序。通过观察波形我们可以清晰地看到阶跃响应突然改变目标速度看实际速度跟踪的快慢、超调大小、稳定时间。抗扰性能在电机稳定运行时用手轻轻捏住车轮增加负载看速度下跌多少恢复速度有多快。稳态误差稳定后实际速度与目标速度的差值是否在可接受范围内。例如如果响应曲线上升很慢说明Kp太小如果超调严重且振荡说明Kp太大或Ki太大如果负载变化后速度恢复很慢说明Ki不够大。有了波形作为依据调参就从“玄学”变成了“科学”。我常用的数据打包格式是简单的CSV目标值,测量值,输出值\n。在串口中断中快速发送在上位机按逗号分割即可。为了减少串口数据量对系统实时性的影响也可以每5个或10个周期发送一次数据。9. 常见问题排查与性能优化在实际部署中你可能会遇到以下问题问题一电机低速时速度测量跳动大甚至出现反转计数。原因编码器信号质量差在电平临界点抖动。电机低速时脉冲间隔时间长抖动的影响更明显。解决硬件上确保编码器供电稳定信号线加上拉电阻和滤波电容。软件上除了前面提到的一阶滤波可以尝试“速度死区”处理。当计算出的速度绝对值小于某个阈值如5 RPM时直接认为速度为零。或者使用M法测速测量固定时间内的脉冲数在低速时误差大可以切换到T法测速测量相邻两个脉冲的时间间隔但T法在高速时精度差。更高级的方法是M/T法结合两者优点。问题二PID输出控制电机时电机发出“滋滋”声或抖动。原因PWM频率不在合适的范围内。频率太低如1kHz以下电机会有明显的噪音和振动频率太高驱动芯片的开关损耗会增大。解决将电机驱动PWM频率设置在10kHz到20kHz之间。这个频率段通常能兼顾效率和静音。检查你的定时器预分频和自动重装载值设置是否正确。问题三系统响应很快但一加上负载速度就掉下去恢复很慢。原因积分项Ki不足导致系统对抗负载扰动的能力鲁棒性差。解决适当增大Ki。但要注意增大Ki的同时可能需要重新微调Kp并检查积分限幅是否合理防止积分饱和。问题四代码跑起来后电机完全不受控或者PWM输出异常。排查链路查硬件用万用表或示波器检查PWM输出引脚是否有波形电压幅值是否正确。检查电机驱动芯片的输入逻辑和输出端。查初始化确认定时器编码器、PWM、采样定时器是否都成功启动HAL_TIM_Base_Start()HAL_TIM_PWM_Start()。查中断确认采样定时器中断是否正常进入。可以在中断服务函数里翻转一个LED灯来测试。查数据流在PID计算函数前后打印出关键变量目标值、测量值、误差、输出值看计算逻辑是否正确。特别检查测量速度值是否正常如果速度一直为0闭环就无法建立。性能优化建议将PID计算中的浮点运算改为定点数运算Q格式可以大幅提升在无FPU的Cortex-M3/M4内核上的计算速度。如果控制多个电机可以考虑将它们的PID计算分散在不同的定时器中断中避免一个中断内计算量过大。对于更复杂的场景如位置控制、速度规划可以考虑引入更高级的控制算法如模糊PID、前馈控制等但经典PID对于大多数速度控制场景已经足够强大和稳定。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。