在文章的开头首先叠个甲,本人也只是一名普通的刚入门大一新生,对智能车有一个初步的了解 。在调试自己的车的时候总结了一些电感采集的经验以供分享 ,如有错误或者不足的地方,欢迎指正。
文章目录
智能车电感介绍
我的车采用的是三轮,前轮无电机, 后面左右两轮各有一个电机。因此我们需要采集左右两边的电感值来计算pid算法中的error来实现对小车的控制,所以全车只有两个电感均匀分布在左右两侧 。但是 ,现实往往没有理想这么简单,对于我们的车,特别是像我们大一这种结构还不是很完善的车来说 ,电感的传递波动会很多。 并且不同的赛道 ,电感值也会有偏差, 无论是左右的横向对比, 又或者是单左边或者右边的纵向对比 都会造成难以避免的误差。本文意在通过软件的方式尽量减小这一误差 ,下面便是一些解决办法。
1.电感滤波
首先进行电感滤波,对于电感来说 ,采集难免时难免会出现不准的情况 。因此为了过滤掉这些不准的地方, 我们选择使用一次均值滤波和一次去极值滤波的方式来将误差尽量缩小
(1)均值滤波
首先均值滤波的思想便是将多次采集的电感值取平均, 我们为了使程序简洁易懂 ,选择将其封装成一个函数如下。(建议加在系统自带的adc函数内)
// @brief ADC均值滤波
// @param adcn 选择ADC通道
// @param resolution 分辨率
// @param count 均值滤波次数
// @return uint16 转换的ADC值
// Sample usage: adc_mean_filter (ADC_P10, ADC_12BIT, 10)
//
uint16 adc_mean_filter (ADCN_enum adcn, ADCRES_enum resolution, uint8 count)
{
uint8 i;
uint32 sum;
sum = 0;
for(i=0; i<count; i++)
{
sum += adc_once(adcn, resolution); //累加
}
return sum/count; //求平均
}
然后我们便可在adc采集函数中调用此函数直接输出均值滤波后的电感的值
(2)去极值滤波
对于上述滤波后 ,电感值得到一点稳定 。但是有一种情况, 那就是一瞬间电感值突然很大或者很小。 上面的均值滤波并不能很好的解决这个问题 。因为由于运行时间很快 一瞬间的很大或者很小可能导致整个均值滤波的值都很大或者很小, 所以我们又引用去极值滤波方法, 所谓去极值, 便是将最大和最小去除 。
在上面均值滤波中 ,我们已经可以用一个函数直接计算出均值滤波之后的值。 那我们不妨, 直接将这些数据存入数组中 ,并且把每一个均值滤波后的电感值看做去极值滤波中的一个普通的直接测量得到的电感值, 再将这些电感去极值后再求平均得到最终的电感值。其中代码块如下
首先我们需要一个去极值滤波函数 ,对此我们将其封装为下图
int16 I_Median_Average_Filter(int16 *ADC) //去掉最大值和最小值(传进来的是数组,存储的是电感值)
{
uint8 i;
int16 max,min; //定义极值以及和
int16 sum = 0;
max = ADC[0]; //初始化最小值和最大值
min = ADC[0];
for(i=0;i<sizeof(ADC);i++) // sizeof(ADC)是传进来的电感数组的长度
{
if(max<ADC[i])max = ADC[i]; //找出最大的电感
if(min>ADC[i])min = ADC[i]; //找出最小的电感
sum += ADC[i]; //累加
}
sum =(sum-max-min)/(sizeof(ADC)-2); //电感的累加值减去最大值和最小值 再求平均
return sum; //将去极值的值传回原函数
}
这样我们就将去极值函数封装完成, 下面我们将均值滤波函数和去极值滤波函数一起封装成一个函数 。结合起来形成一个双重滤波,便可以将滤波这边的问题解决
#define FILTER_N 5 //滤波深度
uint16 adc_date[2]; //储存电感采集值 原始值2个电感
int16 Left_Adc=0,Right_Adc=0;//电感值
//注此处两个变量为全局变量,这样在其他文件中也可以使用 注意要包含extern的头文件
float AD_Date_Fitier()
{
uint8 i;
int16 filter_buf_L[FILTER_N]; //左电感储存数组
int16 filter_buf_R[FILTER_N]; //右电感储存数组
//--------均值滤波--------------
for(i = 0; i <FILTER_N; i++)//采值
{
filter_buf_L[i] = adc_mean_filter (ADC_P01,ADC_12BIT,10); //将多个均值滤波后的电感传入左电感数组
filter_buf_R[i] = adc_mean_filter (ADC_P00,ADC_12BIT,10); //将多个均值滤波后的电感传入右电感数组
}
//--------去极值求平均---------
adc_date[0]= I_Median_Average_Filter(filter_buf_L);//左电感最终值
adc_date[1] =I_Median_Average_Filter(filter_buf_R);//右电感最终值
// 用adc_date数组再存储一次是为了后面归一化 如果不用归一化可以直接等于
Left_Adc = adc_date[0];//左电感最终值
Right_Adc = adc_date[1];//右电感最终值
return 0.0;
}
至此 我们的电感滤波完成 并且封装成了一个AD_Date_Fitier()函数。
2.电感归一化
(1)为什么要归一化
刚刚经过滤波,我们得到了较为精准的左右电感采集值,横向的电感值得到了优化, 但是纵向的电感值却没有得到很好的矫正。 举一个很简单的例子: 当比赛的场地和你训练的场地的电感的电源不同的时,场地的电感值就会变,可能就会导致你原来调的参数并不能很好的驱使车子运作。并且或许你左右电感的特性可能不同,也许左边电感更灵敏或者更容易变大一点,这样也会导致电感值测量的误差。所以我们可以对采集到的电感值做归一化处理。所谓归一化,便是给左右电感一个统一的标准(0-100)之间,无论其中一个电感多灵敏多大,两侧的电感值都在这个范围之内变化,类似于给尺子标上了刻度以及零刻度。当然用差比和法也能做到,但是在差比和之前进行归一化,可以有效降低误差。由此可得归一化可以有效降低电感放大倍率的影响,并且非常有效的使得中线位置更加稳定(如果对我以上解释不理解的话可以先看下面的代码和注释再来看)
(2)电感归一化的实现
对于电感归一化的实现,首先我们需要测量出小车在运动过程中的最小值和最大值,再根据最小值和最大值对其进行归一化处理,得到一个0-1的数,再将其乘以100便得到了归一化后的电感值。为了方便起见,我们仍然将其封装成一个函数。代码如下。
uint16 adc_date[2]; //储存电感采集值 原始值2个电感
uint16 adc_max[2]={3000,3000}; //归一化电感最大值
uint16 adc_min[2]={0,0}; //归一化电感最小值 (给定一个需要)
void normalize_date(void) //电感归一化
{
int16 JSADC_DATE[2];//这个数据类型必须是有符号的 用于计算储存
unsigned char i;
for(i=0;i<2;i++) //归一化处理
{
//注意此处的adc_date数组中存储的是上面经过两次滤波后的左右电感值 这也是我将其设成全局变量的原因
//i=0的时候是左电感 i=1的时候是右电感
if(adc_date[i]<adc_min[i]) adc_min[i]=adc_date[i];//刷新最小值
if(adc_date[i]>adc_max[i]) adc_max[i]=adc_date[i];//刷新最大值
JSADC_DATE[i]= (adc_date[i]-adc_min[i])*100/(adc_max[i]-adc_min[i]); //归一化处理 将得到的数据全部归类到0-1之间 并且乘以100
if(JSADC_DATE[i]<=0) //
JSADC_DATE[i]=0;
else if(JSADC_DATE[i]>=100)
JSADC_DATE[i]=100;
} //当最大值或者最小值超过设定的范围之内的时候 避免误差
Left_Adc = JSADC_DATE[0];//左电感最终值
Right_Adc = JSADC_DATE[1];//右电感最终值
}
至此,我们便将电感采集即其处理全部完成,并且将其封装成了一个normalize_date()函数
3.电感部分整体代码框架
(1)将均值滤波函数添加到库函数中去(也可以不添加直接添加到自己的adc文件中)
库函数zf.adc.c中
// @brief ADC均值滤波
// @param adcn 选择ADC通道
// @param resolution 分辨率
// @param count 均值滤波次数
// @return uint16 转换的ADC值
// Sample usage: adc_mean_filter (ADC_P10, ADC_12BIT, 10)
//
uint16 adc_mean_filter (ADCN_enum adcn, ADCRES_enum resolution, uint8 count)
{
uint8 i;
uint32 sum;
sum = 0;
for(i=0; i<count; i++)
{
sum += adc_once(adcn, resolution); //累加
}
return sum/count; //求平均
}
并在库函数zf.adc.h中将该函数的声明自己添加进去
(2)自己创建一个adc.c和adc.h文件
adc.h文件中
float AD_Date_Fitier();
extern uint16 adc_date[2]; //左右电感采集值
extern int Left_Adc,Right_Adc;//左右电感值
extern void normalize_date(void);//电感归一化
adc.c文件中
#include "headfile.h"
uint16 adc_date[2]; //储存电感采集值 原始值2个电感
int16 Left_Adc=0,Right_Adc=0;// 左右电感值
uint16 adc_max[2]={3000,3000}; //归一化电感最大值
uint16 adc_min[2]={0,0}; //归一化电感最小值 (给定一个需要)
#define FILTER_N 5 //滤波深度
float AD_Date_Fitier()
{
uint8 i;
int16 filter_buf_L[FILTER_N]; //左电感储存数组
int16 filter_buf_R[FILTER_N]; //右电感储存数组
//--------滑动滤波--------------
for(i = 0; i <FILTER_N; i++)//采值
{
filter_buf_L[i] = adc_mean_filter (ADC_P01,ADC_12BIT,10); //左
filter_buf_R[i] = adc_mean_filter (ADC_P00,ADC_12BIT,10); //右
}
//--------冒泡排序去极值求平均---------
adc_date[0]= I_Median_Average_Filter(filter_buf_L);//左
adc_date[1] =I_Median_Average_Filter(filter_buf_R);//右
Left_Adc = adc_date[0];//左电感滤波最终值
Right_Adc = adc_date[1];//右电感滤波最终值
return 0.0;
}
void normalize_date(void) //电感归一化
{
int16 JSADC_DATE[2];//这个数据类型必须是有符号的 用于计算储存
unsigned char i;
for(i=0;i<2;i++) //归一化处理
{
if(adc_date[i]<adc_min[i]) adc_min[i]=adc_date[i];//刷新最小值
if(adc_date[i]>adc_max[i]) adc_max[i]=adc_date[i];//刷新最大值
JSADC_DATE[i]= (adc_date[i]-adc_min[i])*100/(adc_max[i]-adc_min[i]); //归一化处理 将得到的数据全部归类到0-1之间
if(JSADC_DATE[i]<=0)
JSADC_DATE[i]=0;
else if(JSADC_DATE[i]>=100)
JSADC_DATE[i]=100;
}
Left_Adc = JSADC_DATE[0];//左电感最终值
Right_Adc = JSADC_DATE[1];//右电感最终值
}
至此,便可以在其他函数中调用这两个函数来进行电感采集(注意使用时记得在使用的函数中包含adc.h的头文件)。
下面本人代码使用的案列 仅供参考
(3)整体的使用
pid.c文件中
#include "headfile.h" //该头文件中已经包含了adc.h等等头文件
struct pid_parameter pid={0,6000,57,0.0,0,1050,6000,1050,6000};
float powerleft=8000;//左边电机转速
float powerright=8000;//右边电机转速
void pidfunction( struct pid_parameter* pid,float*powerleft,float*powerright){
AD_Date_Fitier();//采集电感
normalize_date();//对电感进行归一化
pid->error=(float)(float)(Left_Adc-Right_Adc)/(float)(Left_Adc+Right_Adc); //计算出error的值
................
.................
}
感谢诸位的聆听