智能车浅谈–软件布局篇

前言

智能车省赛已经过去将近一个月,为了纪念一个多学期的努力,在此做一个小总结。

软件上的结构布局

(基于C车模说明,其他车模也可做参考)

        1:x路ADC读取。(针对电感) 

                读出来的值,最好做个滤波处理,bz这里用的均值滤波。

参考代码(主控WCH32V307,用的龙邱商家的库):

    /*电感 ADC读取初始化*/
    ADC1Init(ADC1ch12_C2);    
    ADC1Init(ADC1ch13_C3);
    ADC1Init(ADC1ch14_C4);

    /*电感读取 10次数据取均值*/
    ADC[0] = ADC1_Read_Average(ADC1ch12_C2,10); //左1
    ADC[1] = ADC1_Read_Average(ADC1ch13_C3,10); //中间
    ADC[2] = ADC1_Read_Average(ADC1ch14_C4,10); //右1

              摄像头一帧数据裁剪,再通过大津法(也可是其他算法)获取阈值,最后二值化。(针对灰度摄像头,本章不细讲)

        2:一路IO口作PWM输出。

                        用来控制舵机打脚转向。

参考代码:

    /*舵机信号线IO口初始化PWM模式 频率50HZ */
    ServoInit(50);   

    /*pwm控制 当前输出对应舵机中值*/   
    ServoCtrl(Servo_Center_Mid);  

注:舵机中值需要自己用按键一点点控制pwm输出找到,同时找到左打脚极限和右打脚极限的值,根据这个值做好限幅处理,这样就不会使舵机转到极限位置卡齿轮从而损坏舵机。

        3:倆路IO口作PWM输出,倆路IO口作高低电平输出。

                        用来通过电机驱动模块间接控制电机转动

                        注:有的电机驱动模块是需要4路IO口输出PWM进而控制电机转动。

参考代码:

    /* 电机驱动的频率为10Khz */
    MotorInit(10000); 
    
    /*双电机控制*/
    MotorCtrl(dutyL, dutyR);   

        4.定时器输入捕获模式,获取编码器的一定时间内的脉冲数,进而获取当前电机转速

(这个配置一般商家给的例程会有,不需要自己配置)

参考代码:

    /*编码器模式初始化*/
    Encoder_Init(TIM2_ENCA_A15,TIM2_ENCB_B3);  
    Encoder_Init(TIM3_ENCA_B4, TIM3_ENCB_B5);  

基于以上四点,软件上的基本配置就好了。布局完成后,现在谈谈让车跑起来的思路。

软件上的编程思路:

        1.舵机转向环:

        电感相当于车的眼睛,比如车前瞻架倆路电感,一左一右对称。通过电磁感应(赛道上有铺设电磁线)和ADC通道读取获得俩个感应电压Lv(左电感产生的感应电压),Rv(右电感产生的感应电压)。具体原理参考这里:传送门

       理论上小车放在赛道正中间,Lv ≈Rv

        小车水平向左移动(至右电感垂直投影在赛道正中间),Lv < Rv

        小车水平向右移动(至左电感垂直投影在赛道正中间),Lv > Rv

       获取的值有了这种关系,我们就可以利用俩个电感值(感应电压值)作差,得到一个偏差值Error。

                     Error = Lv – Rv;(当然还能用差比和,归一化等使算出来的Error值更稳定)

       将这个error乘以一定比例kp加在舵机中值上,最终算出来的值就是要给到舵机的pwm。这样,小车就能正常寻迹了。(后期想要优化寻迹,可能就需要PID,模糊控制等其它算法了)

参考代码:

    /*************************电磁的舵机打角PID*************************/

    //差比和 分母加上中间电感产生的稳定效果相对较好
    dainci_piancha = 100*(ADC[0] - ADC[2])/(ADC[0] + ADC[2] +ADC[1]);  
    //舵机的偏差PID
    jiao= (dianci_P*dainci_piancha + dianci_D * (dainci_piancha - Last_dainci_piancha));
    Last_dainci_piancha = dainci_piancha;
    DUOJI_PWM=(u16)(Servo_Center_Mid+jiao);
    ServoCtrl(DUOJI_PWM);     // 舵机PWM输出,转向

        2.电机开环

        电机的速度无非是主控输出倆路PWM给到电机驱动,再驱动电机转动。 我最开始让小车跑的时候就是给一个固定的PWM,也就是全程跑一个速度,这个属于开环控制。开环控制有一定的缺陷,比如不能让小车在直道上跑相对更快的速度,速度快了弯道和各类元素可能走不好,停车也是用惯性停的。总结来说,开环跑速度上不去。

        3.电机闭环(速度环)

        闭环利用编码器传回来的脉冲数,计算出实时速度(bz这里是直接用的脉冲数来当做速度与自己的预期作PID)。

    ECPULSE1 = Read_Encoder(TIM3); //左电机  获取脉冲值(间接的速度值)
    ECPULSE2 = Read_Encoder(TIM2); //右电机  获取脉冲值

         与我们的预期速度作差算出一个速度偏差error值,然后作PID。算出一个新的PWM输出到电机上。举个例子:比如实际速度小于我的预期速度,那说明小车速度没达到我的预期,那么就要基于当前速度再加速。理论上通过PID运算,最后我输出的PWM就会比当前的PWM值要大。电机速度环调的差不多的时候,赛道不同的路段跑不同的预期速度就好写了。

        参考代码:

        /*************   电机增量式PID控制  ***************/
        S_error=(int)(speed-Current_speed); //speed为我的预期速度,Current_speed为算出来的当前速度
        duty=duty+(S_error-S_error_pre)*Motor_P+S_error*Motor_I + Motor_D*(S_error - 2*S_error_pre + S_error_prepre);
        S_error_prepre = S_error_pre;
        S_error_pre=S_error;

        Car_control_dianji();  //PWM输出duty值 作用到电机上

      上面代码通过PID算出来的PWM,在输出到电机前可以做一个限幅,因为如果PID参数没有调好导致超调情况,小车电机可能会疯转,所以为了保护好自己的爱车,没调好前可以限个幅。(如下)

        /*************   电机增量式PID控制  ***************/
        S_error=(int)(speed-Current_speed);
        duty=duty+(S_error-S_error_pre)*Motor_P+S_error*Motor_I + Motor_D*(S_error - 2*S_error_pre + S_error_prepre);
        S_error_prepre = S_error_pre;
        S_error_pre=S_error;

        if(duty>=4500) duty=4500;    //限幅值要自己设置
        else if(duty<=-4500) duty=-4500;
        Car_control_dianji();  //PWM输出duty值 作用到电机上

       注:电机的闭环可以每个电机单独写一个闭环,也可以把获取的左右编码器的脉冲值取平均,然后只写一个闭环。(个人建议写每个电机单独写,若你是B车模当我没说doge。因为单独写好写差速)

         调速度环要注意的点:

        一定要让主控能够区分电机的正反转。编码器有个dir脚是用来区分正反转的,比如正转输出高电平,则反转就是低电平。在代码上写个判断即可。(由于大伙编码器的装法大概率是镜像分布在左右,若基于上述条件,如果左编码器正转为1,反转为0。则右编码器正转为0,反转为1。这个自己试试就知道了,因为对于编码器本身只识别是顺时针转还是逆时针转。若顺时针转输出1,则逆时针转肯定输出为0。)参考如下:

    /*令正转数值为正数,反转数值为负数*/
    /*若正转数值读出来的数值为负,则通过代码修正为正数。反之一样*/
    if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5) == 1) //满足说明为正转
    {
        if(ECPULSE1 < 0)
        {
            ECPULSE1 = -ECPULSE1;
        }
        else
        {
            ECPULSE1 = ECPULSE1 * 1;
        }
    }
    else //不满足说明为反转
    {
        if(ECPULSE1 < 0)
        {
            ECPULSE1 = ECPULSE1 * 1;
        }
        else
        {
            ECPULSE1 = -ECPULSE1;
        }
    }
    if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_3) == 1)//满足说明为反转
    {
        if(ECPULSE2 < 0)
        {
            ECPULSE2 = ECPULSE2 * 1;
        }
        else
        {
            ECPULSE2 = -ECPULSE2;
        }
    }
    else//不满足说明为正转
    {
        if(ECPULSE2 < 0)
        {
            ECPULSE2 = -ECPULSE2;
        }
        else
        {
            ECPULSE2 = ECPULSE2 * 1;
        }
    }

        4.电机闭环(方向环)

        如果说电机速度环是为了控速,那么电机方向环就是用来辅助小车转向。你可以想想F车(三轮)是怎么寻迹的,没有舵机的F车只能通过电机差速(俩个轮子转速不同)来转向。但有舵机的C车模同样可以过弯道利用差速辅助舵机,从而使小车弯道速度更快通过。(弯道快才是真的快,谁直道不会加速?(doge))。如何写方向环这里就提供一个思路,因为是弯道差速,可以参考舵机转向的PID。

最后给各位第一次准备智能车的软件手一些建议:

1.电机速度环和电机方向环不能重叠,代码上要理清楚

2.PID控制最好写在定时器里

3.能写结构体就写结构体,不然后面变量多的怀疑当初是不是自己加的。

4.模块化编程不用多说了吧,然后就是写注释。

如果想了解小车的硬件布局,可以参考这篇文章–智能车浅谈——硬件篇

以上就是bz对软件上的思路和布局,希望能对您有所帮助,如果有写错的地方望指正。

物联沃分享整理
物联沃-IOTWORD物联网 » 智能车浅谈–软件布局篇

发表评论