STM32 Cubemax(七) —— 单级PID控制带编码器的直流减速电机速度
目录
STM32 Cubemax(七) —— 单级PID控制带编码器的直流减速电机速度
前言
前面的文章介绍了如何去读取带编码器电机中编码器的值,并对此值进行处理得到了电机当前的转速值。而带编码器的电机最大的作用也就是可以让我们得到电机的速度反馈,利用PID或者其他控制算法,去控制其达到我们设定的速度。
如有看电机控制相关,请先看看我的上篇博客
STM32 Cubemax(六) —— STM32利用定时器编码器模式处理带编码器直流电机
一、PID控制
PID控制作为一种经典的控制算法,如果你听过这个控制算法,那你也一定对其有算了解了,不管是书还是各种网站视频,都有讲的十分好的教程,如果对此还有不了解的,可以去b站或者CSDN上看看相关视频,具体原理这里就不多阐述了。
而我们这次要控制的对象则为电机的速度,我们很容易可以得出下图。
我们的目标即是通过编码器得到的速度值,通过PID控制器,来控制电机达到我们预定的设定值。
二、PID代码
- 我们首先创建PID.h文件并在其中定义有关PID的结构体
#define LIMIT(x,min,max) (x)=(((x)<=(min))?(min):(((x)>=(max))?(max):(x))) //限幅定义
typedef struct _PID
{
float kp,ki,kd; //PID的三个参数
float error,lastError; //当前误差和上一次的误差
float integral,maxIntegral; //积分量和积分的限幅
float output,maxOutput; //PID的输出量和PID的最大输出量
}PID;
2. 定义PID的初始化函数
//PID初始化函数
void PID_Init(PID *pid,float p,float i,float d,float maxI,float maxOut)
{
pid->kp=p;
pid->ki=i;
pid->kd=d;
pid->maxIntegral=maxI;
pid->maxOutput=maxOut;
}
3.最后是关键的PID计算函数,其实也十分简单,就是根据PID的原理编写
//单级PID计算,需要传入的参数,PID结构体,目标值,反馈值
void PID_SingleCalc(PID *pid,float reference,float feedback)
{
pid->lastError = pid->error; //更新上一次的误差
pid->error = reference-feedback; //更新当前误差
//下面分别是P,I,D的计算
pid->output += pid->error*pid->kp; //P为根据当前误差计算输出量
pid->integral += pid->error*pid->ki; //I为累计误差的输出量
LIMIT(pid->integral,-pid->maxIntegral,pid->maxIntegral); //限制I的输出,抑制超调
pid->output += pid->integral;
pid->output = (pid->error - pid->lastError)*pid->kd; //D以当前误差减去上次误差作为微分环节
LIMIT(pid->output,-pid->maxOutput,pid->maxOutput); //限制PID总输出
}
三、利用PID控制器控制电机输出
1.首先,我们先修改一下我们上次的Motor结构体,添加与PID相关的参数
typedef struct _Motor
{
int32_t lastAngle; //上次计数结束时转过的角度
int32_t totalAngle; //总共转过的角度
int16_t loopNum; //电机计数过零计数
float speed; //电机输出轴速度
float targetSpeed; //添加设定的目标速度
PID pid; //添加电机对应PID
}Motor;
2.在我们上一次的电机代码中加入PID.h和PID.c文件 ,并创建给电机发送控制的代码
//电机发送指令
void Motor_Send(enum Mode mode)
{
PID_SingleCalc(&motor.pid, motor.targetSpeed, motor.speed); //进行PID计算,传入我们设定的目标值targetSpeed,和我们上篇文章处理编码器值得到的speed值
output = motor.pid.output;
if(output > 0) //对应正转
{
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_2, (uint32_t)output);
IN1(0);
IN2(1);
}
else //对应反转
{
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_2, (uint32_t)(-output));
IN1(1);
IN2(0);
}
}
四、如何确定PID中的参数
在我们的结构体中,我们定义了kp,ki,kd,还有maxIntegral积分限幅,maxOutput输出限幅,意味着我们需要在PID初始化时确定这五个参数。
其中比较好确定的是maxOutput即输出限幅,这个间接表达了我们的最大占空比,在此处,因为我们PWM设定的counter period为10000,则maxoutput当然也是设定为10000。
而其他参数,我一般使用时先将参数全部设定为0,然后在debug中进行调整,最后在确定出最优的参数
PID_Init(&motor.pid, 0, 0, 0, 0, 10000);
在debug中我们打开对Motor这个参数进行观察,然后点开PID这一栏
然后我们就可以对其中的变量进行确定了。
对于此电机,一般我们只需要确定PI参数即可。我们先给电机targetspeed设定一个合适的值,因为我们的单位是RPM,每分钟多少转,我们可以设定为60.
然后先确定P的参数,从一个较小的值开始给,一直增加到感觉到电机有明显的震动,然后在慢慢的减少,直到电机不再震动,将此时的值,乘以0.7左右,即是P比较优的值
I值的确定,要和I的限幅一起确定。在我们确定好P的值后,会发现此时距离目标值仍有误差error,我们就需要通过I的值来消除这个error。而I的限幅则是防止I过饱和现象的出现,即当电机出现堵转时,防止一I积累过冲而导致输出一下过大的情况,这个可以通过在调节I时,观察error值的变化来确定,以防止出现error正负经常变化的情况。
总结
本博客只是以自己实际调节的PID中使用的代码,及经验讲解,理论部分不多,毕竟PID的理论已经比较普及了,以我学生的身份去理解,肯定也没有书上讲的好,如果需要对此有比较深刻的认识,可以去网上看看书本中的例子来进一步理解。