平衡车 -- PID
🌈个人主页:羽晨同学
💫个人格言:“成为自己未来的主人~”
PID
PID是用来改善控制系统的。
接下来,我们来完成PID控制器的具体的代码。
我们创建一个PID的结构体来长期保存PID的数据。
typedef struct
{float Kp;//比例系数float Ki;//积分项的系数float Kd;//微分项的系数float SP;//用户的设定值
}PID_TypeDef;
接下来,我们创建两个函数便于初始化PID和对PID的SP的值进行设定。
//
// @简介:对PID控制器进行初始化
// @参数 Kp - 比例系数
// @参数 Ki - 积分系数
// @参数 kd - 微分系数
//
void PID_Init(PID_TypeDef*PID,float Kp,float Ki,float Kd)
{PID->Kd = Kd;PID->Ki = Ki;PID->Kp = Kp;PID->SP = 0.0f;
}
//
// @简介:改变设定值SP
// @参数 SP - 新的设定值
//
void PID_ChangeSP(PID_TypeDef*PID,float SP)
{PID->SP = SP;
}
接下来,我们来完成另外一个函数,来完成PID的运算,由于需要用到积分项和微分项,所以,我们需要额外保存几个变量。
uint64_t t_k_1;//t[k-1] 上次运行PID的时间float err_k_1;//err[k-1],上次运行PID时的误差float err_int_k_1;//err_int[k-1],上次运行的积分值
然后,我们在PID的初始化函数中,对于这几个变量进行初始化。
void PID_Init(PID_TypeDef*PID,float Kp,float Ki,float Kd)
{PID->Kd = Kd;PID->Ki = Ki;PID->Kp = Kp;PID->SP = 0.0f;PID->err_int_k_1 = 0.0f;PID->err_k_1 = 0.0f;PID->t_k_1 = 0.0f;
}
//
// @简介:执行一次PID运算
// @参数 FB - 反馈的值,也就是传感器采回的值
// @返回值:PID控制器计算的结果
//
float PID_Compute(PID_TypeDef*PID,float FB)
{float err = PID->SP - FB;uint64_t t_k = GetUs();float deltaT = (t_k - PID->t_k_1)*1.0e-6f;float err_dev = (err - PID->err_k_1)/deltaT;float err_int = PID->err_int_k_1 + (err + PID->err_k_1)*deltaT*0.5f;float COp = PID->Kp*err;float COi = PID->Ki*err_int;float COd = PID->Kd*err_dev;float CO = COp+COi+COd;//更新PID->err_int_k_1 = err_int;PID->err_k_1 = err;PID->t_k_1 = t_k;return CO;
}
PID测试
我们接下来,对左右电机进行一个测试。
我们创建两个struct对象,分别代表左右电机
static PID_TypeDef pid_motor_l;//左电机调速系统的PID控制器
static PID_TypeDef pid_motor_r;//右电机调速系统的PID控制器
//
// @简介:电机的初始化函数
//
void App_Motor_Init(void)
{ PID_Init(&pid_motor_l,0.5,7,0);//PI控制器PID_Init(&pid_motor_r,0.5,7,0);//PI控制器
}
//
// @简介:电机调速系统的进程函数
//
void App_Motor_Proc(void)
{PERIODIC(1)// #1. 通过编码器获取左右电机旋转的角速度float omega_l = App_Encoder_GetSpeed_L();float omega_r = App_Encoder_GetSpeed_R();// #2. 计算PID控制器的输出float ua_l = PID_Compute(&pid_motor_l,omega_l);float ua_r = PID_Compute(&pid_motor_r,omega_r);// #3. 将电压ua设置到点击两端float vbat = App_Bat_Get();float duty_l = ua_l/vbat * 100.0f;float duty_r = ua_r/vbat * 100.0f;App_Pwm_Set_L(duty_l);App_Pwm_Set_R(duty_r);}
然后我们创建两个函数,分别来控制左右电机的转速
//
// @简介:用来设置左电机的转速omega的值
//
void App_Motor_SetOmega_L(float omega)
{PID_ChangeSP(&pid_motor_l,omega);
}
//
// @简介:用来设置左电机的转速omega的值
//
void App_Motor_SetOmega_R(float omega)
{PID_ChangeSP(&pid_motor_r,omega);
}
PID改进
输出限幅
所以为了防止这种情况,我们在结构体中增加两个变量。
typedef struct
{float Kp;//比例系数float Ki;//积分项的系数float Kd;//微分项的系数float SP;//用户的设定值uint64_t t_k_1;//t[k-1] 上次运行PID的时间float err_k_1;//err[k-1],上次运行PID时的误差float err_int_k_1;//err_int[k-1],上次运行的积分值float UpperLimit;//上限值float LowerLimit;//下限值
}PID_TypeDef;
//
// @简介:对PID控制器进行初始化
// @参数 Kp - 比例系数
// @参数 Ki - 积分系数
// @参数 kd - 微分系数
//
void PID_Init(PID_TypeDef*PID,float Kp,float Ki,float Kd)
{PID->Kd = Kd;PID->Ki = Ki;PID->Kp = Kp;PID->SP = 0.0f;PID->err_int_k_1 = 0.0f;PID->err_k_1 = 0.0f;PID->t_k_1 = 0.0f;PID->UpperLimit = +3.4e+38f;PID->LowerLimit = -3.4e+38f;
}
//
// @简介:设置PID控制器门限
// @参数:Upper - 上限值
// @参数:Lower - 下限值
//
void PID_LimitConfig(PID_TypeDef*PID,float Upper,float Lower)
{PID -> LowerLimit = Lower;PID -> UpperLimit = Upper;
}
这样子,我们就可以在点击初始化函数中,配置PID的上下限了
//
// @简介:电机的初始化函数
//
void App_Motor_Init(void)
{ PID_Init(&pid_motor_l,0.5,7,0);//PI控制器PID_LimitConfig(&pid_motor_l,+8.4f,-8.4f);PID_Init(&pid_motor_r,0.5,7,0);//PI控制器PID_LimitConfig(&pid_motor_r,+8.4f,-8.4f);
}
这样,我们就对其上下限进行了限制。
//
// @简介:执行一次PID运算
// @参数 FB - 反馈的值,也就是传感器采回的值
// @返回值:PID控制器计算的结果
//
float PID_Compute(PID_TypeDef*PID,float FB)
{float err = PID->SP - FB;uint64_t t_k = GetUs();float deltaT = (t_k - PID->t_k_1)*1.0e-6f;float err_dev = (err - PID->err_k_1)/deltaT;float err_int = PID->err_int_k_1 + (err + PID->err_k_1)*deltaT*0.5f;float COp = PID->Kp*err;float COi = PID->Ki*err_int;float COd = PID->Kd*err_dev;float CO = COp+COi+COd;//更新PID->err_int_k_1 = err_int;PID->err_k_1 = err;PID->t_k_1 = t_k;if(CO > PID->UpperLimit) CO = PID->UpperLimit;if(CO<PID->LowerLimit) CO = PID->LowerLimit;return CO;
}
这样就实现了输出限幅
积分限幅
if(PID->err_int_k_1>PID->UpperLimit) PID->err_int_k_1 = PID->UpperLimit;if(PID->err_int_k_1<PID->LowerLimit)PID->err_int_k_1 = PID->LowerLimit;
PID复位
//
// @简介:对PID控制器进行复位
//
void PID_Reset(PID_TypeDef*PID)
{PID->err_int_k_1 = 0.0f;PID->err_k_1 = 0.0f;PID->t_k_1 = 0;
}
//
// @简介:开关电机
// @参数 on 控制电机的开关,0 - 关闭,非零 - 开启
//
void App_Motor_Cmd(uint8_t on)
{App_Pwm_Cmd(on);//在开关电机的同时需要对PID控制器进行复位PID_Reset(&pid_motor_l);PID_Reset(&pid_motor_r);
}
//
// @简介: 按键回调函数
//
static void OnUserKey_Clicked(uint8_t clicks)
{if(clicks == 1){pwm_on^=1;App_Motor_Cmd(pwm_on);}
}
首次运行时忽略积分和微分项
//
// @简介:执行一次PID运算
// @参数 FB - 反馈的值,也就是传感器采回的值
// @返回值:PID控制器计算的结果
//
float PID_Compute(PID_TypeDef*PID,float FB)
{float err = PID->SP - FB;uint64_t t_k = GetUs();float deltaT = (t_k - PID->t_k_1)*1.0e-6f;float err_dev = 0.0f;float err_int = 0.0f;if(PID->t_k_1 !=0){err_dev = (err - PID->err_k_1)/deltaT;err_int = PID->err_int_k_1 + (err + PID->err_k_1)*deltaT*0.5f;}float COp = PID->Kp*err;float COi = PID->Ki*err_int;float COd = PID->Kd*err_dev;float CO = COp+COi+COd;//更新PID->err_int_k_1 = err_int;PID->err_k_1 = err;PID->t_k_1 = t_k;if(CO > PID->UpperLimit) CO = PID->UpperLimit;if(CO<PID->LowerLimit) CO = PID->LowerLimit;if(PID->err_int_k_1>PID->UpperLimit) PID->err_int_k_1 = PID->UpperLimit;if(PID->err_int_k_1<PID->LowerLimit)PID->err_int_k_1 = PID->LowerLimit;return CO;
}