小伤口 发表于 2022-3-31 16:59:08

智能车笔记-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 版本的

高山 发表于 2022-9-7 20:20:32

这么好的帖子都没人
顶!!!
页: [1]
查看完整版本: 智能车笔记-PID闭环