智能车四轮控制思路分享

机械结构

四轮车的重心尽量靠近中心位置,PCB和电池的摆放位置尽可能低一些,保证小车在过弯时既能轻松的转向又不会甩尾,在直线上的加减速也会有更好的效果。

舵机调教

对于C车模,四轮车来说,转向最重要的一环就是舵机,如果舵机是歪的,那怎么跑都跑不好,拿到车模的第一步就是将舵机进行归中处理,并对舵机的左右极限角度进行限幅。

编码器的读值与滤波处理

我比赛时候用的是逐飞的正交编码器,读取编码器数值,我们只需要调用逐飞的开源库即可,这一个步骤是相对来说简单的,我们就不展开来说。

编码器读值


    encoder_data_1 = encoder_get_count(ENCODER_1);                        
    Left_Speed.Encoder = -(int)encoder_data_1;                                           
    encoder_clear_count(ENCODER_1);                                            

    encoder_data_2 = encoder_get_count(ENCODER_2);                            
    Right_Speed.Encoder = (int)encoder_data_2;                                              
    encoder_clear_count(ENCODER_2);                                          

编码器滤波

第二步就是编码器的滤波处理,由于小车在高速运行的过程中,即使编码器可能安装的比较稳定,但是还是会有车辆抖动的情况,这种情况在过弯的时候尤为明显,为了防止这种不必要的读值,我使用了FIR滤波。

void FIR_Init(FIR *filter, float sample_freq, float cutoff_freq)
{
    filter->sample_freq = sample_freq;
    filter->cutoff_freq = cutoff_freq;
    
    float fr = sample_freq/cutoff_freq;
    float ohm = tanf(PI/fr);
    float c = 1.0f+2.0f*cosf(PI/4.0f)*ohm + ohm*ohm;

    filter->b0 = ohm*ohm/c;
    filter->b1 = 2.0f*filter->b0;
    filter->b2 = filter->b0;
}

/*需要滤波的信号sample  上次的输出信号_output*/
float FIR_apply(FIR *filter, float sample)
{
    filter->oupt = sample * filter->b0 + filter->temp1 * filter->b1 + filter->temp2 * filter->b2;

    filter->temp2 = filter->temp1;
    filter->temp1 = sample;

    return filter->oupt;
}
FIR_Init(&FIR_encoder, 300, 30);
encoder_left=FIR_apply(&FIR_encoder, encoder1);
encoder_right=FIR_apply(&FIR_encoder, encoder2);

速度闭环

在处理了编码器的读值之后,就应该来调试我们的速度闭环了,在电机的速度闭环上,我这里给出两个方案:1.传统的增量式PI速度闭环 2.ADRC速度闭环

两个方法的优劣

对与新手来说,我更加推荐PID增量式,理由是好调参,资料更为全面,而且花不了很多时间就能得到一个不错的闭环效果。

而ADRC闭环需要调整的参数更多,但理论上效果是更加好的。

增量式PID

这里我给出增量式pid的代码,并使用前馈控制使电机响应更快

/*
 * @brief:前馈控制
 *        控制周期:0.002
 * 设置转动惯量和摩擦系数均为理想状态
 */
float T = 0.002;//ms
float PreFeedback(int Input,int Last_Input)
{
    float Output;
    Output = (Input - Last_Input)/T + Input;
    return Output;
}

void PID_Parameter_Init(PID *sptr)
{
    sptr->SumError  = 0;
    sptr->LastError = 0;	
    sptr->PrevError = 0;	
    sptr->LastData  = 0;
}

int32 PID_Increase(PID *sptr, float *PID, int32 NowData, int32 Point)
{
    //当前误差,定义为寄存器变量,只能用于整型和字符型变量,提高运算速度
    int32 iError,	//当前误差
          Increase/*,Last_Increase*/;	//最后得出的实际增量


    iError = Point - NowData;	// 计算当前误差

    Increase =  PID[KP] * (iError - sptr->LastError)
                + PID[KI] * iError
                + PID[KD] * (iError - 2 * sptr->LastError + sptr->PrevError);

    sptr->PrevError = sptr->LastError;	// 更新前次误差
    sptr->LastError = iError;		  	// 更新上次误差
    sptr->LastData  = NowData;			// 更新上次数据

    Last_Increase = Increase;

    Increase = PreFeedback(Increase,Last_Increase);//使用前馈调节加快响应
    return Increase;	// 返回增量
}

ADRC

这里是学习ADRC代码和视频的地方,我对于ADRC的理解基本也是来自这里

以下是ADRC的调用部分

void LADRC_Loop(LADRC_NUM *LADRC_TYPE1,float* Expect,float* RealTimeOut)
{
	float  Expect_Value = *Expect;
	float  Measure = *RealTimeOut;
    LADRC_TD(LADRC_TYPE1,Expect_Value);
    LADRC_ESO(LADRC_TYPE1,Measure); 
    LADRC_LF(LADRC_TYPE1);
}

陀螺仪的读值与滤波

零飘处理

由于陀螺仪在静态时会有零点漂移,故在初始化的时候我们需要对陀螺仪进行零飘处理

void offset_init()      /陀螺仪零飘初始化,建议放在各外设初始化之前,因为太耗时
{
    for (uint16_t i = 0; i < LOOP_NUM; i++)
    {
		imu660ra_get_gyro();
		Gyro_offset_z+=icm20602_gyro_z;
        DELAY_MS(2);
    }
    Gyro_offset_z/=LOOP_NUM;
}

我使用的陀螺仪是IMU660RA,这里读值也是直接调用逐飞的库,由于我们只用陀螺仪辅助转向,所以这里只取z轴(偏航角)的读数

imu660ra_get_gyro();
Gyro.z = (imu660ra_gyro_z-Gyro_offset_z)/16.40f;

IIR滤波

接下来便是对陀螺仪进行滤波处理,这里我使用的是IIR滤波。

void IIR_Init(filter2p_t *filter, float sample_freq, float cutoff_freq)
{
    filter->sample_freq = sample_freq;
    filter->cutoff_freq = cutoff_freq;
    
    float fr = sample_freq/cutoff_freq;
    float ohm = tanf(PI/fr);
    float c = 1.0f+2.0f*cosf(PI/4.0f)*ohm + ohm*ohm;

    filter->b0 = ohm*ohm/c;
    filter->b1 = 2.0f*filter->b0;
    filter->b2 = filter->b0;
    filter->a1 = 2.0f*(ohm*ohm-1.0f)/c;
    filter->a2 = (1.0f-2.0f*cosf(PI/4.0f)*ohm+ohm*ohm)/c;
}

/*需要滤波的信号sample  上次的输出信号_output*/
float IIR_apply(filter2p_t *filter, float sample)
{
    float temp0 = sample - filter->temp1 * filter->a1 - filter->temp2 * filter->a2;
    filter->oupt = temp0 * filter->b0 + filter->temp1 * filter->b1 + filter->temp2 * filter->b2;

    filter->temp2 = filter->temp1;
    filter->temp1 = temp0;

    return filter->oupt;
}

IIR_Init(&IIR_gyro, 500, 30);
Gyro.z=IIR_apply(&IIR_gyro, Gyro.z);

转向环

转向环这里可以直接使用模糊pid,模糊pid对不同曲率的弯道适应能力相比较于普通的位置式pid强,但对于c车来说,我还是建议2.8m以上再使用模糊pid。

普通位置式

int32 PID_Realize(PID *sptr, float *PID, int32 NowData, int32 Point)//传统位置式
{
    //当前误差,定义为寄存器变量,只能用于整型和字符型变量,提高运算速度
    int32 iError,	// 当前误差
          Realize;	// 最后得出的实际增量

    iError = Point - NowData;	// 计算当前误差
    sptr->SumError += PID[KI] * iError;	// 误差积分
    if (sptr->SumError >= PID[KT])
    {
        sptr->SumError = PID[KT];
    }
    else if (sptr->SumError <= -PID[KT])
    {
        sptr->SumError = -PID[KT];
    }

    Realize = PID[KP] * iError
              + sptr->SumError
              + PID[KD] * (iError - sptr->LastError);
    sptr->PrevError = sptr->LastError;	// 更新前次误差
    sptr->LastError = iError;		  	// 更新上次误差
    sptr->LastData  = NowData;			// 更新上次数据

    return Realize;	// 返回实际值
}

模糊pid

int32 PID_Fuzzy(PID *sptr, float *PID, int32 NowData, int32 Point)//模糊pid
{
    //当前误差,定义为寄存器变量,只能用于整型和字符型变量,提高运算速度
    int32 iError,	// 当前误差
          Fuzzy_U;	// 最后得出的实际模糊增益

    iError = Point - NowData;	// 计算当前误差
    sptr->SumError += PID[KI] * iError;	// 误差积分

    Fuzzy_U = (PID[KP] * Fuzzy( (float)iError,(float)(iError - sptr->LastError) ) ) * iError
              + PID[KD] * Fuzzy( (float)iError,(float)(iError - sptr->LastError) )*(iError - sptr->LastError);
    sptr->PrevError = sptr->LastError;	// 更新前次误差
    sptr->LastError = iError;		  	// 更新上次误差
    sptr->LastData  = NowData;			// 更新上次数据

    return Fuzzy_U;	// 返回实际值
}

const int rule[7][7]=
{
	{ 6 , 5 , 4 , 3 , 2 , 1 , 0},//0
	{ 5 , 4 , 3 , 2 , 1 , 0 , 1},//1
	{ 4 , 3 , 2 , 1 , 0 , 1 , 2},//2
	{ 3 , 2 , 1 , 0 , 1 , 2 , 3},//3
	{ 2 , 1 , 0 , 1 , 2 , 3 , 4},//4
	{ 1 , 0 , 1 , 2 , 3 , 4 , 5},//5
	{ 0 , 1 , 2 , 3 , 4 , 5 , 6},//6
};//模糊规则表,不改

float Fuzzy(float E,float EC)
{

    /*输入量P语言值特征点*/
    float EFF[7]={-75,-50,-25,0,25,50,75};//要改,最大偏差值分三段
    /*输入量D语言值特征点*/
    float DFF[7]={-15,-10,-5,0,5,10,15};
    /*输出量U语言值特征点(根据赛道类型选择不同的输出值)*/
    float UFF[7]={0,1,2,3,4,5,6};

    float U=0;  /*偏差,偏差微分以及输出值的精确量*/
    float PF[2]={0},DF[2]={0},UF[4]={0};
    /*偏差,偏差微分以及输出值的隶属度*/
    int Pn=0,Dn=0,Un[4]={0};
    float t1=0,t2=0,t3=0,t4=0,temp1=0,temp2=0;
    /*隶属度的确定*/
    /*根据PD的指定语言值获得有效隶属度*/
    if(E>EFF[0] && E<EFF[6])
    {
        if(E<=EFF[1])
        {
            Pn=-2;
            PF[0]=(EFF[1]-E)/(EFF[1]-EFF[0]);
        }
        else if(E<=EFF[2])
        {
            Pn=-1;
            PF[0]=(EFF[2]-E)/(EFF[2]-EFF[1]);
        }
        else if(E<=EFF[3])
        {
            Pn=0;
            PF[0]=(EFF[3]-E)/(EFF[3]-EFF[2]);
        }
        else if(E<=EFF[4])
        {
            Pn=1;
            PF[0]=(EFF[4]-E)/(EFF[4]-EFF[3]);
        }
        else if(E<=EFF[5])
        {
            Pn=2;
            PF[0]=(EFF[5]-E)/(EFF[5]-EFF[4]);
        }
        else if(E<=EFF[6])
        {
            Pn=3;
            PF[0]=(EFF[6]-E)/(EFF[6]-EFF[5]);
        }
    }

    else if(E<=EFF[0])
    {
        Pn=-2;
        PF[0]=1;
    }
    else if(E>=EFF[6])
    {
        Pn=3;
        PF[0]=0;
    }

    PF[1]=1-PF[0];


    //判断D的隶属度
    if(EC>DFF[0]&&EC<DFF[6])
    {
        if(EC<=DFF[1])
        {
            Dn=-2;
            DF[0]=(DFF[1]-EC)/(DFF[1]-DFF[0]);
        }
        else if(EC<=DFF[2])
        {
            Dn=-1;
            DF[0]=(DFF[2]-EC)/(DFF[2]-DFF[1]);
        }
        else if(EC<=DFF[3])
        {
            Dn=0;
            DF[0]=(DFF[3]-EC)/(DFF[3]-DFF[2]);
        }
        else if(EC<=DFF[4])
        {
            Dn=1;
            DF[0]=(DFF[4]-EC)/(DFF[4]-DFF[3]);
        }
        else if(EC<=DFF[5])
        {
            Dn=2;
            DF[0]=(DFF[5]-EC)/(DFF[5]-DFF[4]);
        }
        else if(EC<=DFF[6])
        {
            Dn=3;
            DF[0]=(DFF[6]-EC)/(DFF[6]-DFF[5]);
        }
    }
    //不在给定的区间内
    else if (EC<=DFF[0])
    {
        Dn=-2;
        DF[0]=1;
    }
    else if(EC>=DFF[6])
    {
        Dn=3;
        DF[0]=0;
    }

    DF[1]=1-DF[0];

    /*使用误差范围优化后的规则表rule[7][7]*/
    /*输出值使用13个隶属函数,中心值由UFF[7]指定*/
    /*一般都是四个规则有效*/
    Un[0]=rule[Pn+2][Dn+2];
    Un[1]=rule[Pn+3][Dn+2];
    Un[2]=rule[Pn+2][Dn+3];
    Un[3]=rule[Pn+3][Dn+3];

    if(PF[0]<=DF[0])    //求小
        UF[0]=PF[0];
    else
        UF[0]=DF[0];
    if(PF[1]<=DF[0])
        UF[1]=PF[1];
    else
        UF[1]=DF[0];
    if(PF[0]<=DF[1])
        UF[2]=PF[0];
    else
        UF[2]=DF[1];
    if(PF[1]<=DF[1])
        UF[3]=PF[1];
    else
        UF[3]=DF[1];
    /*同隶属函数输出语言值求大*/
    if(Un[0]==Un[1])
    {
        if(UF[0]>UF[1])
            UF[1]=0;
        else
            UF[0]=0;
    }
    if(Un[0]==Un[2])
    {
        if(UF[0]>UF[2])
            UF[2]=0;
        else
            UF[0]=0;
    }
    if(Un[0]==Un[3])
    {
        if(UF[0]>UF[3])
            UF[3]=0;
        else
            UF[0]=0;
    }
    if(Un[1]==Un[2])
    {
        if(UF[1]>UF[2])
            UF[2]=0;
        else
            UF[1]=0;
    }
    if(Un[1]==Un[3])
    {
        if(UF[1]>UF[3])
            UF[3]=0;
        else
            UF[1]=0;
    }
    if(Un[2]==Un[3])
    {
        if(UF[2]>UF[3])
            UF[3]=0;
        else
            UF[2]=0;
    }
    t1=UF[0]*UFF[Un[0]];
    t2=UF[1]*UFF[Un[1]];
    t3=UF[2]*UFF[Un[2]];
    t4=UF[3]*UFF[Un[3]];
    temp1=t1+t2+t3+t4;
    temp2=UF[0]+UF[1]+UF[2]+UF[3];//模糊量输出
    U=temp1/temp2;
    return U;
}

阿克曼转向模型

在小车后轮差速的策略上,我选择了阿克曼转向结构,如此转向方案可以让小车在弯道更丝滑

以下是阿克曼转向参考代码

void AKM_Analysis(float velocity , int32 angle)
{
    //C车模数据
	float Car_L_ = 205; //长(mm)
    float Car_T_ = 153; //宽(mm)
    float K_Turn_;
    float Rad;
    angle = LIMIT(angle, -70, 70);
    Rad = DEGTORAD(angle);
    K_Turn_ = FastTan(Rad) * Car_T_/2/Car_L_;
    K_Turn_ = LIMIT(K_Turn_,-5,5);
    Left_Speed.Target = velocity * (1+K_Turn_);
    Right_Speed.Target = velocity * (1-K_Turn_);
}
void Servo_Analysis(int32 angle)
{
    angle = LIMIT(angle, -70, 70);
    Servo_Attribute.Servo_Target = Servo_Attribute.Servo_Mid - angle * 2.33;//这里角度系数是由舵机最大范围除以最大转向角度算出来的
    Servo_Attribute.Servo_Target = LIMIT(Servo_Attribute.Servo_Target, Servo_Attribute.Servo_Min, Servo_Attribute.Servo_Max);
    Servo_set(Servo_Attribute.Servo_Target);
}

控制总章程

以下代码便是我车辆控制转向环的总体代码,由于速度闭环我设置为更高的频率,就不放在转向环里。

void Cascade_Control(int mode)
{
    //1.先由外环解析偏差,由于用到两种传感器,先选择偏差来源
    if(Control_flag.Direct_flag)
    {
        switch(mode)
        {
            case Camera_Tracking:Speed_Control.Direct_Speed = PID_Fuzzy(&Direct_CAMERA_PID, Direct_Camera,img_attribute.Center_Deviation , SteerAttributeExceptMid); break;//摄像头获取偏差
            case Induct_Tracking:Speed_Control.Direct_Speed = -PID_Realize(&Direct_Induct_PID, Direct_Induct, Induct_Attribute.Center_Deviation , SteerAttributeExceptMid);break;//电磁获取偏差
            default:break;
        }
    }
    //2.根据陀螺仪解析姿态
    if(Control_flag.Turn_flag)
    {
        Speed_Control.Turn_Speed = -PID_Realize(&Turn_CAMERA_PID, Turn, Gyro.z , Speed_Control.Direct_Speed);
    }
    //3.由小车运动学模型解析左右轮差速
    Servo_Analysis(Speed_Control.Turn_Speed);
    Speed_Change(img_attribute.Gearshift);
    AKM_Analysis(Speed_Control.Target_Speed, Speed_Control.Turn_Speed);
}

  • 9
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: CSDN智能车使用四轮差速作为其驱动系统。差速驱动系统是一种常见的机械驱动系统,适用于机器人和汽车等设备。它使用两个间距较小的驱动轮,并独立控制每个驱动轮的速度、转向和运动方向。 四轮差速系统的原理是通过改变左右两个驱动轮的转速差异来驱动车辆的转向。当左右两个驱动轮转速相等时,车辆直行;当左驱动轮速度大于右驱动轮时,车辆向右转;当右驱动轮速度大于左驱动轮时,车辆向左转。 使用差速驱动系统的好处是,车辆具有良好的操控性和灵活性。由于可以独立控制每个驱动轮的速度和转向,车辆可以实现较小的转弯半径,并且可以在狭窄的空间内自由转动。此外,差速驱动系统还可以提供良好的抓地力和悬挂控制,以适应不同地形条件下的行驶需要。 然而,四轮差速驱动系统也存在一些缺点。首先,由于需要自主控制每个驱动轮的速度和转向,需要较复杂的电子控制系统和算法。此外,由于每个驱动轮的转速会影响整个车辆的行驶状况,所以需要准确地控制和同步每个驱动轮的转速,这对于设计和调试来说是一项挑战。 综上所述,CSDN智能车使用四轮差速驱动系统,通过控制左右驱动轮的转速差异来实现转向。该驱动系统具有良好的操控性和灵活性,但也需要复杂的电子控制系统和算法来实现精确调控。 ### 回答2: CSDN智能车四轮差速是一种常见的汽车驱动方式,通过控制车轮的差速来实现车辆的转向和行进。差速驱动方式可以灵活地控制车辆的前进和转向,适用于各种路况和环境。四轮差速驱动方式是指车辆的每个轮子都可以独立地旋转,并且可以通过差速器来调整每个轮子的旋转速度。 这种驱动方式的优点之一是具有良好的操控性能。通过差速器控制不同车轮的速度差异,可以实现转向效果,使车辆具有较小的转弯半径和更灵敏的操控性,适用于各种曲线道路和转弯场景。 另外,四轮差速驱动方式还具有较好的过坑能力。通过独立控制每个轮子的转速,可以根据路面情况调整不同轮子的行进速度,使车辆能够克服不同大小的障碍物和坑洼,提升车辆通过性和稳定性。 此外,四轮差速驱动方式还可以实现原地旋转和平稳加速。通过差速器控制不同轮子的旋转速度,可以实现车辆的原地旋转,更好地适应狭窄空间和复杂环境。同时,通过差速的调整,可以实现平稳加速,提高车辆的动力性和舒适性。 总之,CSDN智能车四轮差速是一种灵活操控、适应性强的驱动方式,能够满足不同路况和环境下的行驶需求,具有较好的操控性、通过性和动力性能。 ### 回答3: CSDN智能车四轮差速是一种汽车前后四个轮子的差速控制方式。差速控制是通过改变左右两侧轮子的转速差异来实现转向和转弯的控制智能车的四个轮子通常是由电机驱动的,通过控制电机的转速来实现车辆的运动。为了实现差速控制,左右两边的轮子可以独立控制其转速。在直线行驶时,四个轮子的转速相同,车辆可以平稳地向前或向后运动。 当需要转向或转弯时,可以通过改变左右两侧轮子的转速差异来实现。例如,当希望左转时,可以增加左侧轮子的转速,减小右侧轮子的转速。因为左右两侧轮子的转速差异,车辆会向左转弯。反之,当希望右转时,可以增加右侧轮子的转速,减小左侧轮子的转速。 差速控制可以通过程序或算法来实现,在智能车中通常会使用微控制器或单片机来实现控制逻辑。通过接收传感器的反馈信号,智能车可以根据需求改变轮子的转速,从而控制车辆的运动状态。 总的来说,CSDN智能车四轮差速是一种通过改变左右两侧轮子转速差异来实现转向和转弯的控制方式。通过精确的差速控制智能车可以实现精确的转向和转弯动作,提高了车辆的灵活性和操控性能。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值