在工业控制和自动化领域,直流电机因其良好的调速性能被广泛应用。而要实现精确的转速控制,PID算法无疑是最经典且实用的解决方案。本文将详细介绍基于STM32F103C8T6微控制器的直流电机PID闭环控制系统,从硬件选型到软件实现,再到参数整定和调试技巧,分享我在实际项目中的完整经验。
这个系统的核心功能是通过霍尔编码器实时检测电机转速,使用位置式PID算法计算控制量,最终通过PWM调节L298N驱动模块的输出电压,形成完整的闭环控制。同时,系统还配备了128×160分辨率的液晶显示屏,用于实时显示设定转速和实际转速的对比曲线,极大方便了调试过程。
提示:选择STM32F103C8T6作为主控,不仅因为其性价比高(市场价约10元),更因其丰富的外设资源——多达4个通用定时器(其中TIM1和TIM8是高级定时器),特别适合电机控制应用。
这款ARM Cortex-M3内核的微控制器运行频率可达72MHz,具有64KB Flash和20KB SRAM,完全能满足电机控制的需求。其外设资源分配如下:
25GA-260编码器减速电机是一款性价比极高的直流减速电机,主要参数如下:
| 参数 | 值 | 说明 |
|---|---|---|
| 额定电压 | 12V | 工作电压范围6-12V |
| 空载转速 | 260RPM | 减速后输出轴转速 |
| 编码器类型 | 霍尔AB相 | 每转输出13个脉冲(减速前) |
| 减速比 | 1:30 | 电机轴到输出轴的减速比 |
L298N双H桥驱动模块是最经典的电机驱动方案,虽然效率不如新型MOSFET驱动,但其稳定性好、驱动能力强(峰值电流2A),非常适合初学者使用。需要注意的是,L298N内部是双H桥结构,驱动单个电机时建议并联使用以提高电流能力。
系统采用霍尔编码器检测电机转速,其工作原理是:
这种硬件计数方式相比软件中断计数更加精确可靠,特别是在高速情况下不会丢失脉冲。
编码器接口的初始化是整个转速检测的基础,必须正确配置:
c复制void Encoder_Init(void){
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = 0xFFFF; //16位计数器最大值
TIM_TimeBaseStructure.TIM_Prescaler = 0; //不分频
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
//配置编码器接口模式
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising,
TIM_ICPolarity_Rising);
//配置输入捕获滤波器
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter = 6; //10阶滤波防抖
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_Cmd(TIM3, ENABLE);
}
关键点说明:
TIM_EncoderMode_TI12表示同时使用TI1和TI2作为编码器输入转速计算通常在定时器中断中完成,基本流程是:
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;
}
重要提示:积分限幅是防止"积分饱和"的关键措施,特别是在启动阶段或目标值突变时,可以避免输出值过大导致系统失控。
TIM1配置为PWM输出模式,驱动L298N模块:
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_ClockDivision = 0;
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_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM1, ENABLE);
TIM_Cmd(TIM1, ENABLE);
}
关键参数说明:
对于初学者,试凑法是最直观的参数整定方法:
在实际项目中,我测得Ku=12.5,Tu=0.22s,按Z-N公式计算出PID参数为:
电机启动时过冲严重
转速在小范围内波动
响应速度慢
L298N模块发热严重
系统使用ST7735驱动的LCD实时显示转速曲线,为提高刷新效率,采用双缓冲机制:
曲线绘制函数优化如下:
c复制void LCD_DrawCurve(uint16_t *buf, uint16_t color){
static uint16_t prev_y = 0;
for(uint8_t x=0; x<128; x++){
uint16_t y = 160 - buf[x]/65; //转速映射到Y轴
LCD_DrawLine(x-1, prev_y, x, y, color);
prev_y = y;
}
}
经验分享:屏幕刷新率应与数据采样率匹配。当采样间隔为100ms时,刷新率设置在5-10fps即可,过高会导致闪烁,过低则曲线不连贯。
在实际运行中,必须考虑以下保护措施:
速度计算滤波:采用移动平均滤波处理转速数据
c复制#define FILTER_LEN 5
float speedFilter(float newSpeed){
static float buffer[FILTER_LEN] = {0};
static uint8_t index = 0;
float sum = 0;
buffer[index] = newSpeed;
index = (index + 1) % FILTER_LEN;
for(uint8_t i=0; i<FILTER_LEN; i++){
sum += buffer[i];
}
return sum / FILTER_LEN;
}
抗积分饱和:当误差较大时暂停积分
c复制if(fabs(pid->Err) > 100){ //误差较大时停止积分
pid->Integral = 0;
}
动态调整PID参数:根据误差大小自动调整参数
c复制if(fabs(pid->Err) > 50){ //大误差区间
pid->Kp = 8.0;
pid->Ki = 0; //大误差时去掉积分
}else{ //小误差区间
pid->Kp = 5.0;
pid->Ki = 2.0;
}
在不同负载条件下测试系统性能,结果如下:
| 测试条件 | 稳态误差(RPM) | 调节时间(s) | 超调量(%) |
|---|---|---|---|
| 空载 | ±2 | 0.8 | 5% |
| 50%负载 | ±3 | 1.2 | 8% |
| 满载 | ±5 | 1.5 | 12% |
从测试结果可以看出,系统在空载和半载条件下性能较好,满载时性能有所下降。这主要是因为L298N在满载时输出电压波动较大,影响了控制精度。如需进一步提高性能,可以考虑改用MOSFET驱动方案,如DRV8871等。
经过这个项目的实践,我深刻体会到电机控制是一个"三分理论,七分实践"的领域。PID参数在仿真中可能表现良好,但实际系统中机械惯性、电气延迟等因素会显著影响控制效果。因此,必须结合实际响应曲线进行反复调试。
对于希望进一步深入学习的开发者,我建议:
硬件方面,可以考虑:
这个项目虽然基于STM32F103实现,但其中的原理和方法同样适用于其他平台。掌握了PID控制的核心思想,就能应对各种运动控制场景的挑战。