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

您所在的位置:网站首页 速度控制范围 第18届全国大学生智能汽车竞赛四轮车开源讲解【4】

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

2024-07-15 20:46| 来源: 网络整理| 查看: 265

开源汇总写在下面 第18届全国大学生智能汽车竞赛四轮车开源讲解_Joshua.X的博客-CSDN博客  开源链接写在下面 https://gitee.com/joshua_xu/the-18th-smartcaricon-default.png?t=N7T8https://gitee.com/joshua_xu/the-18th-smartcar

注:文章中所有参数,角点范围之类的东西仅作为参考,实际参数,请根据需要实际调整!!!!!!!!!

实际上,智能车所有参数都需要根据你的实际情况进行调整,万万不可照搬不误!!!!!

一、控制方法 1.控制对象基本介绍 1.1舵机

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

C车模允许使用的有S3010,U400这两款舵机

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

舵机占PWM脉宽与转动角度之间关系

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

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

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

PWM初始化函数

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

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

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

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

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

\frac{20ms\frac{}{}}{10000}=\frac{1.5ms\frac{}{}}{x}

解得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附近,符合理论估计。

拉杆长度,机械偏差,轮子磨损,都会影响舵机中值

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

车子放正,左边地砖缝作为参考线,舵机归正推着走,看误差

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

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

找到了中值,左右极值后,就可以自由的让车模左右转动了。改变的就是这个变量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没问题,实测什么样我就不知道。各位准备好钱,自行尝试。

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

1.2.电机

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

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

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

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

电机驱动的两种方式 驱动方式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=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=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是正转,但实际你的车是反转,这种情况就把电机接线,或者代码改一个就好(一般建议改代码,电机接线不好改)。

二、方向控制

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

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

方向控制流程图

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

本文将讲解计算视野内中线,误差获取/计算偏差,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.单行控制/领跑行控制

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

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

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>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=LEFT_MAX)//限幅处理 angle=LEFT_MAX; else if(angleCNT; 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(out150) { pwm_L=SPEED_MAX; } else if(Speed_Left_Set - speed_left_real150) { pwm_L=SPEED_MAX; } else if(Speed_Right_Set- speed_right_real


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3