直流电机控制是嵌入式开发中最基础也最经典的应用场景之一。在工业自动化、机器人控制、智能家居等领域,我们经常需要对电机转速进行精确控制。传统的开环控制方式难以应对负载变化带来的转速波动,而基于STM32的PID闭环控制方案能够实现±1%以内的转速控制精度。
这个项目完整实现了从硬件搭建到软件调参的全流程,特别适合刚接触电机控制的嵌入式开发者练手。我将在下文详细拆解每个环节的技术要点,包括PWM生成、编码器测速、PID算法实现等核心模块。这些知识同样适用于步进电机、伺服电机等其他类型的电机控制场景。
主控芯片:STM32F103C8T6(72MHz主频,3个通用定时器)
电机驱动模块:L298N双H桥驱动
直流电机:JGA25-370(12V/3000RPM)
编码器:增量式正交编码器
code复制STM32 PA8(TIM1_CH1) -> L298N ENA
STM32 PB6(TIM4_CH1) -> L298N IN1
STM32 PB7(TIM4_CH2) -> L298N IN2
编码器A相 -> PA0(TIM2_CH1)
编码器B相 -> PA1(TIM2_CH2)
L298N OUT1/OUT2 -> 电机正负极
注意:电机电源与MCU电源需共地,PWM频率建议10-20kHz(超出人耳听觉范围)
使用TIM1的CH1通道生成PWM:
c复制// PWM初始化代码
TIM_TimeBaseInitTypeDef TIM_Base;
TIM_OCInitTypeDef TIM_OC;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
TIM_Base.TIM_Period = 719; // 72MHz/(719+1)=100kHz
TIM_Base.TIM_Prescaler = 0;
TIM_Base.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_Base);
TIM_OC.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OC.TIM_OutputState = TIM_OutputState_Enable;
TIM_OC.TIM_Pulse = 0; // 初始占空比0%
TIM_OC.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM1, &TIM_OC);
TIM_CtrlPWMOutputs(TIM1, ENABLE);
TIM_Cmd(TIM1, ENABLE);
利用TIM2的编码器接口模式:
c复制// 编码器初始化
TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising,
TIM_ICPolarity_Rising);
TIM_SetCounter(TIM2, 0);
TIM_Cmd(TIM2, ENABLE);
// 速度计算(每100ms调用)
int32_t GetSpeed() {
static int32_t last_cnt = 0;
int32_t current_cnt = TIM_GetCounter(TIM2);
int32_t delta = (current_cnt - last_cnt) * 10; // 转换为计数/秒
last_cnt = current_cnt;
return delta * 60 / 52; // 转换为RPM
}
位置式PID实现:
c复制typedef struct {
float Kp, Ki, Kd;
float integral;
float last_error;
} PID_Controller;
float PID_Update(PID_Controller* pid, float setpoint, float feedback) {
float error = setpoint - feedback;
// 积分项抗饱和处理
if(fabs(pid->integral) < 1000) {
pid->integral += error;
}
float derivative = error - pid->last_error;
pid->last_error = error;
return pid->Kp * error +
pid->Ki * pid->integral +
pid->Kd * derivative;
}
先调P:将Ki、Kd设为0,逐步增大Kp直到系统出现等幅振荡
Ziegler-Nichols经验公式:
微调优化:
现象1:电机抖动严重
现象2:转速稳态误差大
现象3:响应超调过大
c复制// 在PID更新函数中加入积分限幅
if(pid->integral > 1000) pid->integral = 1000;
if(pid->integral < -1000) pid->integral = -1000;
// 或者采用积分分离法
if(fabs(error) < 10) { // 只在误差较小时积分
pid->integral += error;
}
根据误差大小自动切换参数:
c复制if(fabs(error) > 50) { // 大误差区间
pid->Kp = 1.0;
pid->Ki = 0;
} else { // 小误差区间
pid->Kp = 0.5;
pid->Ki = 1.5;
}
在PID输出基础上叠加前馈项:
c复制float feedforward = setpoint * 0.12; // 前馈系数需实测
float output = PID_Update(&pid, setpoint, feedback) + feedforward;
使用1000RPM阶跃信号测试:
实测中发现,电机温度升高后内阻变化会导致特性曲线偏移,建议在长时间运行时加入在线参数自整定功能。