赞
踩
https://gitee.com/joshua_xu/the-18th-smartcarhttps://gitee.com/joshua_xu/the-18th-smartcar
注:以下方案有可能会用到一个或者多个下述变量,以下变量均在开源【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
注:文章中所有参数,角点范围之类的东西仅作为参考,实际参数,请根据需要实际调整!!!!!!!!!
实际上,智能车所有参数都需要根据你的实际情况进行调整,万万不可照搬不误!!!!!
智能车的电磁和摄像头是目前室内两大寻迹方式,摄像头我们前文一直在介绍,后续还会继续介绍一些元素的判断。
这里介绍一下电磁控制。
先贴上某飞家的电磁教程。
这里介绍一下我的方案。
我的电磁直立在舵机上,几乎没有前瞻,因为电磁主要用于断路,断路区间不会很长,0前瞻跑慢一点足够了。
电磁我放了5颗电感,但是由于运放只有四路通道,其实只接通了四颗。分别是最左,最右
中间,还有偏左边的一颗。
左侧的排线接口为数据线,中间的排母是TOF测距模块。
在代码中我实际使用到的是最左,最右,和反面的一颗电感,总共三颗电感。
左右电感用来寻迹,中间电感用来检测环岛。
在跑车之前需要记得调整运放板的电位器(滑动变阻器)。需要保证车子正放时,两边对称的电感信号值是基本一致的。不然车子即使正放,由于数据不对称车,子仍然是歪着跑的。
这里还涉及到环岛的电感数值变大的问题,拧运放倍率时,记得要在普通赛道上调整。调整后,再去环岛看一下,是否数据到达软件设计的环岛判断阈值,以及在其他地方不能随意到达阈值。不然出库看到电感过大就判环岛,那就完了。
一定要记得跑车前看一下电磁情况。
一定要记得跑车前看一下电磁情况。
一定要记得跑车前看一下电磁情况。
电磁信号是通过运放芯片放大,通过单片机ADC读取,ADC精度可由初始化决定。
- adc_init(ADC1_IN12_C2, ADC_12BIT);
- adc_init(ADC1_IN13_C3, ADC_12BIT);
- adc_init(ADC1_IN14_C4, ADC_12BIT);
- adc_init(ADC1_IN15_C5, ADC_12BIT);
精度肯定尽可能的高,我这里最高开到了12Bit。(理论0-4095,实际到3500左右就封顶了,这和硬件设计有关)
需要的基本变量如下:
- #define ADC_FILTER_TIME 5
- #define ADC_NUM 4
-
- volatile uint16 ADC_Raw_Value[ADC_NUM][ADC_FILTER_TIME];//adc原始数据,,第一维是指第某个电感,第二纬是五次的数据
- volatile uint16 ADC_Max_Value[ADC_NUM];//adc每个通道最大值,用于滤波
- volatile uint16 ADC_Min_Value[ADC_NUM];//adc每个通道最小值,用于滤波
- volatile uint16 ADC_Sum_Value[ADC_NUM];//5个数据求和,去掉最大,最小值
- volatile float ADC_Fil_Value[ADC_NUM];//adc滤波后的值
- volatile float ADC_Nor_Value[ADC_NUM];//adc每个通道归一化后的值
- volatile float ADC_Sum=0;
- volatile float ADC_Dif=0;
- volatile float ADC_Err=0;//最终使用的电磁误差
数据处理需要分5步:
首先是原始数据的收集。由于原始数据具有很高的偶然性。我在收集数据时收集5次数据,以便在后面对电磁数据进行滤波处理。
- /*-------------------------------------------------------------------------------------------------------------------
- @brief 电感数据获取
- @param null
- @return null
- Sample ADC_Get_Value();
- @note 直接读数,放在二维数组里,第一维是第某个电感,第二纬是一次性读5个数据,五次数据
- -------------------------------------------------------------------------------------------------------------------*/
- void ADC_Get_Value(void)//读值
- {
- int i=0;
- for(i=0;i<ADC_FILTER_TIME;i++)
- {
- ADC_Raw_Value[0][i]=adc_convert(ADC1_IN12_C2);//最左
- ADC_Raw_Value[1][i]=adc_convert(ADC1_IN15_C5);//左2
- ADC_Raw_Value[2][i]=adc_convert(ADC1_IN13_C3);
- ADC_Raw_Value[3][i]=adc_convert(ADC1_IN14_C4);//最右
- }
- }
我开了一层循环,每个电感收集5次数据,作为一轮数据。其中二维数据的第一维度是电感序号,第二纬度是电感第某次的数据值。
(其实开个二层循环更好,对于后续增加电感也很方便)
在我们获取原始数据时,我们是获取了五次的数据值,这五次数据作为一轮。下面我们要找到这五次数据的最大,最小值。用于后续处理。
找最大最小值的方式很简单,
- //将每个电感的第一次数据存下来
- for(i=0;i<ADC_NUM;i++)//数据准备
- {
- ADC_Max_Value[i]=ADC_Raw_Value[i][0];
- ADC_Min_Value[i]=ADC_Raw_Value[i][0];
- }
-
- //后续数据和他比较
- for(i=0;i<ADC_NUM;i++)//两重循环,取出每组adc的最大值,最小值
- {
- for(j=0;j<ADC_FILTER_TIME;j++)
- {
- if(ADC_Max_Value[i]<ADC_Raw_Value[i][j])
- ADC_Max_Value[i]=ADC_Raw_Value[i][j];
- if(ADC_Min_Value[i]>ADC_Raw_Value[i][j])
- ADC_Min_Value[i]=ADC_Raw_Value[i][j];
- }
- }
到这一步,我们已经获取到每一颗电感5次数据中的最大,最小值。
下面需要对这五次数据进行均值滤波。简单的说就是5次数据,去掉最大的,去掉最小的,剩下三个取平均。这样可以克服意外情况带来的数据读取偏差。
-
- for(i=0;i<ADC_NUM;i++)//5次数据求和
- {
- for(j=0;j<ADC_FILTER_TIME;j++)
- {
- ADC_Sum_Value[i]+=ADC_Raw_Value[i][j];
- }
- }
-
- //(5次数据求和值-最大-最小)/3
- ADC_Fil_Value[i]=((float)(ADC_Sum_Value[i]-ADC_Max_Value[i]-ADC_Min_Value[i])/((float)ADC_FILTER_TIME-2));
这里我是分开讲述的,我实际代码中,为了减小内存开销,是在写在一起的。
大家直接使用即可。
- int i=0,j=0;
- for(i=0;i<ADC_NUM;i++)//数据准备
- {
- ADC_Max_Value[i]=ADC_Raw_Value[i][0];
- ADC_Min_Value[i]=ADC_Raw_Value[i][0];
- ADC_Sum_Value[i]=0;
- }
- for(i=0;i<ADC_NUM;i++)//两重循环,取出每组adc的最大值,最小值
- {
- for(j=0;j<ADC_FILTER_TIME;j++)
- {
- ADC_Sum_Value[i]+=ADC_Raw_Value[i][j];
- if(ADC_Max_Value[i]<ADC_Raw_Value[i][j])
- ADC_Max_Value[i]=ADC_Raw_Value[i][j];
- if(ADC_Min_Value[i]>ADC_Raw_Value[i][j])
- ADC_Min_Value[i]=ADC_Raw_Value[i][j];
- }//然后求和,去掉最大最小,取平均
- ADC_Fil_Value[i]=((float)(ADC_Sum_Value[i]-ADC_Max_Value[i]-ADC_Min_Value[i])/((float)ADC_FILTER_TIME-2));
- }//此时的值就是滤波后的值
这里讲一下归一化处理,到这一步,我的电感数据理论上是从0~4095,(由于硬件平台差异,我的只能到3500)但是如果换一个信号发生器,这个数值会变的,具体变多少没人知道。
所以为了车模有更强的适应性,我们需要将数据进行归一化处理,所谓归一化,就是将数据从
0~4096映射到0~1。
如果我车模正放在自家赛道上,左右电感理论上数值差不多,那么左右值可能是都是1000,那么换了一个赛道,换一个信号发生器,电磁线直径都和自家实验室不一样,那么左右电感信号有可能是1500。车子稍微歪一点,带来的误差就会比原来要大,那么车子就会跑出问题。
为此,我们将所有数据进行归一化处理,看一下车子在当前赛道的最大值是多少,当前电感值占最大值的百分之多少。这样可以最大程度,增强车模稳定性。
归一化公式如下
其中的Xmin,在智能车里面就是0,因为电感最小值就是没有电感信号0.
电感最大值可以调整车模位置,进行寻找。
那么公式就化简成这样
这里的x就是滤波后的X的值。我在实际使用时,将归一化放大了100倍,让数据处于0~100之间,避免过多的小数浮点运算带来的误差以及性能的浪费。
- for(i=0;i<ADC_NUM;i++)//归一化处理
- {
- ADC_Nor_Value[i]=(ADC_Fil_Value[i]/3500)*100;//归一化后,放大100倍,数据处于0~100
- }//这里的3500是实测,我的这套硬件开的12bit,他的adc读满是3500
数据再经过归一化处理后,就变成了我们正常跑车时候使用的数据了。
我使用了最左,最右两个电感进行差比和运算,差比和的结果代表者车子距离中线(电磁线)的偏差。
具体差比和原理,大家看一下某飞的文章。
- ADC_Sum=ADC_Nor_Value[0]+ADC_Nor_Value[3];
- ADC_Dif=ADC_Nor_Value[0]-ADC_Nor_Value[3];
- if(ADC_Sum>10)
- {
- ADC_Err=(ADC_Dif/ADC_Sum)*100;//用于电磁控制的err从这里计算出来的
- }
在之前的文章中,我有说过利用电磁来做出界保护,正好在电磁这里和大家说一下。
- if(Stop_Flag==0)//在正常情况下
- {
- if(Barricade_Flag==0)//且不是在横断过程中
- {
- if(sum<5&&Img_Disappear_Flag==1)
- {//电感过小,图像丢失,还不是路障
- count++;
- if(count>=10)//有10次机会,当累计10次四个电感数据之和小于某一阈值,直接停车
- {
- count=0;
- Stop_Flag=2;//这个标志位给2是立刻停下,在control文件中有阐述
- }
- }
- else if(Search_Stop_Line>=60&&sum<=5)//或者最长白列很长,但是电感值很小
- {//总之,出界保护建立在电感值非常小,图像要么丢,要么很离谱,那么直接刹车保护
- Stop_Flag=2;
- }
- }
- }
我并不是电磁数据小就立刻停车,因为只要停车,就不会再启动了,除非重新发车。
我有个10次缓冲的机会。当累计10次电感值过小,才会触发保护。确保不轻易停车
我简单说一下电磁车的元素处理。
由于电磁车数据很少,基本只有电感这一套数来据源。
电感数据少,但是可以通过排布的不同,获取不同的数据,有横放,竖放,外八,内八,放在电感架顶部,电感架底部等多种做法不同的放置方法。
对不同赛道处理不同,比如外八/内八对弯道的预测效果比较好,横放对直道感应好一些。剩下的由于我对电磁涉猎不多,这里不展开讲解,大家有兴趣的自行查阅资料。
至于元素判断那就可以把所有电感归一化后的数据发送到上位机上,用曲线图展示出来,手推车看不同电感在经过不同元素的变化情况,找到元素的特征点,并进行对应打角,减速之类的处理。
个人使用的模糊控制是建立在pid的基础上,主要是对pid系数的控制。
我对模糊控制原理不是很懂,故不做讲解,主要讲使用。
想了解原理的,自行查阅资料,我就不带歪你们了。
首先,我对普通pd算法的理解。
p越大,转弯越迅速,但是对于直道越容易抖动。因为一点点的误差,乘以的p很大,那么反馈回去的值就很大,易发生震荡。
d越大,实测会引发高频震荡,因为d乘的(本次误差-上次误差),也就是误差变化率。他对未来的“预判”效果越严重,也会导致问题。
最简单的控制优化,就是分段pid,如果误差小于某一阈值,那么p就给小一点,大于某一阈值,p给大一点,这样可以在直道更容易稳定,弯道可以灵活转弯。
只分两段肯定是不够的,想要好一点可以多层分段,然而实际跑车过程中路况复杂,而且pd参数也不能只靠当前误差来确定。
那么我们就对误差进行超级细分。
以下是个人对模糊PID使用的理解,不见得对!仅供参考。
以下是个人对模糊PID使用的理解,不见得对!仅供参考。
以下是个人对模糊PID使用的理解,不见得对!仅供参考。
模糊控制这里我们引入了误差变化率,这一变量。
误差变化率=本次误差-上次误差;
在实际调用过程中如下:(我只用了模糊P,D是固定的)
- *-------------------------------------------------------------------------------------------------------------------
- @brief 电磁PD控制
- @param err
- @return 舵机打角值
- Sample Steer_Angle=PD(Err);//舵机PD调
- @note null
- -------------------------------------------------------------------------------------------------------------------*/
- int PD_ADC(float err)//舵机PD调节
- {
- int u;
- float P=1.18;//p动的
- float D=2.56;//d死的
- volatile static float error_current,error_last;
- float ek,ek1;
- error_current=err;
- ek=error_current;
- ek1=error_current-error_last;
- P=Fuzzy_P(err,ek1);//传统模糊PID
- D=1.185;//d死的
- 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;
- }
因为误差变化率pd里面有用到,我就直接调用了pd里面的ek1;
模糊PID就相当于一个二维函数,输入两个参数,输出一个结果。
输入的是误差,误差变化率,任意一个参数增大,会导致结果增大,两个参数增大,结果会更大。增大多少,需要考虑隶属度和模糊表,这个我们不必关心。
Fuzzy.c文件内容如下:
- #include "zf_common_headfile.h"
- #include "Fuzzy.h"
-
- #define PB 6
- #define PM 5
- #define PS 4
- #define ZO 3
- #define NS 2
- #define NM 1
- #define NB 0
-
- 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;
-
- float Fuzzy_P(int E,int EC)
- {
-
-
- //只要改下面这几行参数
- //这玩意没什么规律,p越大,转弯越好,直道会有抖动,p小转不过来,凭感觉调
- //建议先用单套pd,看看车子正常的p大概在什么范围,下面的p就会有方向
- float EFF[7]={-100,-80,-60,0,60,80,100};//摄像头误差分区
- /*输入量D语言值特征点*/
- float DFF[7]={-80,-60,-20,0,20,60,80};//误差变化率分区
- /*输出量U语言值特征点(根据赛道类型选择不同的输出值)*/
- float UFF[7]={0,0.36,0.75,0.996,1.36953,1.7098,2.185};//限幅分区
- //只要改上面这几行参数
-
-
- int rule[7][7]={
- // 0 1 2 3 4 5 6
- { 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
- };
-
- /*隶属度的确定*/
- /*根据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;
- }
-
- float Fuzzy_D(int E,int EC)
- {
-
- //只要改下面这几行参数
- /*输入量P语言值特征点*/
- float EFF[7]={-60,-40,12,0,12,25,60};//摄像头误差分区
- /*输入量D语言值特征点*/
- float DFF[7]={-40,-20,-7,0,7,20,40}; //误差变化率分区
- /*输出量U语言值特征点(根据赛道类型选择不同的输出值)*/
- float UFF[7]={0,1.5,1.9,2.6,3.9,4.3,5.836}; //限幅分区
- //只要改上面这几行参数
-
-
- int rule[7][7]={
- // 0 1 2 3 4 5 6
- { 6 , 1 , 2 , 3 , 4 , 5 , 6},//0
- { 1 , 2 , 3 , 4 , 5 , 6 , 5},//1
- { 2 , 3 , 4 , 5 , 6 , 5 , 4},//2
- { 3 , 4 , 5 , 6 , 5 , 4 , 3},//3
- { 4 , 5 , 6 , 5 , 4 , 3 , 2},//4
- { 5 , 6 , 5 , 4 , 3 , 2 , 1},//5
- { 6 , 5 , 4 , 3 , 2 , 1 , 0},//6
- };
- int Pn=0,Dn=0,Un[4]={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;
- }
-
Fuzzy.h文件内容如下:
- #ifndef __FUZZY_H__
- #define __FUZZY_H__
-
- #include "zf_common_headfile.h"
-
- float Fuzzy_P(int E,int EC);//第一个参数是误差,第二个是误差变化率
- float Fuzzy_D(int E,int EC);
-
- #endif
使用模糊PID代码时候,只要修改下面这三个地方就好。
- //只要改下面这几行参数
- //这玩意没什么规律,p越大,转弯越好,直道会有抖动,p小转不过来,凭感觉调
- //建议先用单套pd,看看车子正常的p大概在什么范围,下面的p就会有方向
- float EFF[7]={-100,-80,-60,0,60,80,100};//摄像头误差分区
- /*输入量D语言值特征点*/
- float DFF[7]={-80,-60,-20,0,20,60,80};//误差变化率分区
- /*输出量U语言值特征点(根据赛道类型选择不同的输出值)*/
- float UFF[7]={0,0.36,0.75,0.996,1.36953,1.7098,2.185};//限幅分区
- //只要改上面这几行参数
这三个数组我是这么理解的。
误差:中间是0,最左最右是误差最大最小值。
中间就根据误差实际情况,找到经常出现的误差值
误差变化率:中间是0,最左最右是变化率的最大最小值
中间值就根据实际情况,选取经常出现的值。
每次进行模糊运算时,根据你的误差,误差变化率在哪一个限幅区间,在限幅区间中,按照一定的规则选择这个区间中的某一个值,作为本次的模糊结果,
限幅的话就基于常规pd系数,常规状态下的pd系数稍微放大一点,给到最值,中间就合划分即可。
整体来说,模糊PID调节是非常模糊的,就是一个凭着感觉调,没啥好经验。
希望能够帮助到一些人。
本人菜鸡一只,各位大佬发现问题欢迎留言指出。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。