1. STM32直流电机PID控制实战解析
最近完成了一个基于STM32的直流电机转速闭环控制项目,使用PID算法实现精确调速。这个看似基础的项目实际暗藏不少玄机,从编码器信号处理到PID参数整定,每个环节都可能成为性能瓶颈。下面分享我的完整实现过程和踩坑经验。
2. 硬件架构设计
2.1 核心组件选型
主控选用STM32F103C8T6,这款Cortex-M3内核的MCU性价比极高,72MHz主频足够处理常规控制算法。电机驱动模块采用经典的L298N,其最大输出电流2A,足以驱动小型直流电机。关键部件清单如下:
| 组件 | 型号 | 关键参数 |
|---|---|---|
| 主控芯片 | STM32F103C8T6 | 64KB Flash, 20KB RAM, 3个定时器 |
| 电机驱动 | L298N | 双H桥, 2A持续电流 |
| 编码器电机 | 25GA-260 | 减速比1:260, AB相霍尔输出 |
| 显示屏 | ST7735 | 128x160分辨率, SPI接口 |
注意:L298N需配合散热片使用,长时间工作建议加装散热风扇。电机电源与逻辑电源务必隔离,避免干扰MCU运行。
2.2 电路设计要点
- 电源隔离:电机驱动部分使用独立12V电源,通过LM2596降压至5V给逻辑电路供电
- 信号滤波:编码器信号线上并联104电容,软件端配置定时器输入滤波
- 保护电路:电机输出端加装续流二极管,防止反电动势损坏驱动芯片
3. 编码器信号处理
3.1 硬件接口配置
25GA-260电机自带霍尔编码器,输出AB两相脉冲。将这两路信号接入TIM3的CH1和CH2引脚,利用STM32硬件编码器接口实现四倍频计数:
c复制void Encoder_Init(void){
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseStructure.TIM_Period = 0xFFFF; //16位计数器
TIM_TimeBaseStructure.TIM_Prescaler = 0; //不分频
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
//配置为编码器模式3(双沿计数)
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising,
TIM_ICPolarity_Rising);
TIM_ICInitStructure.TIM_ICFilter = 6; //设置10阶滤波
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_Cmd(TIM3, ENABLE);
}
3.2 速度计算策略
在100ms定时中断中读取计数值并换算转速:
c复制void TIM4_IRQHandler(void){
if(TIM_GetITStatus(TIM4, TIM_IT_Update)){
// 读取编码器计数值
int16_t cnt = TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3, 0); //立即清零
// 计算转速RPM
// 电机转一圈产生13个脉冲×4倍频=52计数
// 减速比260:1 → 输出轴转一圈需要52*260=13520计数
float rpm = (cnt * 600.0f) / (13520 * 0.1f);
motor.speed = rpm;
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
}
}
实测发现:滤波值设为6时既能有效抑制抖动,又不会丢失有效脉冲。低于4时会出现误计数,高于8则可能丢失快速脉冲。
4. PID算法实现
4.1 位置式PID结构体
c复制typedef struct{
float SetSpeed; //目标转速
float ActualSpeed; //实际转速
float Err; //当前误差
float ErrLast; //上次误差
float Kp,Ki,Kd; //PID参数
float Integral; //积分项
float Output; //输出值
}PID;
void PID_Calc(PID *pid){
pid->Err = pid->SetSpeed - pid->ActualSpeed;
pid->Integral += pid->Err;
//积分限幅防止过冲
if(pid->Integral > 2000) pid->Integral = 2000;
if(pid->Integral < -2000) pid->Integral = -2000;
pid->Output = pid->Kp * pid->Err
+ pid->Ki * pid->Integral
+ pid->Kd * (pid->Err - pid->ErrLast);
pid->ErrLast = pid->Err;
}
4.2 参数整定方法
试凑法步骤:
- 先将Ki和Kd设为0,逐步增大Kp直到系统出现持续振荡
- 记录此时Kp值(Ku)和振荡周期(Tu)
- 按Ziegler-Nichols公式计算:
- Kp = 0.6*Ku = 7.5
- Ki = 2*Kp/Tu = 68.18
- Kd = Kp*Tu/8 = 0.206
实际调试技巧:
- 先只启用P控制,观察系统响应
- 加入D项抑制超调,但过大的Kd会导致高频抖动
- 最后加入I项消除静差,需严格限制积分范围
5. PWM输出配置
5.1 定时器初始化
c复制void PWM_Init(uint16_t arr, uint16_t psc){
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
//时基配置
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler = psc;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
//PWM输出配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; //初始占空比0%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
//死区时间配置(关键!)
TIM_BDTRInitStructure.TIM_DeadTime = 0x3F; //约5us死区
TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);
TIM_CtrlPWMOutputs(TIM1, ENABLE);
TIM_Cmd(TIM1, ENABLE);
}
5.2 安全保护机制
- 电流检测:通过ACS712检测电机电流,超过1.5A立即切断PWM
- 软件限幅:限制PWM占空比变化率,避免突变导致电流冲击
- 堵转保护:持续3秒转速为0时自动停机
6. 液晶显示实现
6.1 双缓冲刷新机制
c复制uint16_t lcd_buf[2][128]; //双缓冲区
uint8_t display_buf = 0;
void LCD_RefreshTask(void){
//非当前显示缓冲区写入新数据
uint8_t write_buf = 1 - display_buf;
for(int i=0; i<128; i++){
lcd_buf[write_buf][i] = speed_history[(history_ptr+i)%128];
}
//切换缓冲区
LCD_DrawCurve(lcd_buf[write_buf], RED);
display_buf = write_buf;
}
6.2 曲线绘制优化
转速值到屏幕坐标的映射公式:
c复制y = LCD_HEIGHT - (speed * LCD_HEIGHT / MAX_SPEED);
其中MAX_SPEED根据电机特性设定,我的项目中为300RPM。
显示刷新率建议控制在20-30FPS,过高会导致闪烁,过低则曲线不连贯。使用DMA传输可显著降低CPU负载。
7. 系统调试经验
7.1 典型问题排查
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 转速波动大 | 编码器信号干扰 | 加强硬件滤波,检查接线屏蔽 |
| 电机启动困难 | PID积分饱和 | 设置积分限幅,加入抗饱和处理 |
| L298N发烫 | 死区时间不足 | 增大死区时间,检查散热条件 |
| 曲线显示卡顿 | 刷新率过高 | 启用双缓冲,降低刷新频率 |
7.2 调试技巧
- 分阶段验证:先测试开环PWM控制,再验证编码器读数,最后实现闭环
- 参数记录:保存不同PID参数下的响应曲线,对比分析
- 实时监控:通过串口实时输出关键变量,辅助分析系统状态
这个项目让我深刻体会到控制系统的复杂性——理论计算只是起点,实际调试中电机惯性、电路延迟、信号噪声等因素都会显著影响性能。建议在初期就建立完善的调试手段,比如我后期添加的曲线对比显示功能,极大提升了调试效率。