1. 硬件选型与电路搭建搞过电机控制的朋友都知道硬件选型是项目成败的第一步。我这次用的主控是STM32F103C8T6也就是大家常说的蓝 pill板性价比高到离谱某宝20块钱就能拿下。驱动芯片选的TB6612这玩意儿比传统的L298N强太多了——发热量小、效率高最关键的是自带死区保护再也不用担心H桥直通烧芯片了。电机我用的是带霍尔编码器的直流减速电机型号是JGA25-370减速比30:1。这里有个坑要特别注意不同厂家的编码器线数可能不同我这款电机转一圈输出13个脉冲经过减速箱后实际输出390个脉冲/转13×30。买电机时一定要找卖家确认这个参数否则后面PID调参会出大问题。电源部分我用了12V锂电池供电通过LM2596降压模块得到5V给驱动芯片。这里有个血泪教训一定要确保单片机地和驱动芯片地可靠连接我第一次调试时就因为地线接触不良导致PWM信号紊乱电机抽风似的乱转。后来用万用表量才发现地线阻抗居然有几十欧姆...2. 核心控制原理剖析增量式PID和位置式PID最大的区别在于它不直接计算控制量而是计算控制量的增量。这就好比开车时不是直接踩油门到某个固定位置而是根据车速变化不断调整踩油门的力度。在实际电机控制中这种算法有三个明显优势不需要累加误差避免积分饱和输出变化平稳不会出现位置式PID那种突跳更易于实现手动/自动无扰动切换具体到我们的直流电机调速系统控制流程是这样的通过编码器获取实际转速脉冲→与期望脉冲比较得到误差→PID算法计算PWM增量→调整电机驱动信号。这里我直接把期望值设为脉冲数而不是转速省去了转速换算的步骤实测响应速度反而更快。3. STM32CubeMX关键配置用CubeMX配置外设能省去大量底层代码编写时间。我的配置步骤如下3.1 定时器配置TIM1用作10ms周期中断这是PID计算的节奏控制器。配置时要注意时钟树确保APB1 Timer Clocks为72MHzPrescaler设为720-1Counter Period设为1000-1开启定时器中断TIM3配置为编码器接口模式Encoder Mode选择Encoder Mode TI1 and TI2Polarity保持默认的Rising Edge计数器自动重装载值设为6553516位最大值TIM4用于PWM输出Channel3配置为PWM Generation CH3Pulse初始值设为0PWM频率设为20kHz超出人耳听觉范围避免噪音3.2 GPIO配置PA3和PA4设为输出模式控制电机转向PB8自动配置为TIM4_CH3PWM输出PA6和PA7自动配置为TIM3的编码器输入4. 增量式PID算法实现直接上干货这是经过实测可用的PID核心代码typedef struct { float Kp; float Ki; float Kd; float prev_error; float prev2_error; } PID_IncTypeDef; float PID_Incremental(PID_IncTypeDef *pid, float error) { float delta pid-Kp * (error - pid-prev_error) pid-Ki * error pid-Kd * (error - 2*pid-prev_error pid-prev2_error); pid-prev2_error pid-prev_error; pid-prev_error error; return delta; }使用时需要注意几个关键点采样周期必须固定我这里用10ms输出增量需要叠加到之前的PWM值上要对PWM输出做限幅处理0-ARR值调参经验分享先调Kp让系统有响应但不震荡然后加Kd抑制超调最后加Ki消除静差。我的电机最终参数是Kp0.8Ki0.05Kd0.3这个仅供参考不同电机差异很大。5. 编码器数据处理技巧读取编码器值时有个细节容易忽略STM32的编码器接口是16位计数器而电机长时间运转会导致计数器溢出。正确处理方式应该是int32_t Get_Encoder_Delta(void) { static uint16_t last_cnt 0; int16_t delta (int16_t)(TIM3-CNT - last_cnt); last_cnt TIM3-CNT; return delta; }这个方法巧妙地利用了int16_t的自动符号扩展特性即使计数器溢出也能正确计算出脉冲增量。实测在电机转速300RPM时10ms内的脉冲增量大约是195个390脉冲/转 × 5转/秒 × 0.01秒。6. 系统调试与优化调试时建议用VOFA这类串口示波器工具可以同时监控期望脉冲、实际脉冲、PWM输出三个关键参数。我总结的调试步骤是先开环测试固定PWM值观察电机响应加入比例控制看系统跟随性加入微分项抑制转速超调最后加积分消除稳态误差遇到电机抖动严重时可以尝试降低PWM频率我的从20kHz降到10kHz后明显改善在PID输出后加一阶低通滤波检查电源是否足够电机启动电流可能是稳态的3-5倍7. 完整工程源码解析工程采用模块化设计主要文件包括main.c系统初始化和主循环pid.c增量式PID算法实现motor.c电机驱动接口encoder.c编码器读取接口主循环中关键的处理逻辑while(1) { if(pid_timer_flag) { // 10ms定时到达 int32_t real_pulse Get_Encoder_Delta(); float error target_pulse - real_pulse; float pwm_delta PID_Incremental(pid, error); current_pwm pwm_delta; current_pwm constrain(current_pwm, 0, MAX_PWM); Set_PWM(current_pwm); pid_timer_flag 0; } }这个项目最让我有成就感的部分是解决了脉冲-转速-PWM的转换难题。最初我也纠结于要把期望值设为转速后来发现直接控制脉冲反而更直接有效。这提醒我们嵌入式开发中能用硬件解决的问题就不要用软件复杂化。
STM32增量式PID直流电机调速实战:从原理到源码解析
发布时间:2026/6/30 13:00:29
1. 硬件选型与电路搭建搞过电机控制的朋友都知道硬件选型是项目成败的第一步。我这次用的主控是STM32F103C8T6也就是大家常说的蓝 pill板性价比高到离谱某宝20块钱就能拿下。驱动芯片选的TB6612这玩意儿比传统的L298N强太多了——发热量小、效率高最关键的是自带死区保护再也不用担心H桥直通烧芯片了。电机我用的是带霍尔编码器的直流减速电机型号是JGA25-370减速比30:1。这里有个坑要特别注意不同厂家的编码器线数可能不同我这款电机转一圈输出13个脉冲经过减速箱后实际输出390个脉冲/转13×30。买电机时一定要找卖家确认这个参数否则后面PID调参会出大问题。电源部分我用了12V锂电池供电通过LM2596降压模块得到5V给驱动芯片。这里有个血泪教训一定要确保单片机地和驱动芯片地可靠连接我第一次调试时就因为地线接触不良导致PWM信号紊乱电机抽风似的乱转。后来用万用表量才发现地线阻抗居然有几十欧姆...2. 核心控制原理剖析增量式PID和位置式PID最大的区别在于它不直接计算控制量而是计算控制量的增量。这就好比开车时不是直接踩油门到某个固定位置而是根据车速变化不断调整踩油门的力度。在实际电机控制中这种算法有三个明显优势不需要累加误差避免积分饱和输出变化平稳不会出现位置式PID那种突跳更易于实现手动/自动无扰动切换具体到我们的直流电机调速系统控制流程是这样的通过编码器获取实际转速脉冲→与期望脉冲比较得到误差→PID算法计算PWM增量→调整电机驱动信号。这里我直接把期望值设为脉冲数而不是转速省去了转速换算的步骤实测响应速度反而更快。3. STM32CubeMX关键配置用CubeMX配置外设能省去大量底层代码编写时间。我的配置步骤如下3.1 定时器配置TIM1用作10ms周期中断这是PID计算的节奏控制器。配置时要注意时钟树确保APB1 Timer Clocks为72MHzPrescaler设为720-1Counter Period设为1000-1开启定时器中断TIM3配置为编码器接口模式Encoder Mode选择Encoder Mode TI1 and TI2Polarity保持默认的Rising Edge计数器自动重装载值设为6553516位最大值TIM4用于PWM输出Channel3配置为PWM Generation CH3Pulse初始值设为0PWM频率设为20kHz超出人耳听觉范围避免噪音3.2 GPIO配置PA3和PA4设为输出模式控制电机转向PB8自动配置为TIM4_CH3PWM输出PA6和PA7自动配置为TIM3的编码器输入4. 增量式PID算法实现直接上干货这是经过实测可用的PID核心代码typedef struct { float Kp; float Ki; float Kd; float prev_error; float prev2_error; } PID_IncTypeDef; float PID_Incremental(PID_IncTypeDef *pid, float error) { float delta pid-Kp * (error - pid-prev_error) pid-Ki * error pid-Kd * (error - 2*pid-prev_error pid-prev2_error); pid-prev2_error pid-prev_error; pid-prev_error error; return delta; }使用时需要注意几个关键点采样周期必须固定我这里用10ms输出增量需要叠加到之前的PWM值上要对PWM输出做限幅处理0-ARR值调参经验分享先调Kp让系统有响应但不震荡然后加Kd抑制超调最后加Ki消除静差。我的电机最终参数是Kp0.8Ki0.05Kd0.3这个仅供参考不同电机差异很大。5. 编码器数据处理技巧读取编码器值时有个细节容易忽略STM32的编码器接口是16位计数器而电机长时间运转会导致计数器溢出。正确处理方式应该是int32_t Get_Encoder_Delta(void) { static uint16_t last_cnt 0; int16_t delta (int16_t)(TIM3-CNT - last_cnt); last_cnt TIM3-CNT; return delta; }这个方法巧妙地利用了int16_t的自动符号扩展特性即使计数器溢出也能正确计算出脉冲增量。实测在电机转速300RPM时10ms内的脉冲增量大约是195个390脉冲/转 × 5转/秒 × 0.01秒。6. 系统调试与优化调试时建议用VOFA这类串口示波器工具可以同时监控期望脉冲、实际脉冲、PWM输出三个关键参数。我总结的调试步骤是先开环测试固定PWM值观察电机响应加入比例控制看系统跟随性加入微分项抑制转速超调最后加积分消除稳态误差遇到电机抖动严重时可以尝试降低PWM频率我的从20kHz降到10kHz后明显改善在PID输出后加一阶低通滤波检查电源是否足够电机启动电流可能是稳态的3-5倍7. 完整工程源码解析工程采用模块化设计主要文件包括main.c系统初始化和主循环pid.c增量式PID算法实现motor.c电机驱动接口encoder.c编码器读取接口主循环中关键的处理逻辑while(1) { if(pid_timer_flag) { // 10ms定时到达 int32_t real_pulse Get_Encoder_Delta(); float error target_pulse - real_pulse; float pwm_delta PID_Incremental(pid, error); current_pwm pwm_delta; current_pwm constrain(current_pwm, 0, MAX_PWM); Set_PWM(current_pwm); pid_timer_flag 0; } }这个项目最让我有成就感的部分是解决了脉冲-转速-PWM的转换难题。最初我也纠结于要把期望值设为转速后来发现直接控制脉冲反而更直接有效。这提醒我们嵌入式开发中能用硬件解决的问题就不要用软件复杂化。