第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制

这篇具有很好参考价值的文章主要介绍了第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

开源汇总写在下面

第18届全国大学生智能汽车竞赛四轮车开源讲解_Joshua.X的博客-CSDN博客

一、控制方法

1.控制对象基本介绍

1.1舵机

舵机是控制车模方向的重要组成部分,一般放在车头,实物见下图。

第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
C车模允许使用的有S3010,U400这两款舵机

如果比赛限制车模类型为C车模,那么只能使用S3010(已停产)或者U400舵机进行比赛,控制舵机主要是控制PWM波的脉宽。控制规律如下图。

第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
舵机占PWM脉宽与转动角度之间关系

关于PWM波这里不再介绍,CSDN上有很多的介绍。

S3010,U400这两款舵机是的控制频率为50Hz(官方推荐参数),想要控制这个舵机,就需要单片机产生50HZ的PWM波,改变占空比,使脉冲宽度在1ms~2ms,使之实现左右转动,控制车模方向。

首先我们选取一个能够产生PWM波的IO口(并非所有IO口都可以产生PWM波),使他的PWM频率是50Hz。在配置的库中,所有PWM波的占空比的细分都被映射到了0~10000。(有些是0~50000,需要查看对应工程的实际情况)

第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
PWM初始化函数

占空比0是纯低电平,占空比10000是满占空比,也就是纯高电平。

我们一起来推导下如何才能实现脉冲在1~2ms之间。

已知我们配置是PWM频率为50Hz,那么周期就是20ms。

按照比例映射,当占空比为10000时输出是20ms脉宽,我们求占空比为x时,输出1.5ms脉宽。

那么我们得到了这样一个比例式:

解得x=750时,单片机将输出50HZ,脉宽时间为1.5ms的PWM波。对应着舵机理论归中。

舵机初始化代码如下:

#define STEER_PIN    TIM2_PWM_MAP3_CH1_A15//舵机控制信号输出口

#define STEER_RIGHT  705  //舵机右打死,-
#define STEER_MID    782  //舵机归中
#define STEER_LEFT   857  //舵机左打死,+

//舵机初始化函数
pwm_init(STEER_PIN, 50, STEER_MID);

我的中值STEER_MID是782,由于安装位置,拉杆机械误差,车模机械误差,实际中值一般很难是750,但是也在750附近,符合理论估计。

第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
拉杆长度,机械偏差,轮子磨损,都会影响舵机中值

实际上,STEER_MID这个值需要机械归零校准的,具体流程就是将舵机打角在视觉中值附近(也就是看起来车轮是正的)。把车放在地砖线附近,用手推车。(我一般推2m左右,当然越远越准)参考地砖线,如果车子走的是直线,那就找到了这个中值;如果往左/右歪,就适当加减STEER_MID这个值,直到推出来是直线为止。

第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
车子放正,左边地砖缝作为参考线,舵机归正推着走,看误差

同理调整这个数字,找到轮胎左右打角的极限,再往回收一点点,让轮子打角幅度比较大,但又能流畅转动。这样就找到了舵机的左、中、右值。这样基本的控制准备工作就做好了。

详细机械调整方法,会在后文的机械篇提到,敬请期待。

找到了中值,左右极值后,就可以自由的让车模左右转动了。改变的就是这个变量angle的值。

//舵机占空比设置函数
pwm_set_duty(STEER_PIN, STEER_MID+angle);//舵机调节

目前我们知道舵机在占空为STEER_MID时舵机打角为正,且增大/减小该值会向左/右打角。

那么我在他的基础上+angle这个变量,当angle为正的时候,PWM脉宽大于1.5ms,舵机向左打,当angle为负,PWM脉宽小于1.5ms,舵机向右打,那么我想办法将赛道的拐弯情况,和这个变量做个映射,这个映射就是PD控制,这样就实现了车模的自主巡线。

1.1.1 舵机魔改

舵机的推荐电压在7v左右,具体情况查看参数手册。

其实在官方推荐的参数上可以适当再增加一点电压,电压越高,舵机相应速度越快。但是同时也越容易损坏,这方面需要自行权衡。

50Hz的频率也是可以进行魔改,据群内消息,u400舵机用300Hz没问题,实测什么样我就不知道。各位准备好钱,自行尝试。

具体数值关系上文有提到公式,自行换算。

第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法

1.2.电机

这是我们智能车使用的普通直流有刷电机。

第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法

 直流有刷电机主要靠驱动板进行供电,驱动。

一个电机需要两路信号来控制方向和转速,共有两种方法,详情见下图。

 驱动方式不同是因为驱动芯片的不同,导致代码编写会有不同,这一点需要和硬件队友进行确认。

第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
电机驱动的两种方式
驱动方式1

利用两路pwm对一个电机进行控制。如果A路是PWM,B路0,则是正转,且PWM占空比越大,转速越快,想要反转也只需要将AB两路信号调转过来即可,A路输出0,B路输出PWM。控制代码如下:(0为低电平,占空比为0的PWM波就是低电平)

void Motor_Right(int pwm_R)
{
    if(pwm_R>=SPEED_MAX)//限幅处理
        pwm_R=SPEED_MAX;
    else if(pwm_R<=SPEED_MIN)
            pwm_R=SPEED_MIN;
    if(pwm_R>=0)
   {
    pwm_duty(MOTOR01_CH1, pwm_R);//右电机正转
    pwm_duty(MOTOR01_CH2, 0);
   }
  else
   {
    pwm_duty(MOTOR01_CH1, 0);     //右电机反转
    pwm_duty(MOTOR01_CH2, -pwm_R);
   }
}

这里我是做了一个函数,输入的参数是我期望的占空比。如果是正,那么就是正转,输入到A通道里面,B给0;如果是负数,那我认为需要反转,将占空比加个负号(负负得正,占空比只能有正数)输入到B通道中,A给0,这样就实现了正反转控制。

驱动方式2

这种方式比较好理解,一路1或0确定控制方向,另一路PWM用来控制转速。代码如下:

void Motor_Right(int pwm_R)
{
    if(pwm_R>=SPEED_MAX)//限幅处理
        pwm_R=SPEED_MAX;
    else if(pwm_R<=SPEED_MIN)
            pwm_R=SPEED_MIN;

    if(pwm_R>=0)//正转
    {
        pwm_set_duty(MOTOR02_SPEED_PIN, pwm_R);//右电机正转
        gpio_set_level(MOTOR02_DIR_PIN, 0);//02左电机,D14,1反转,0正转
    }//这里给1给0正反转需要实测,一切以实际为准
    else
   {
        pwm_set_duty(MOTOR02_SPEED_PIN, -pwm_R);//右电机反转
        gpio_set_level(MOTOR02_DIR_PIN, 1);//02左电机,D14,1反转,0正转
   }
}

还是先判断正转反转,正转就给方向脚0,反转就给方向角1,然后选取绝对值输入到占空比设置函数中去。

这两种方式,无论哪种方式都不是绝对的。实际情况和电机接线方式,电池正负极接入方式,都有关系。有可能我是代码A给pwm,B给0是正转,但实际你的车是反转,这种情况就把电机接线,或者代码改一个就好(一般建议改代码,电机接线不好改)。

二、方向控制

方向控制的核心问题就是车模知道自己在赛道什么位置,他需要直道自己向哪边调整。

还是那句话,没有绝对优秀的算法,每一种算法都有自己的优劣,这里仅展示一些方法来给各位提供参考。

第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
方向控制流程图

 寻找边界我们在上一章边线提取已经讲过了,元素判断我们会在后面进行讲解。

本文将讲解计算视野内中线,误差获取/计算偏差,PD算法,将PD算出的打角值放入占空比调整函数。

在方向控制中最重要的就是:误差获取/计算偏差,PD算法,大家的车子其他的流程都是一样的,所谓国赛代码,普通代码的差距一般也就在这里。

注:以下方案有可能会用到一个或者多个下述变量,以下变量均在开源【3】边线提取一章有讲。

const uint8  Standard_Road_Wide[MT9V03X_H];//标准赛宽数组
volatile int Left_Line[MT9V03X_H]; //左边线数组
volatile int Right_Line[MT9V03X_H];//右边线数组
volatile int Mid_Line[MT9V03X_H];  //中线数组
volatile int Road_Wide[MT9V03X_H]; //实际赛宽数组
volatile int White_Column[MT9V03X_W];//每列白列长度
volatile int Search_Stop_Line;     //搜索截止行,只记录长度,想要坐标需要用视野高度减去该值
volatile int Boundry_Start_Left;   //左右边界起始点
volatile int Boundry_Start_Right;  //第一个非丢线点,常规边界起始点
volatile int Left_Lost_Time;       //边界丢线数
volatile int Right_Lost_Time;
volatile int Both_Lost_Time;//两边同时丢线数
int Longest_White_Column_Left[2]; //最长白列,[0]是最长白列的长度,也就是Search_Stop_Line搜索截止行,[1】是第某列
int Longest_White_Column_Right[2];//最长白列,[0]是最长白列的长度,也就是Search_Stop_Line搜索截止行,[1】是第某列
int Left_Lost_Flag[MT9V03X_H] ; //左丢线数组,丢线置1,没丢线置0
int Right_Lost_Flag[MT9V03X_H]; //右丢线数组,丢线置1,没丢线置0

1.误差获取

中线计算我都是和偏差放在一起,因为中线的意义在于计算偏差,只要我能得到偏差,中线就没那么重要,我就没有单独计算中线。

误差获取就是去计算某行,或者某些行的中线与理论中线之间存在的偏差。用理论中线的值减去赛道中线的值得到(赛道中线减去理论中线的值也可以,只是正负号的含义相反罢了)。用这个偏差来表征车模偏离赛道中心的距离。这个值的正负号代表着方向,数值大小代表着偏离程度。

误差获取有很多种办法。

  1. 单行控制/领跑行控制
  2. 误差积累
  3. 误差平均
  4. 动态前瞻
  5. 加权平均

上述方案我们都简单的介绍一下

1.单行控制/领跑行控制

他的本质就是电磁式跑法,将摄像头视野中固定的某行作为舵机打角行,计算该行的中线与理论中线的偏差,前瞻的位置是死的。他的特点在于只采用一行作为控制行,而且这一行是固定不动的,所以一但这一行的图像出现了问题,对于舵机打角的判断就会有很大的影响,而且方向判断只用一行,对于摄像头收集到的多行数据有些浪费。

建议与后文提到的的动态前瞻进行组合使用,可以取得较好的效果。

float Err_Sum(int err_get_line)
{
    float err=0;
    err=(MT9V03X_W/2-((Left_Line[err_get_line]+Right_Line[err_get_line])>>1));//右移1位,等效除2
    return err;
}

2.误差积累

这是我17届车赛采用的做法,具体讲述就是固定某个范围,将这范围内的误差积累求和,将这个和作为误差返回。


#define VALID_HEIGHT  15  //只扫描从下往上数15行,根据情况,可以修改该值

int Err_Sum()
{
    int i=0,sum=0;
    for(i=MT9V03X_H-1;i>MT9V03X_H-VALID_HEIGHT-1;i--)
    {
        sum+=(MT9V03X_W/2)-Mid_Line[i];
    }
    return sum;
}

当时是直接选取了屏幕最下方的15行误差积累作为舵方向控制,给舵机使用。

算法属于能用,但不是很好用的范围。最直接的结果就是这个误差值很大,需要将PD参数调整的很小。

3.误差平均

这个算法是上面误差积累的改进,也是固定某些行,计算他们的误差,但是这里取了平均值,对于一些异常数据会有一定的滤波抗干扰作用。

float Err_Sum(void)
{
    int i;
    float err=0;
    //常规误差
     for(i=51;i<=55;i++)
     {
        err+=(MT9V03X_W/2-((Left_Line[i]+Right_Line[i])>>1));//右移1位,等效除2
     }
    err=err/5.0;
return err;
}

4.动态前瞻

这个是比较高端有用的算法。理论上来说,速度越快,前面越是直道,我们的前瞻越应该看到越远,从而及时的反应过来前方路况;速度越慢,弯道越多,前瞻应该稍微近一点。

这就需要我们以速度,和视野范围为输入量,前瞻位置为输出量,去拟合一个函数。去合理的计算前瞻位置,很遗憾,本人并没有完成这项工作,这里就交各位自行发挥。

5.加权平均

这个是我本届比赛使用的方法,本人使用比较稳定。

先将赛道的每一行都给予相应的权重。

//加权控制
const uint8 Weight[MT9V03X_H]=
{
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1,              //图像最远端00 ——09 行权重
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1,              //图像最远端10 ——19 行权重
        1, 1, 1, 1, 1, 1, 1, 3, 4, 5,              //图像最远端20 ——29 行权重
        6, 7, 9,11,13,15,17,19,20,20,              //图像最远端30 ——39 行权重
       19,17,15,13,11, 9, 7, 5, 3, 1,              //图像最远端40 ——49 行权重
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1,              //图像最远端50 ——59 行权重
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1,              //图像最远端60 ——69 行权重
};

接着从赛道最下面到搜索截止行,将所有误差进行加权求平均,权重数组就是上面的数组。

float Err_Sum(void)
{
    int i;
    float err=0;
    float weight_count=0;
    //常规误差
    for(i=MT9V03X_H-1;i>=MT9V03X_H-Search_Stop_Line-1;i--)//常规误差计算
    {
        err+=(MT9V03X_W/2-((Left_Line[i]+Right_Line[i])>>1))*Weight[i];//右移1位,等效除2
        weight_count+=Weight[i];
    }
    err=err/weight_count;
    return err;
}

这样利用到了赛道每一行的数据,增强了抗干扰性,又可以通过权重比例来确定车模切内还是切外,本人实际效果较好,推荐大家使用。

注:误差的获取的一个原则就是在其他情况都相同的情况下,误差选取的范围距离车越近,车模反应越慢,从而越切外;选取的范围越远,反应越提前,车模越切内。所有的这些需要各位车手根据自己的实际情况合理的选取误差。

其实个人观看国赛车视频,大部分国赛车还是比较偏向切内一点的。

至此,我们就完成了误差获取,都可以调用

Err=Err_Sum();      //误差计算

这个函数,获取到摄像头收到数据的误差,然后就可以将误差放入PD,进行下一步控制。

2.PD控制

所谓PD控制就是PID算法的改进版,PID算法也不在这里赘述。

我使用的是位置式的PD,去掉了积分环节I。增量式与位置式PID的区别也不再阐述,智能车方向环大多是位置式PD算法。

PD的控制的误差传入就是上面我们计算的摄像头误差,由于我们期望车模永远贴着中线跑,所以设定值(期望值)不会变是0。

#define STEER_RIGHT  705  //舵机右打死,-
#define STEER_MID    782  //舵机归中
#define STEER_LEFT   857  //舵机左打死,+

#define LEFT_MAX     (STEER_LEFT- STEER_MID)//+
#define RIGHT_MAX    (STEER_RIGHT-STEER_MID)//-

//函数本体
int PD_Camera(float expect_val,float err)//舵机PD调节
{
   float  u;
   float  P=1.98; //参数需自行整定,这里仅作为参考
   float  D=1.632;
   volatile static float error_current,error_last;
   float ek,ek1;
   error_current=err-expect_val;
   ek=error_current;
   ek1=error_current-error_last;
   u=P*ek+D*ek1;
   error_last=error_current;

   if(u>=LEFT_MAX)//限幅处理
       u=LEFT_MAX;
   else if(u<=RIGHT_MAX)//限幅处理
       u=RIGHT_MAX;
   return (int)u;
}

//实际使用
Steer_Angle=PD_Camera(0,Err);//摄像头舵机PD调
Steer(Steer_Angle);

这是最传统的PD,没有什么修改,原汁原味。

江湖传言,纯P控制,跑到2m/s没问题,PD控制跑到2.5m/s没有问题,再往上提速就得看看分段PD,模糊PD了。

根据我自己实测,的确是这样。单独一套PD参数,图像做好上2.5m/s的确没问题的。

我实际使用的模糊PD,将会在后面的在电磁一章进行讲述。

得出PD算出的打角值后,我们将数据送到舵机占空比调整函数中即可。

流程如下

//while(1)中
Err=Err_Sum();               //误差计算
//中断中
Steer_Angle=PD_Camera(0,Err);//摄像头舵机PD调
Steer(Steer_Angle);          //实际占空比控制

舵机输出函数本体如下:

/*-------------------------------------------------------------------------------------------------------------------
  @brief     舵机电机输出函数
  @param     angle 输入舵机方向信号,+-LEFT_MAX,RIGHT_MAX之间
  @return    null
  Sample     Steer(angle);
  @note      舵机最终输出函数,由其他函数调用
                                           在给舵机前有限幅处理的
#define STEER_RIGHT  771  //舵机右打死,-
#define STEER_MID    851  //舵机归中
#define STEER_LEFT   931  //舵机左打死,+
-------------------------------------------------------------------------------------------------------------------*/
void Steer(int angle)
{
    if(angle>=LEFT_MAX)//限幅处理
       angle=LEFT_MAX;
   else if(angle<=RIGHT_MAX)
        angle=RIGHT_MAX;
    pwm_set_duty(STEER_PIN, STEER_MID+angle);//舵机调节
}

需要注意的是,我的输出值是基于归中值的相对值,范围是左右极限减去中值,因为我在最终的调整舵机占空比函数中用的是STEER_MID+angle,这里是angle就是基于中间值的正负偏差。

如果摄像头偏差是Err是0,那么PD算出来的结果也基本是0附近,舵机最终输出的就是STEER_MID+0,就是STEER_MID,轮子效果就是归中打直。

还有一个问题,就是正负号的问题。舵机控制中,有很多处理需要考虑正负号。

  1. 首先是误差获取,是用(屏幕中线-赛道中线),还是(赛道中线-屏幕中线)。
  2. PD里面的期望值,正常来说我的期望值是0,那么我的error_current是err-0,还是0-err。
  3. 舵机输出函数,输出的值是STEER_MID+angle,还是STEER_MID-angle。

这几个数值的正负号最好都要统一,不然的话误差会直接反过来。车子往右偏,结果轮子还在往右打。

或者误差是正数,结果舵机打的是负数的角,虽然不影响运行,但是看起来仍有些不舒服。

最后一个问题,控制周期的问题。我在国赛代码中有看到过两种控制策略:

  1. 整套流程放在while(1)中,摄像头每处理完一帧图片,接下来就计算偏差,计算PD,进行舵机控制。
  2. 舵机控制放在20ms中断中,因为舵机50Hz的频率,保证舵机准时输出。中线误差计算放在while(1)中计算。

这两种策略都是有道理的。

第一种策略保证了使用一帧图片,做一次控制,保证每一帧图像结束后就进行控制,精细度更高。

第二种控制策略是我在比赛中所使用的。因为我调车的时候,有时会接上图传或者打开屏幕,这样每一圈while(1)时间会有所不同。在打开屏幕时,一圈while(1)需要40ms;关闭屏幕一圈while(1)只需要10ms左右;只打开图传,一圈while(1)需要15ms左右。这就导致我在打开图传情况下调整的参数,不适配我关闭图传时的参数,导致控制出现问题。所以我将舵机控制放在定时器中,误差更新在while(1)中,只要一圈while(1)速度小于20ms,那么控制都是来得及的。

三、速度控制

1.开环控制

所谓开环控制,个人认为就是没有反馈的控制,这里特指电机的速度控制。

我们在将期望的占空比输入到电机的控制函数中,电机的速度会变快。但是具体多快,到没到我们的预期,阻力对他影响如何,这都无法得知,只能知道一件事,我期望他变快,我给电机的电压挺高的。

比如我直道给了3000,也就是3000/10000=30%的占空比,我期望他跑快一点。弯道给2000,20%的占空比,期望他慢一点。

然而车模实际运行需要抵抗阻力,而且加速减速需要反应时间,车模跑起来还会有惯性。很容易就出现加不起速,刹不住车的情况。在弯道还没减速到20%占空比,就已经出界了,这都是开环控制的弊端,所以一般建议初期使用开环,等到控制稍微成熟一点,使用闭环增强稳定性,也便于提速。

2.闭环控制

闭环就是有反馈的控制,我现在给出一个期望速度,经过计算得出一个占空比,然后就去测量车轮的速度情况如何,如果没到我的期望,那我就再给高一点的占空比。如果到了,那我就维持当前占空比;如果超了,那我给小一点,如果超的多了,那我甚至可以让他反转,让他强制减速。这个控制周期一般很短,我个人使用的10ms进行一次控制,队友用的5ms,都是可以的。

这个测量转速的装置是编码器。

2.1编码器

编码器把角位移或直线位移转换成电信号,前者称为码盘,后者称为码尺。按照读出方式编码器可以分为接触式和非接触式两种;按照工作原理编码器可分为增量式和绝对式两类。增量式编码器是将位移转换成周期性的电信号,再把这个电信号转变成计数脉冲,用脉冲的个数表示位移的大小。

第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
方向编码器、正交编码器、角度编码器

不同的主控芯片有不同的适用范围,参考原则见下图。

第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
根据主控芯片不同选择不同的编码器

实际使用时他就是测量车轮转速的一个设备,通过齿轮啮合,与车轮一同转动。

第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
编码器通过齿轮与车轮啮合

这里简单提一句,这三者齿轮啮合不要太紧,也不能太松,基本原则是每两个齿轮之间,能够通过一张A4纸,A4纸不会破,且用手转起来两边阻力基本一致即可。具体情况将会在机械篇里面讲解,敬请期待。

初始化编码器后,将它放到定时器中断中,定时读取数据,读完清零。

读到的数据越大,说明车轮转速越快。

初始化,读编码器数据代码如下:

#define ENCODER_L_CH1   TIM1_ENCOEDER_MAP3_CH1_E9
#define ENCODER_L_CH2   TIM1_ENCOEDER_MAP3_CH2_E11
#define ENCODER_L_TIM   TIM1_ENCOEDER

#define ENCODER_R_CH1   TIM9_ENCOEDER_MAP3_CH1_D9
#define ENCODER_R_CH2   TIM9_ENCOEDER_MAP3_CH2_D11
#define ENCODER_R_TIM   TIM9_ENCOEDER

encoder_dir_init(ENCODER_L_TIM,ENCODER_L_CH1,ENCODER_L_CH2);      //编码器初始化  左后轮
encoder_dir_init(ENCODER_R_TIM,ENCODER_R_CH1,ENCODER_R_CH2);      //编码器初始化  右后轮


//下述代码放在定时器中断中
Speed_Left_Real=(-encoder_get_count(ENCODER_L_TIM));//加个负号,保证向前走编码器是正数,倒退是负数,更符合直觉
Speed_Right_Real=(encoder_get_count(ENCODER_R_TIM));
encoder_clear_count(ENCODER_R_TIM);
encoder_clear_count(ENCODER_L_TIM);
Velocity_Control(Speed_Left_Real,Speed_Right_Real);//读取转速,闭环控制
2.1.1 沁恒使用方向编码器的bug

我之前使用过调车使用的是英飞凌的芯片,使用起来编码器一切正常。

后来根据规则使用了沁恒的CH32V307主控,编码器使用的是某邱家的1024线方向编码器,会出现bug。

就是有一个轮子跑着跑着会疯转。原因是有一个编码器在正转的时候会莫名其妙读到负数,然后PID认为误差过大,开始拉满占空比调整,轮子就疯转起来。

这个问题不止我一个人遇到,我也咨询过技术人员,得到的答复是直接读寄存器的值。

第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
关于编码器bug在群中的讨论
//-------------------------------------------------------------------------------------------------------------------
// 函数简介     定时器编码器解码取值
// 参数说明     timer_ch      定时器枚举体
// 返回参数     void
// 备注信息
// 使用示例    encoder_get_count(TIM2_ENCOEDER)  // 获取定时器2的采集到的编码器数据
//-------------------------------------------------------------------------------------------------------------------
int16 encoder_get_count(encoder_index_enum encoder_n)
{
    int16 result = 0;
    int16 return_value = 0;
    switch(encoder_n)
    {
        case TIM1_ENCOEDER:  result = TIM1->CNT;   break;
        case TIM2_ENCOEDER:  result = TIM2->CNT;   break;
        case TIM3_ENCOEDER:  result = TIM3->CNT;   break;
        case TIM4_ENCOEDER:  result = TIM4->CNT;   break;
        case TIM5_ENCOEDER:  result = TIM5->CNT;   break;
        case TIM8_ENCOEDER:  result = TIM8->CNT;   break;
        case TIM9_ENCOEDER:  result = TIM9->CNT;   break;
        case TIM10_ENCOEDER: result = TIM10->CNT;  break;
        default:             result = 0;                 break;
    }
    if(0xFF == encoder_dir_pin[encoder_n])
    {
        return_value = result;
    }
    else
    {
        if(!gpio_get_level((gpio_pin_enum)encoder_dir_pin[encoder_n]))
        {
            return_value = -result;
        }
		else
		{
			return_value = result;
		}
    }

    return return_value;
}

上面是方向编码器的读取代码,在读取寄存器数值后,又用IO口进行了方向的判断。

如果直接读寄存器的值,那么就失去了方向这一个数据,

当然,可以用角度编码器,或者正交编码器试试,不清楚有没有这个bug。

我个人也遇到了这个bug,我是左轮有这个问题,右轮一切正常。

我对左轮编码器数据直接取了绝对值,因为正常跑车过程中不会有负数出现。

在刹车时,用PID刹车1s,然后PWM给0。用PID是保证迅速刹车,用PWM给0是为了防止疯转,切断电机输出。车子一般在PID刹车时就停下来了,PWM给0就不会因为惯性再往前跑了。

当然,也有可能是硬件坏了,大家可以换块板子试试,或者换个编码器试试。

其他主控平台暂未发现这个bug,希望代码库尽快更新,修复这个bug。

2.2 PID

正常情况下,我们希望车模在直道跑到的快一点,弯道稍微慢一点。当速度足够高时,我们还会希望后轮进行差速,弥补舵机打角的不足。

想要控制车轮转速快速到达预期值,就需要闭环控制,闭环控制使用最大多的就是PID算法。

我使用的是增量式PI算法,大部分智能车也都是增量式PI算法,算法不再重复介绍,直接上代码。

/*-------------------------------------------------------------------------------------------------------------------
  @brief     PID控制
  @param     int set_speed ,int speed,期望值,实际值
  @return    电机占空比SPEED_MIN~SPEED_MAX
  Sample     pwm_R= PID_R(set_speed_right,right_wheel);//pid控制电机转速
             pwm_L= PID_L(set_speed_left,left_wheel ); //pid控制电机转速
  @note      调参是门玄学
-------------------------------------------------------------------------------------------------------------------*/
int PID_L(int set_speed ,int speed)//pid控制电机转速
{
    volatile static int out;
    volatile static int out_increment;
    volatile static int ek,ek1;
    float kp=1.46,ki=2.3;
  
    if(Go==7)//正常运行状态使用的参数
    {
        //float P_L=30;
        //float I_L=1.6;
        kp = P_L;//一套pi足矣,速度拨动不会太大
        ki = I_L;
    }
    else//发车阶段速度环要硬,不能超调晃动
    {
        kp = 20.0;//一套pi足矣,速度拨动不会太大
        ki = 0.9;
    }
    ek1 = ek;
    ek = set_speed - speed;
    out_increment= (int)(kp*(ek-ek1) + ki*ek);
    out+= out_increment;

    if(out>=SPEED_MAX)//限幅处理
        out=SPEED_MAX;
    else if(out<=SPEED_MIN)
        out=SPEED_MIN;
    return (int) out;
}

我理解的PID就是跟随,我在当前期望一个速度,输入进去,同时输入进一个当前转速,输出的就是PWM波的期望占空比,我的期望值有可能在随时改变,直道要加速,弯道要减速,还要差速,那就要做到输出与输入跟随的紧密,不超调,不震荡。中间计算过程无需关心,我只关系输入与输出。

第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
调车过程中的速度线
第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
黄线是期望,绿线是实际
第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
蓝线是期望,红线是实际

 上面两张图是我实际调出来的速度曲线图,通过调整合适的PI参数,是完全可以做到速度响应好的效果,提速快,刹车稳。

(这里打个广告,上面图是我的车使用蓝牙向上位机发送的数据,上位机可以根据收到的数据绘制出曲线图,相应的软件和通讯协议规范我之前的文章中有,传送门在这里:VOFA+上位机三种协议(FireWater,JustFloat,RawData)C语言参考代码_justfloat协议_Joshua.X的博客-CSDN博客)

这里提醒一下,调PID时候要让车在赛道上跑,收集实际数据。车模悬空时候的参数基本不用调就可以特别好看,但没什么用。

第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
空载速度线
第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
负载速度线

 这是我在群里看到的两张图,很有代表性。显然空载和负载曲线完全不同。

2.3棒棒算法

棒棒算法是补充PID的算法,他正常情况下不会作用。只在极限情况下使用,与PID配合。

具体作用是当当前转速与期望转速过大,直接拉满输出。这里的拉满是有正有负的。

保证了瞬间可以将速度拉上去,瞬间将速度降下来。

代码如下

#define SPEED_MAX   4000  //电机速度限幅,正
#define SPEED_MIN  -4000  //电机速度限幅,负

void Velocity_Control(int speed_left_real,int speed_right_real)//赛道类型判别,来选定速度
{
    int pwm_R=0,pwm_L=0;
    if(Go==7)//当标志位被置7后,正常进行速度控制
    {

        //速度决策




        //速度决策
        pwm_L= PID_L(Speed_Left_Set ,speed_left_real );//pid控制电机转速
        pwm_R= PID_R(Speed_Right_Set,speed_right_real);//pid控制电机转速

        //棒棒作为[PID的辅助
        if(Speed_Left_Set - speed_left_real>150)
        {
            pwm_L=SPEED_MAX;
        }
        else if(Speed_Left_Set - speed_left_real<-150)
        {
            pwm_L=SPEED_MIN;
        }
        if(Speed_Right_Set- speed_right_real>150)
        {
            pwm_L=SPEED_MAX;
        }
        else if(Speed_Right_Set- speed_right_real<-150)
        {
            pwm_L=SPEED_MIN;
        }
    }
    Motor_Left (pwm_L);
    Motor_Right(pwm_R);
}

当然,这样对PID的冲击非常大,会影响PID的稳定性。就像你骑自行车,刚起步的时候有人突然推你一把,你的速度能够立刻提起来,但是你不见得接受的住这突然的提速,很容易摔倒。

所以棒棒的阈值需要调整的比较高。我最后比赛没有使用。

2.4 ADRC

ADRC也是一种控制方式,作用和PID一样。

也是通过期望输出,实际转速之间通过计算,得到期望的占空比,不过由于我只听说过,完全没有接触过,这里只让大家知道这种算法,详细资料请自行获取。

3.速度决策

我的速度决策其实做的并不好,参数太少了,导致调整比较生硬。

化简代码如下:

int Base_Speed=330;     //基准速度
int Straight_Speed=400; //直道高速
float Shift_Ratio=1.5;  //变速系数
float Err_Diff=1.1;     //常规差速系数,差速过大容易导致侧翻

void Velocity_Control(int speed_left_real,int speed_right_real)//赛道类型判别,来选定速度
{
    int pwm_R=0,pwm_L=0;
    if(Go==7)//当标志位被置7后,正常进行速度控制
    {
        if(Electromagnet_Flag==0)//默认摄像头跑
        {
            Speed_Left_Set =Base_Speed;
            Speed_Right_Set=Base_Speed;
            if(Straight_Flag==1)//直道高速冲刺
            {
                Speed_Left_Set=Straight_Speed;
                Speed_Right_Set=Straight_Speed;
            }
            Speed_Left_Set =Speed_Left_Set -(MT9V03X_H-Search_Stop_Line)*Shift_Ratio;//变速
            Speed_Right_Set=Speed_Right_Set-(MT9V03X_H-Search_Stop_Line)*Shift_Ratio;//

            Speed_Left_Set =Speed_Left_Set -Err*Err_Diff;//差速
            Speed_Right_Set=Speed_Right_Set+Err*Err_Diff;
        }
        pwm_L= PID_L(Speed_Left_Set ,speed_left_real );//pid控制电机转速
        pwm_R= PID_R(Speed_Right_Set,speed_right_real);//pid控制电机转速
    }
    Motor_Left (pwm_L);
    Motor_Right(pwm_R);
}

我实际的控速测量要比这个复杂一些,主要是有出入库,坡道,横断,断路等元素,需要对速度做出把控。

只要能看懂我上述的代码,我开源的代码也是一样的,只是多了各种元素状态,不同速度而已。

  1. Base_Speed:车模在运行过程中我设置了一个基本速度,车模大部分时间是使用这个速度在跑;
  2. Straight_Speed:直道高速,车模前方是长直道,那么直接高速冲就完了。直道的判断会在元素文章里面讲。
  3. Shift_Ratio:变速系数,有效视野越短,说明是弯道,我的速度就需要在基准速度的基础上慢一点,视野比较长,就快一点。
  4. Err_Diff:差速系数,车模高速转弯时,仅靠舵机是会比较吃力,需要后轮进行差速,来辅助车模转弯,我的差速就是摄像头误差*差速系数,一边加,一边减,叠加到期望值中。误差越大,差速越大。但是不要太大,会导致车模侧。

基本的参数就是这四个,实际代码中有电磁速度,环岛速度,坡道速度,横断速度。还有不同元素对应不同的的差速系数,原理都是上面那一套,大家自行体会。

参数越多,调节起来越精确、就像山地车有那么多轮盘可以变速,因为不同的场地适应不同的轮盘,可以获得更好的效果。你用共享单车一套轮盘骑着走也不是不行,只是适应性差一些。

一个重点,参数越多,调节越精确,但是调节起来难度越大;参数少,各种参数适配会好做一点,但是效果会稍微差一点。

四、调参经验

1.方向

说实话,个人感觉对方向控制你影响最大的是机械。

每一套机械都会存在一套参数区间,不同的PD系数,不同的前瞻行,不同的误差权重都会带来不一样的效果。每套参数区间大小不一样,范围未知,只能凭借运气去试。所以参数越多,越难调。

机械就是摄像头高度,俯仰角度,摄像头位置这些,当你凭运气调整到一个比较好的位置,你的PD参数,误差权重,前瞻位置,都不用太大的调整,就可以跑的很流畅。

注:个人认为摄像头高度高一些会比较有利于图像,有利于调参。

我曾经就有一次,在调整完摄像头后随便给了个参数,车子跑的又快又稳,我还以为是运气好,参数对了。后期又调整了参数,发现其实参数对车模影响不大,机械影响要更大。

当然,你不能凭运气保证你的机械是合适的,该调参还是得调参。个人建议如下:

  1. 先将前瞻行,权重行放的位置低一点。
  2. 低速行驶下先用纯P控制速度开环处理。
  3. 等到开环控制到1.8m/s左右可以闭环,这时会瞬间感觉车灵活了,可以稍微提速。
  4. 到2.3/ms可以加上D,D可以增加车模的灵活性,属于锦上添花。
  5. 个人感觉P反应打角力度,D是灵敏度,刚开始调车小P大D效果好一点。
  6. 等到速度快了,2.5m/s就可以把前瞻行,权重往前放,有利于车模提速
  7. 行驶中,听到车模轮胎在地面发出“吱吱”漂移声,不是好事。说明前轮打角了,但是车子没有转弯,后轮在推着打角的前轮往前冲,车模前轮胎和赛道进行滑动摩擦,不是车轮滚动,建议直接换轮胎,这点会在后续的机械一章中讲解。
  8. 我个人的控制理念是一套控制算法控制全程,所以我基本没有对中线,边线,pd之类的进行特别处理。我只在元素处进行了必要的补线。当然这也根据不同人的想法,我也看到有大佬在技术报告中提到对中线进行滤波处理。这都可以的,只要效果好,没什么不行的。

以上均为个人建议,仅供参考。

2.速度

其实我只在前期调了速度曲线,想着让曲线更加丝滑,反应更快,更加贴着我的期望速度在跑。

但是后期我发现了一件事,我给的期望速度真的合理吗?

未必,我的期望速度不合理,那我实际速度贴的好又有什么用呢?

所以我将速度调整到反应迅速,提速,刹车都很灵敏的时候,我就不再看速度线了。因为有可能速度线中的超调,震荡有可能是适合车模差速过弯的。车模沿着速度线跑未必能跑出最好效果。

所以在不同场地时候,我更多的是调整期望速度,差速系数这些东西,没有动PI参数。

只在出库时候,有特殊要求,我调整了PI系数。

这里简单讲一下实际跑车中PI参数含义,以刹车为例。

  1. 在车模刹车中,纯P控制,就是车子慢慢刹车,直到刹停为止,P越大刹车阻力越大。
  2. I的作用:有一个微微的回弹,也就是超调。I越大,回弹次数越多,回弹越激烈。过大的I会导致控制发散,造成车子刹不住,还会“发疯”。
  3. 所以一般不建议I给太大,P可以适当给大一点。
  4. 不同的车子PI系数没有什么参考价值,我的队友的PI都给到好几万才有一点效果,我只给了几十。所以说效果好就行了,不必纠结参数。

大家也可以将期望车速给0。

  1. P越大,车子越难推,松手后车模只会慢慢回到原点,基本不会回弹。
  2. I越大,车子松手后,会像弹簧一样,在震动中逐渐停止。

最后就是我三年调车经验,在速度环中:P大能抑制I的超调。

第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制,智能车,汽车,单片机,开发语言,c语言,嵌入式硬件,算法
粉线期望,紫线实际

 在上图中,前三个框明显出现了超调现象。在后面的调车中,我增大P,超调变小了。

以上均为个人建议,仅供参考。

3.限幅

方向限幅是为了防止舵机打角过大,轮胎卡底盘,找到轮胎左右打角极限就好。

速度限幅是为了防止占空比输出异常,电机疯转。

速度限幅和供电有关系,380电机的额定电压是7.2v。

如果用正常的2S电池,满电8.4v,那么将占空比拉满了输出给到电机大概就是8v左右,满占空比10000,限幅给到95%~100%都是可以的。

如果用3S电池满电12.6v,拉满占空比给到电机,电机也能抗住,注意散热即可。

这里就劝告各位,调车过一会就让车歇一会,用个风扇吹一吹,保证电机不烧。我同学调车,调起来就根本停不下来,电机都烧了十好几个了,换一个电机参数也都是需要调整的,大家注意。

我比赛需要充电,电压高效率会高一些,所以我选择的是6s电池,满电25.2v。前面也看到我的限幅值在4000,也就是40%。在限幅情况下拉满占空比25.2*0.4=10.8v。

以后比赛应该用不到6s电池,大家使用2s或者3s电池,限幅给到95%~100%就好。

希望能够帮助到一些人。

本人菜鸡一只,各位大佬发现问题欢迎留言指出

qq:2296449414文章来源地址https://www.toymoban.com/news/detail-714560.html

到了这里,关于第18届全国大学生智能汽车竞赛四轮车开源讲解【4】--控制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 第十八届全国大学生智能汽车竞赛——摄像头算法(附带个人经验)

    参加了第十六,十七和第十八届全国大学生智能车竞赛,对摄像头的学习有部分心得,分享给大家,三届车赛,车赛生涯也算是到了尽头。打算从基础的算法开始,给各位一些个人看法,也是对车赛的一次总结。 闲话 :其实摄像头的算法有很多种,弄了两年摄像头,也只是

    2024年02月07日
    浏览(42)
  • 第十八届全国大学生智能汽车竞赛室外赛5g室外专项赛(湘大队)开源

            感谢十八届全国大学生智能汽车竞赛-室外赛的主办方和组委会让我们有了参加这次比赛的机会!当我们在参加5g室外专项赛时,我们发现很多的组又很多的同学都是第一次参加智能车竞赛对于智能车的了解还不是很深,对于代码如何编写需要哪些模块也不是很熟悉,

    2024年02月20日
    浏览(36)
  • NECCS|全国大学生英语竞赛C类|听力|短篇新闻|听写填空|16:40~17:10+17:30~18:10

    一、听写填空 1. 题型 2. 技巧 (1)利用间隙  浏览全文  积极预测 (2)边听边记 (3)注重检查 二、短篇新闻 1. 试题解读 2. 解题技巧 (1)预测要点,有的放矢 (2)掌握新闻六要素 (3)重点关注新闻导语 (4)词汇积累 a 政治与国际关系 b 经济类 c 灾难类 3. 题目类型 (

    2024年02月02日
    浏览(48)
  • 全国大学生智能汽车大赛(二):电感采样、卡尔曼滤波、方向控制代码

    全国大学生智能汽车大赛(一):摄像头识别赛道代码 全国大学生智能汽车大赛(二):电感采样、卡尔曼滤波、方向控制代码 全国大学生智能汽车大赛(三):上下位机通信协议及代码 🥳👹以下为正文😶‍🌫️🥸 摄像头: 8个数据口,一个串口,两eru中断        

    2024年02月02日
    浏览(47)
  • 2021全国大学生电子设计竞赛论文(智能送药小车(F题))(电赛论文模板)

    电赛是一个很奇妙的过程,可能有些人觉得电赛的门槛太高,那便意味着,当你决定要参加电赛的那一刻起,这一段路、这些日子就注定不会太轻松; 我现在回头看真的很感谢电赛,从前期备赛面对自己未曾涉猎的技术不知如何下手的迷茫与怀疑,再到后来四天三夜紧张到不

    2024年02月05日
    浏览(50)
  • 2020年高教社杯全国大学生数学建模竞赛---校园供水系统智能管理(Python代码实现)

    目录 💥1 概述 📚2 问题 🎉3 运行结果 👨‍💻4 Python代码 校园供水系统是校园公用设施的重要组成部分,学校为了保障校园供水系统的正常运行需要投入大量的人力、物力和财力。随着科学技术的发展,校园内已经普遍使用了智能水表,从而可以获得大量的实时供水系统运

    2024年02月11日
    浏览(41)
  • 全国大学生数学建模竞赛【高教杯】- 竞赛题目汇总

    目录 1992 年赛题 A 题 施肥效果分析 B 题 实验数据分解 1993 年赛题

    2024年02月07日
    浏览(48)
  • 2017年全国大学生电子设计竞赛综合测评题

    题目如下: 题目要求电源只能使用5V单电源、给运放使用5V单电源供电。 方波发生电路 有点像梯形是因为multisim上的LM324跟不上变化的速度,使用题目中的AD2302即可。 输出为接近5V的方波,使用滑动变阻器分压。 四分频 74LS74中包含俩个D触发器,二分频电路如下,从CLK出输入

    2023年04月15日
    浏览(49)
  • 第十五届全国大学生信息安全竞赛部分WriteUp

    做了10个,都是烂大街的题目,分数很低。CTF榜单186,以为稳进分区赛了。理论题算上变一千五百多名,华东南二百多名,进不去了,WriteUp也不想上传了。 不是密码选手,但密码非预期搞出来几个 签到电台 关注公众号给的提示“弼时安全到达了”,查找这几个字的中文电码

    2024年02月06日
    浏览(58)
  • 全国大学生数学竞赛备考——高数上(极限、导数、微分、积分、级数)

    全国大学生数学竞赛 竞赛进程分为两个阶段,第一阶段为全国大学生数学竞赛初赛(也称为预赛、赛区赛) 第二阶段为全国大学生数学竞赛决赛 非数学类:竞赛内容为大学本科理工科专业高等数学 ( 只有高等数学一门课程 )课程的教学内容,高等数学教材中出现的,包括选修的

    2023年04月08日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包