要构建一个完整的电机PID双环控制系统,首先需要准备好硬件环境。我这里使用的是STM32F4系列开发板,搭配一个带编码器的直流有刷电机。编码器采用AB相输出,通过定时器的编码器接口模式进行脉冲计数。
在CubeMX中配置引脚时,需要注意几个关键点:
具体到代码实现,方向控制的函数可以这样写:
c复制void Motor_SetDirection(DirectionType dir)
{
switch(dir) {
case DIR_FORWARD:
HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_SET);
break;
case DIR_BACKWARD:
HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_RESET);
break;
case DIR_STOP:
default:
HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_SET);
}
}
编码器数据采集是整个控制系统的基础。我遇到过不少问题,比如脉冲丢失、计数方向错误等。经过多次调试,总结出几个关键点:
首先,定时器配置要正确。在CubeMX中,选择编码器模式时,需要设置正确的计数边沿和极性。我通常使用TI1和TI2都上升沿触发,这样分辨率最高。
编码器数据处理的核心函数如下:
c复制int32_t Encoder_GetCount(void)
{
static int32_t total_count = 0;
int16_t current_count = (int16_t)__HAL_TIM_GET_COUNTER(&htim2);
total_count += current_count;
__HAL_TIM_SET_COUNTER(&htim2, 0);
return total_count;
}
这里有个细节需要注意:定时器的计数器是16位的,当电机长时间运行时可能会溢出。所以需要使用一个32位的变量来累计总脉冲数。我在实际项目中就遇到过因为没考虑溢出导致的位置计算错误。
速度计算通常采用M法测速,即在固定时间间隔内统计脉冲数。计算公式为:
code复制速度 = (脉冲数 × 60) / (编码器线数 × 测速周期(ms) × 减速比)
PID控制是整个系统的核心。我建议先实现单环控制,稳定后再扩展到双环。这里分享我调试过程中总结的经验。
增量式PID适合速度环控制,代码实现如下:
c复制typedef struct {
float kp;
float ki;
float kd;
float max_output;
float max_integral;
float last_error;
float prev_error;
float integral;
} PID_IncTypeDef;
float PID_IncCalculate(PID_IncTypeDef *pid, float target, float feedback)
{
float error = target - feedback;
float p_out = pid->kp * (error - pid->last_error);
float i_out = pid->ki * error;
float d_out = pid->kd * (error - 2*pid->last_error + pid->prev_error);
float output = p_out + i_out + d_out;
// 积分限幅
pid->integral += i_out;
if(pid->integral > pid->max_integral) pid->integral = pid->max_integral;
else if(pid->integral < -pid->max_integral) pid->integral = -pid->max_integral;
pid->prev_error = pid->last_error;
pid->last_error = error;
// 输出限幅
if(output > pid->max_output) output = pid->max_output;
else if(output < -pid->max_output) output = -pid->max_output;
return output;
}
位置式PID适合位置环控制,实现代码如下:
c复制typedef struct {
float kp;
float ki;
float kd;
float max_output;
float max_integral;
float last_error;
float integral;
} PID_PosTypeDef;
float PID_PosCalculate(PID_PosTypeDef *pid, float target, float feedback)
{
float error = target - feedback;
pid->integral += error;
if(pid->integral > pid->max_integral) pid->integral = pid->max_integral;
else if(pid->integral < -pid->max_integral) pid->integral = -pid->max_integral;
float d_error = error - pid->last_error;
float output = pid->kp * error + pid->ki * pid->integral + pid->kd * d_error;
pid->last_error = error;
if(output > pid->max_output) output = pid->max_output;
else if(output < -pid->max_output) output = -pid->max_output;
return output;
}
调参是个需要耐心的过程。我通常先用纯P控制,逐渐增加P值直到系统开始振荡,然后取这个值的60%作为初始P值。接着加入I项消除静差,最后加入D项抑制超调。
双环控制的核心思想是:位置环的输出作为速度环的目标值。这种串级结构能同时保证位置精度和动态响应。
控制周期对系统性能影响很大。我的经验是:
可以使用定时器中断来实现:
c复制void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint32_t speed_cnt = 0;
static uint32_t position_cnt = 0;
if(htim == &htim6) { // 1ms定时器
speed_cnt++;
position_cnt++;
// 速度环控制
if(speed_cnt >= SPEED_LOOP_INTERVAL) {
speed_cnt = 0;
int32_t count = Encoder_GetCount();
float speed = (count * 60.0f) / (ENCODER_LINES * GEAR_RATIO * SPEED_LOOP_INTERVAL * 0.001f);
float speed_out = PID_IncCalculate(&speed_pid, target_speed, speed);
Motor_SetPWM((int32_t)speed_out);
}
// 位置环控制
if(position_cnt >= POSITION_LOOP_INTERVAL) {
position_cnt = 0;
int32_t position = Encoder_GetTotalCount();
target_speed = PID_PosCalculate(&position_pid, target_position, position);
}
}
}
双环控制中容易出现积分饱和问题。我常用的解决方法有:
PWM输出直接关系到电机性能。在STM32中,通常使用定时器的PWM模式。配置时要注意:
PWM设置函数示例:
c复制void Motor_SetPWM(int32_t pwm)
{
pwm = LIMIT(pwm, -MAX_PWM, MAX_PWM);
if(pwm >= 0) {
Motor_SetDirection(DIR_FORWARD);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwm);
} else {
Motor_SetDirection(DIR_BACKWARD);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, -pwm);
}
}
实际项目中,我还增加了软启动和过流保护功能。软启动可以避免电机启动时的电流冲击,过流保护则可以防止硬件损坏。
调试PID控制系统时,我总结了一些实用技巧:
常见问题及解决方法:
我在实际项目中遇到过编码器计数方向与电机转向相反的问题,导致系统不稳定。解决方法是在编码器数据处理时乘以-1校正方向。
当基础功能实现后,可以考虑以下优化:
一个简单的参数自整定思路是:
我在一个机械臂项目中采用了模糊PID控制,相比传统PID,在负载变化时表现更稳定。