作为一名大一的学生,CUIT智能车竞赛是我参加的第一个有关于电子方面的比赛,虽然最终我们的智能车没有成绩,但还是有所收获,并因此写了这个总结。
做一辆能够自动寻迹的智能车,需要
①车模
②两个电机
电机的驱动方法:用l298n模块,用单片机输出两路电压,一路输出高电平一路输出低电平,并输出一路PWM波,来驱动一个电机,改变PWM占空比可以改变电机转动的速度。
启动电机的代码:
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,DJ);
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_2,DJ);
(DJ为设置的占空比)
③l298n模块(用于驱动电机)
④一个舵机(用于智能车转弯),舵机有三根线:VCC(红线),GND(棕线),信号线(黄线)。使用电压3-7.2V,但一般接5V以上才能驱动。PWM占空比为2.5%时为0°,每增加2.5%,增加45°。
⑤干簧管(用于停车)
⑥蓝牙模块(用于调试,)
⑦需要硬件员画电磁杆(用于检测电压变化)和底板(用于升压和放置核心板,减少杜邦线的使用)
⑧需要一块STM32核心板(我用的是STM32F103C6T6)。
做智能车需要三人组队,在队伍中我是一名软件员,任务就是写代码,让我们的智能车能够成功寻迹。
1.ADC采集
因为我们的电磁杆的问题,我们的八字电感没有办法使用,所以只能使用竖直和水平电感(各需要两路ADC),因为还要干簧管检测停车,所以又加了一路ADC采集,在某一次调试过程中,有小伙伴告诉我加一路中间电感可以很快地判断三岔路和环岛,因此我的硬件员又加了一路中间电感,我一共开了6路ADC,相关配置如下图。
(添加ADC的时候我经常会忘记改“Number”,所以一定要记住添加ADC的时候要改相关的配置!)
uint16_t adc_buff[6];
HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_buff,6);
一篇较好的对ADC介绍和使用的文章:http://t.csdn.cn/HqMP3
2.滤波
我用的是平均数滤波。
void Average_Value(void)//滤波
{
int i,sum0=0,sum1=0,sum2=0,sum3=0,sum4=0,sum5=0;
for(i=0;i<10;i++)
{
adc0[i]=adc_buff[0];
sum0=sum0+adc0[i];
}
x0=sum0/10;
for(i=0;i<10;i++)
{
adc1[i]=adc_buff[1];
sum1=sum1+adc1[i];
}
x1=sum1/10;
for(i=0;i<10;i++)
{
adc2[i]=adc_buff[2];
sum2=sum2+adc2[i];
}
x2=sum2/10;
for(i=0;i<10;i++)
{
adc3[i]=adc_buff[3];
sum3=sum3+adc3[i];
}
x3=sum3/10;
for(i=0;i<10;i++)
{
adc4[i]=adc_buff[4];
sum4=sum4+adc4[i];
}
x4=sum4/10;
for(i=0;i<10;i++)
{
adc5[i]=adc_buff[5];
sum5=sum5+adc5[i];
}
x5=sum5/10;
}
3.归一化
定义:把需要处理的数据经过处理后,限制在你所需要的一定范围内。首先归一化是为了后面处理数据的方便,其次是保证程序运行时收敛加快。
归一化的具体作用是归纳统一样本的统计分布性,使用归一化可以提高智能车对各个不同赛道的兼容性。
归一化的公式:x=(x-Min)/(Max-Min)
我的相关代码:
x1=(x1-Min)/(Max-Min);//归一化
x2=(x2-Min)/(Max-Min);
x3=(x3-Min)/(Max-Min);
x4=(x4-Min)/(Max-Min);
4.差比和
差比和的公式:ARR=(R-L)/(R+L)
由于采集电感值时,时刻保持小车位于赛道正中,故还需要研究小车位于内环与外环的差异。由于小车左、右水平电感(L,R)受到小车左、右位置影响不大,而受小车前、后位置影响较大,采取固定L、R值的方式来确认小车的位置,并研究输出误差err(算出的ARR即误差)。算出的误差再经过PID作用来控制舵机转动,使小车始终保持在中心线上。
我的代码:
float Level_Output(void)//水平差比和
{
L= (x1-x2) / (x1+x2);
L=L*500;
return L;
}
float Erect_Output(void)//竖直差比和
{
R= (x3-x4) / (x3+x4);
R=R*500;
return R;
}
差比和算出来的值可适当放大,具体放大多少由自己决定,得到的值可直接给PID使用,从而直接控制舵机的角度变化。
注:x1,x2,x3,x4定义时应定义为float。
5.PID
P:比例项 输入偏差乘以一个常数
I: 积分项 对输入偏差进行积分运算
D:微分项 对输入偏差进行微分运算
将比例、积分、微分三种调节规律结合在一起, 只要三项作用的强度配合适当,既能快速调节,又能消除余差,可得到满意的控制效果。
一篇学长推荐的对PID较详细的解释:PID应用详解 - -零 - 博客园
我在做智能车的过程中,仅使用了PD,下面是我的代码
static float PID(float error)
{
float Return;
Return = P*error + D*(error-Last_error);
Last_error=error;
return Return;
}
float PID_Init(float error)
{
float Return;
P=0.7;
D=0.55;
Return=PID(error);
if(Return< -200)
{
return -200;
}
if(Return > 200)
{
return 200;
}
return Return;
}
调节P时的响应作用比较大,调节D时的响应作用比较小,在具体调节过程中可根据实际情况来调节P和D的数值。
PID的error即为差比和函数的返回值,PID的返回值给舵机来控制舵机的转动。
6.出库
智能车比赛规则需要智能车自己出库和入库
关于入库,我就设置了一个标志位,刚开始让舵机转动一个角度,延时一段时间后转回来,然后改变标志位的值,成功完成了出库。我的代码:
int b=1;
if(b==1)//出库
{
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_4,800);//¶æ»ú
HAL_Delay(1500);
b=0;
}
7.入库
入库需要使用干簧管检测停车,我使用的是外部中断,干簧管遇到磁铁会检测到有下降沿产生,进入中断,标志位加一,因为要跑两圈,所以待标志位加到2时再强制打角使小车入库并关闭电机。
CubeMx相关配置
中断:
(放在it.c中)
int stop=0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin==GPIO_PIN_1)
{
stop++;
}
}
入库:
(放在while循环里)
if(stop==2)//入库
{
DJ=0;
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_4,800);
HAL_Delay(1000);
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,DJ);
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_2,DJ);
}
8.三岔路和环岛
加了一路中间电感之后,我本来想试一下能不能进三岔路和环岛,但出库入库是临时通知的,没有出库和入库就直接没有成绩,无奈之下最后一天我只能先去搞定出库和入库,没有去使用那一路中间电感来检测三岔路和环岛。
思路:
环岛是利用检测到的电压的变化特点来进入和出来环岛。
三岔路是检测导归一化后的值突然变小,控制舵机转动一个角度进入一边三茬路,第二圈检测到时向另一边打角。
9.蓝牙
我调试的时候OLED不能显示,串口助手也不能打印,因此只能用蓝牙来调试,我用的是手机上的蓝牙调试器,非常方便。
打印函数:
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xffff);
return ch;
}
10.比赛
比赛时,4分钟调试时间的时候,电机无故不转动,调试时间过去以后电机才开始转动,我们就让它跑,跑的时候,智能车很抖,第一圈便冲出赛道三次,我想下较为稳妥的代码时,比赛时突然发现自己的代码编译有错误,导致我们无法下载代码,最后用的就是车里原有的代码,时间到了没有完成两圈的寻迹。
赛后我回来检查我的代码,发现是因为我在it.c里定义的用于检测停车的标志位用了static int,这时候这个变量将不能在另外的.c文件里使用,因而程序会报错。
此次智能车竞赛告诉我,平时应多学一些知识,在比赛前不应该再去改动代码,就算改动了也应该提前编译,在赛前提前把需要用的代码打开,下载的时候能够立马下载,组内成员应该积极配合,多交流,软件员和硬件员之间也应该不断的交流,只有团队共同努力才能把比赛做好。