智能车笔记-PID闭环
本帖最后由 小伤口 于 2022-4-3 21:54 编辑什么是PID
PID 是一种经典的控制算法
P-比例(proportional)、I-积分(integral)、D-微分(derivative)
PID控制的实质就是对目标值和实际值误差进行比例、积分、微分运算后的结果用来作用在输出上。
为什么要使用PID算法
以小车为例,相同的占空比在经过小车运行一段时间后,会变慢,因为电压相比之前降低了,虽然占空比没有变化
但随着电压降低,电机的电压也会随着降低,速度自然就变慢了,因此,我们需要在电压降低的同时,增大占空比,提高作用在
电机的电压从而维持小车的速度,但是增大多少占空比呢,这时就需要用到 PID 算法了
关于PID的比例,积分,微分详解,网上已经有许多资料,这里不再做解释
位置式PID 代码
先声明一个PID结构体
typedef struct
{
float kp; //P
float ki; //I
float kd; //D
float out_p;//KP输出
float out_i;//KI输出
float out_d;//KD输出
float out; //pid输出
float integrator; //< 积分值
float last_error; //< 上次误差
}pid_param_t;
PID 实现代码
/*************************************************************************
*函数名称:float constrain_float(float amt, float low, float high)
*功能说明:pid位置式控制器输出
*参数说明:
* @param pid pid结构体
* @param error pid输入误差
*函数返回:PID输出结果
*修改时间:2022年3月18日
*备 注:
*************************************************************************/
float PidLocCtrl(pid_param_t * pid, float error)
{
/* 累积误差 */
pid->integrator += error;
pid->out_p = pid->kp * error;
pid->out_i = pid->ki * pid->integrator;
pid->out_d = pid->kd * (error - pid->last_error);
pid->last_error = error;
pid->out = pid->out_p + pid->out_i + pid->out_d;
return pid->out;
}
闭环
闭环就是不断的将目标值与实际值相比较,
将之间的差值进行比例,积分,微分,也就是通过 PID 算法,
将偏差不断减小并趋向于零的过程
闭环代码实现
512 指的是编码器一圈512个脉冲
0.004 是因为我的定时器是每4 ms 执行一次
//我的是C车所以有两个编码器
#define MotorChiShu 9 //电机齿数
#define BianMaQiChiShu 30 //编码器齿数
volatile sint16 ECPULSE1; //编码器1的值 (512)
volatile sint16 ECPULSE2; //编码器2的值 (512)
/*************************************************************************
*函数名称:sint16 DianJiSpeedL()
*功能说明:计算左电机转速
*参数说明:无
*函数返回:电机速度 motorSpeed
*修改时间:2022年3月19日
*备 注:相邻两个齿轮传动,主动轮齿数乘以转速,得数再除以从动轮的齿数,就是从动轮的转速
*************************************************************************/
sint16 DianJiSpeedL(){
static sint16 motorSpeed;
/* 编码器值进行转换 */
motorSpeed=(ECPULSE2/0.004/512)*BianMaQiChiShu/MotorChiShu;
ECPULSE2=0;
return motorSpeed;
}
/*************************************************************************
*函数名称:sint16 DianJiSpeedR()
*功能说明:计算右电机转速
*参数说明:无
*函数返回:电机速度 motorSpeed
*修改时间:2022年3月19日
*备 注:相邻两个齿轮传动,主动轮齿数乘以转速,得数再除以从动轮的齿数,就是从动轮的转速
*************************************************************************/
sint16 DianJiSpeedR(){
static sint16 motorSpeed;
/* 编码器值进行转换 */
motorSpeed=(ECPULSE1/0.004/512)*BianMaQiChiShu/MotorChiShu;
ECPULSE1=0;
return motorSpeed;
}
/*************************************************************************
*函数名称:sint16 MotorAndPID(pid_param_t * pid,float Err)
*功能说明:进行PID计算并进行限幅
*参数说明:pid_param_t * pid 关于电机PID的结构体 也就是上篇中的 位置式 PID 结构体,float Err目标值与实际值的误差 ()
*函数返回:电机的PWM
*修改时间:2022年3月19日
*备 注: 位置式PID
*************************************************************************/
sint16 MotorAndPID(pid_param_t * pid,float Err){
sint16 MotorPwm=PidLocCtrl(pid,Err);//上篇中的位置式PID函数
if((5000-MotorPwm)>5000){
MotorPwm=5000;
}
if((5000-MotorPwm)<500){
MotorPwm=500;
}
sint16 PWM=5000-MotorPwm;
return PWM;
}
/*这个是定时器*/
void CCU61_CH1_IRQHandler (void)
{
static sint16 MotorPwmL;//左电机的实际PWM
static sint16 MotorPwmR;//右电机的实际PWM
/* 开启CPU中断否则中断不可嵌套 */
IfxCpu_enableInterrupts();
// 清除中断标志
IfxCcu6_clearInterruptStatusFlag(&MODULE_CCU61, IfxCcu6_InterruptSource_t13PeriodMatch);
/* 用户代码 */
MotorSpeedL=DianJiSpeedL();//计算左电机转速
float ErrL=MotorTarget-MotorSpeedL;
MotorPwmL=MotorAndPID(&LSpeed_PID,ErrL);
MotorSpeedR=DianJiSpeedR();//计算右电机转速
float ErrR=MotorTarget-MotorSpeedR;
MotorPwmR=MotorAndPID(&RSpeed_PID,ErrR);
err=MotorSpeedR;
DianJiCtrl(MotorPwmL,MotorPwmR);//这是驱动电机的程序这里就不写了
}
为什么要使用上位机
为了便于调试 PID 的参数,我们可以将智能车的值发送到上位机,
通过图形的方式,进行直观的调节, (将数据发送到上位机可以用蓝牙的方式传输).
PID 调试口诀
参数整定找最佳,从小到大顺序查;
先是比例后积分,最后再把微分加;
曲线振荡很频繁,比例度盘要放大;
曲线漂浮绕大湾,比例度盘往小扳;
曲线偏离回复慢,积分时间往下降;
曲线波动周期长,积分时间再加长;
曲线振荡频率快,先把微分降下来;
动差大来波动慢。微分时间应加长;
理想曲线两个波,前高后低四比一;
一看二调多分析,调节质量不会低;
若要反应增快,增大P减小I;
若要反应减慢,减小P增大I;
如果比例太大,会引起系统震荡;
如果积分太大,会引起系统迟钝。
匿名上位机通信代码
匿名上位机的通信协议,非常简单,大家可以自己去写一写,官方也给出了
非常详细的解释和代码.
这里放出自己模仿官方写的吧
//数据拆分宏定义,在发送大于1字节的数据类型时,比如int16、float等,需要把数据拆分成单独字节进行发送
#define BYTE0(dwTemp) (*(char *)(&dwTemp)) //取出变量的低字节
#define BYTE1(dwTemp) (*((char *)(&dwTemp) + 1)) //高字节
#define BYTE2(dwTemp) (*((char *)(&dwTemp) + 2))
#define BYTE3(dwTemp) (*((char *)(&dwTemp) + 3))
unsigned char DATA;//发送数据缓存
/*************************************************************************
*函数名称:void sentData(sint16 A,sint16 B)
*功能说明:基于匿名上位机的通信协议
*参数说明:sint16 A sint16 B要发送的数据
*函数返回:无
*修改时间:2022年3月19日
*备 注: 匿名上位机V7版
*************************************************************************/
void sentData(sint16 A,sint16 B)
{
int i;
unsigned char sc = 0;
unsigned char ac = 0;
unsigned char _cnt=0;
DATA=0xAA;//帧头
DATA=0xFF;//目标地址
DATA=0XF1;//功能码
DATA=0;//数据长度
//需要将字节进行拆分,调用上面的宏定义即可。
DATA=BYTE0(A);//数据内容
DATA=BYTE1(A);//数据内容
DATA=BYTE0(B);//数据内容
DATA=BYTE1(B);//数据内容
DATA=_cnt-4;//_cnt用来计算数据长度,减4为减去帧开头4个非数据字节
//SC和AC的校验
for(i=0;i<DATA+4;i++)
{
sc+=DATA;
ac+=sc;
}
DATA=sc;//和校验
DATA=ac;//附校验
for(i=0;i<_cnt;i++)UART_PutChar(UART0, DATA);//串口逐个发送数据
}
注意我的匿名上位机是 V7 版本的 这么好的帖子都没人
顶!!!
页:
[1]