根据赛题自动车的要求,自动车需要电磁巡线,陀螺仪越障。主要是难点就是这两个,其他的就是C语言逻辑上的问题。当时还是比较编程序的能力还是比较菜,感觉很难,现在觉得还好。回归正题,当时首先解决最难的问题,就是电磁巡线,需要学习PID算法,这个后面详细介绍,但是我们只需要使用PID算法中的P就可以解决电磁巡线问题。简单的来说,使用当前的值减去过去的值,这个误差值乘一个Kp,得到的值加在车的左右速度上。
一、电磁巡线
我们使用四路电感,两横两竖,之前也尝试过三路电感,但是三路电感在我们的车上效果不太行,而且四路电感比较简单,遇到直角弯,竖电感起作用控制转向,而且遇到环岛时多次调试也可以调出很好的效果。理论上是可以使用四路电感进出环岛的,而且我之前也是不调任何参数进出环岛的,车走的路径也是很丝滑,不过好景不长,第二天就BBQ了,很怪,我现在觉得是当时电磁的电流和车的电量等等很多不可控因素。最后的解决办法只能是当中间两个横电感数值变为原来的接近两倍,就执行左后速度不同的延时函数,有时候STM32F103的延时时间还不一样,应该是和电池的电量有一定的关系。
对了,电磁巡线是需要使用32F1的ADC通道,采值需要滤波,但是学长交了我们好多种滤波方式,给了我们一个可以直接用的代码,后来我在此基础上学习了归化处理,归化处理就是把值的范围缩小到了0~100,这样ADC值就不会波动那么大了,倒是也不错。但是在我车上的效果不太行,我觉得是我当时比较菜吧。
先来解释一下ADC采值滤波算法,代码如下。
void ADC_init()
{
ADC_InitTypeDef ADC_InitStruct;
GPIO_InitTypeDef GPIO_InitStruct;
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1|RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AIN;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3; //添加口 需要修改的地方
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
ADC_DeInit(ADC1);
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; //独立模式
ADC_InitStruct.ADC_ContinuousConvMode=DISABLE; //不开启扫描
ADC_InitStruct.ADC_DataAlign=ADC_DataAlign_Right; //数据右对齐
ADC_InitStruct.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; //触发软件
ADC_InitStruct.ADC_NbrOfChannel=3; //ADC的通道数目 着重注意一下需要修改
ADC_InitStruct.ADC_ScanConvMode=DISABLE;
ADC_Init(ADC1,&ADC_InitStruct);
ADC_Cmd(ADC1,ENABLE);
ADC_ResetCalibration(ADC1); //重置指定的ADC的校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); //等待上一步操作完成
ADC_StartCalibration(ADC1); //开始制定ADC的校准状态
while(ADC_GetCalibrationStatus(ADC1)); //等待上一步操作完成
}
//获取ADC通道的数据
u16 Get_ADC(u8 ch)
{
ADC_RegularChannelConfig(ADC1,ch,1,ADC_SampleTime_239Cycles5); //开始采集 采集哪个口
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));
return ADC_GetConversionValue(ADC1);
}
//软件滤波
#define N 12
u16 Get_ADC_Average(u8 ch)
{
u32 temp_value=0;
u8 t;
for(t=0;t<10;t++) //滤波10次,可以修改
{
temp_value+=Get_ADC(ch);
}
return temp_value/10; //滤波10次,可以修改
}
我使用四路电感电磁巡线为例,代码如下。
int adc1=0,adc2=0,adc3=0,adc4=0; //adc赋值0
int ad_VAL1=0,ad_VAL2=0,ad_VAL3=0,ad_VAL4=0; //归一化前adc的值
double Kp1=2.15; //竖电感Kp
double Kp2=2.5; //横电感Kp
int ErrorA,ErrorB; //竖电感,横电感
int Error1,Error2; //竖电感之差,橫电感之差
int AD_val_1_min=0;
int AD_val_1_max=3800;
int flag=1;
extern int c;
extern int red_flage;
extern int blue_flage;
void xunji()
{
adc1=Get_ADC_Average(ADC_Channel_0); //右竖电感 adc1
adc2=Get_ADC_Average(ADC_Channel_1); //右横电感 adc2
adc3=Get_ADC_Average(ADC_Channel_2); //左横电感 adc3
adc4=Get_ADC_Average(ADC_Channel_3); //左竖电感 adc4
if(adc1 >AD_val_1_max) //限adc1最大值
{
adc1 =AD_val_1_max;
}
if(adc2 >AD_val_1_max) //限adc2最大值
{
adc2 =AD_val_1_max;
}
if(adc3 >AD_val_1_max) //限adc3最大值
{
adc3 =AD_val_1_max;
}
if(adc4 >AD_val_1_max) //限adc4最大值
{
adc4 =AD_val_1_max;
}
Error1 = adc1-adc4; //竖电感值之差
Error2 = adc2-adc3; //横电感值之差
ErrorA = Error1*Kp1; //竖电感*Kp1
ErrorB = Error2*Kp2; //横电感*Kp2
left_front (750+ErrorA+ErrorB); //左前轮
front_right(700-ErrorA-ErrorB); //右前轮
beh_left (750+ErrorA+ErrorB); //左后轮
beh_right (700-ErrorA-ErrorB); //右后轮
//归一化算法
ad_VAL1=100*(adc1 - AD_val_1_min)/(AD_val_1_max-AD_val_1_min); //归一化后adc1的值
ad_VAL2=100*(adc2 - AD_val_1_min)/(AD_val_1_max-AD_val_1_min); //归一化后adc2的值
ad_VAL3=100*(adc3 - AD_val_1_min)/(AD_val_1_max-AD_val_1_min); //归一化后adc3的值
ad_VAL4=100*(adc4 - AD_val_1_min)/(AD_val_1_max-AD_val_1_min); //归一化后adc4的值
if(adc2>2700 &&ad_VAL2>71 && flag==1&&red_flage==1) //右进环岛1 右转 左轮加速
{
left_front (900);
front_right (0);
beh_left (900);
beh_right (0);
delay_ms(1500);
flag=2;
}
if(adc3>2500 &&ad_VAL3>65 && flag==1&&blue_flage==1) //左进环岛1 左转 右轮加速
{
left_front (-100);
front_right (700);
beh_left (-100);
beh_right (700);
delay_ms(1500);
delay_ms(1100);
flag=2;
}
if(flag==2&&adc2>2700 &&ad_VAL2>71&&c==1) //出环岛条件
{flag=3;}
if(flag==2&&adc3>2500 &&ad_VAL2>65&&c==1) //出环岛条件
{flag=3;}
}
陀螺仪在我们比赛的难度不是很大,当时还没有搞清楚难点的时候就是用陀螺仪加延时函数就调好了,后来学长看了,教了我们一下,我们主要是用俯仰角和偏航角,横滚角应该是没变化的,所以需要定义这两个角度的误差值和PID中的Kp,然后再加几个if条件和标志位,就可以完成自动车任务了。自动车的功能大概就这么多,其实也很简单。