电磁寻迹智能车HAL库基于cubeMX—三轮(分段PID+归一化+差速+均值滤波+多路ADC

128 阅读8分钟

在这里插入图片描述

这些也不用多说,注意好预留的接口,没有用到的IO口引出来,干黄管的位置放在车底部为了触发灵敏,考虑好布局排布就欧克了。还有的话我这里是多加了陀螺仪MPU6050,是为了进环岛时的姿态判定,但其实后面时间不够来不及调了。

在这里插入图片描述

电磁杆:

在这里插入图片描述

画的六路,大致是个这样的摆放位置

在这里插入图片描述

其实吧,电磁杆是我们没有考虑好的,不应该这么随便,虽然这样的位置摆放的电磁杆也可以完赛,但很多特殊元素都很难判定,其实电磁最关键的就是利用电磁杆去感知赛道,你处在一个什么样的位置,是否有遇到特殊元素,比如环岛,三叉。所以电磁杆的制作就是最考验每个团队的观察分析能力了,其实我们第一次参加这个比赛的时候完全没有意识到电磁杆的重要性这么强,知道电磁杆很重要,但没有意识到它才是整个比赛的关键,有句话说得好,一个系统里面,硬件结构的搭建决定了整个系统的功能上限,因为软件调试永远离不开这些硬件,软件是驱动硬件的,硬件搞不好软件是没有办法的。

那么呢关于电磁杆我就点到为止,至于电感应该在电磁杆怎么摆放,什么方案最佳,就个人下去探索了,毕竟只有自己思考过的才是最适合自己的。

这里提供一下举个例子,比如我们之前是那种只有水平和竖直位置的摆放,当我们遇到了三叉应该怎么办呢。我们发现,如果中间再加一个水平电感,就会有只有在经过三叉时才有的效果了。因为这个水平电感在正常赛道时会一个切割沿线的电磁线,所以一直有一个电压产生,而当遇到三叉时,中间沿线的电磁线瞬间消失,而这就是三叉的特征,当具体还需要很多细节的处理,以保证判定准确。环岛也可以参照这种方法进行处理了

在这里插入图片描述

在这里插入图片描述

可以看到我们上面的电磁杆也是临时改的,把其中一个水平电感放到了中间去。

在这里插入图片描述

最后的成品图:

在这里插入图片描述

四、软件部分

软件部分核心就是采集电感值,然后将电感值稍微进行一下滤波处理,其实硬件做的电磁感效果比较好的话也不需要怎么滤波。滤波后将实际值通过判断导入分段PID进行目标值的比较,我们将处理后的结果输出给电机执行,这就是整体的软件流程了,其实整体很简单。

另外的话在执行中再加上三叉,环岛等特殊元素的判定就欧克了。

cubeMX部分:

基础的配置,时钟等等就不再多说了,这里说一下 ADC多路配置吧
如图:

在这里插入图片描述

使能DMA模式

在这里插入图片描述
(1)Data Alignment—>Right alignment 此项选择右对齐,保持不变。

(2)Scan Conversion Mode—>Enable 此项选择扫描模式使能,代表对6路ADC输入分别扫描,如果不使能,其将会只读取一个输入的值。

(3)Continuous Conversion Mode —>Enable 此项选择连续扫描模式,表示将连续不断的对ADC的值进行转换。如果此项不使能,将会只采集一次就会停止,直到下一次使能才继续进行一次ADC转换。

(4)Discontinuous Conversion Mode—>Disable 此项和第三项是重复的。

(5)Number of Conversion---->6 此处有多少路输入就选择多少,而且只有在此处选择数字之后下面才会出来6个不同的通道。而且此处应该是在进入ADC1中第一个需要操作的步骤,否则(2)(3)是灰色的,无法选择使能。

使能一个定时器中断,用于一些功能的周期控制和按键的定时扫描

在这里插入图片描述

cubemx其他配置

使能了一些如驱动蜂鸣器的普通GPIO口,用的软件IIC,使能了硬件IIC用于驱动陀螺仪在这里插入图片描述

代码部分

把用户代码单独封装出来

在这里插入图片描述

代码的初始化,其实这里mpu6050没有用到,初始化与否无所谓
while(w\_mpu\_init()!=mpu_ok)        
{
	printf("ERROR\r\n");
	HAL\_Delay(5);
}
dmp\_init();    
OLED\_Init();
OLED\_Clear();
data();
ADC\_DMA\_Iint();
HAL\_TIM\_Base\_Start\_IT(&htim2);
MOTO\_init();

//HAL\_Delay(100000);
PID\_Init();
get\_into('R');

主循环里面的代码执行
 /\* USER CODE BEGIN WHILE \*/
  while (1)
  {
    /\* USER CODE END WHILE \*/

    /\* USER CODE BEGIN 3 \*/
		//读取mpu姿态
		Yaw\_angle();
		//采集AD值
		Ad\_Value();
		Direction\_track();
		//屏幕数据
		Real\_time\_data();
		if(last==1)
		{
			get\_into('L');
		}
		//按键扫描
		scan\_key();
		//PID处理
		
  }

使用干簧管判定停车位这里:用的是标志位,用外部中断进行触发
//判断停车
	if(stop_sta==0 && stop_falg0==0)
	{
		HAL\_GPIO\_WritePin(Bee_GPIO_Port,Bee_Pin,GPIO_PIN_SET);
		stop_falg0=1;
		stop_falg1=1;
	}
	if(stop_sta==1 && stop_falg1==1)
	{
		stop_falg2=1;
		HAL\_GPIO\_WritePin(Bee_GPIO_Port,Bee_Pin,GPIO_PIN_RESET);
	}
	if(stop_falg2==1 && stop_sta==0)
	{
		last=1;
	}

判断环岛,这里是用到标志位加定时器中断计数,但由于整车只调了一个通宵,这里最后是没有调出来的,其实主要也和电磁杆的位置摆放和特殊点的分析不足有关。

		//判断环岛
		if(PID_direction.time==1)
		{
			PID_direction.count++;
			if(PID_direction.count==800)
			{
				HAL\_GPIO\_WritePin(Bee_GPIO_Port,Bee_Pin,GPIO_PIN_RESET);
				PID_direction.time=0;
				PID_direction.count=0;
				PID_direction.target_speed=70;
			}
		}
		if(Left.firet>=800 && Left.second>=800 && Left.third>=800 && Right.firet <=100)			     
		{
			PID_direction.time=1;
			PID_direction.target_speed=30;
	    }
	   if(PID_direction.time==1)
	   {
			//HAL\_GPIO\_WritePin(Bee\_GPIO\_Port,Bee\_Pin,GPIO\_PIN\_SET);
			PID_direction.flag=2;
	   }	
	   if(PID_direction.three_forks==1)
	   {
		if(PID_direction.into_three==1)//已经进入过一次了
		{
			//判定是环岛
			PID_direction.cir=1;
			
			TIM1->CCR1=30;  
			TIM1->CCR2=60;                                                     			
// TIM1->CCR1=20; //调试
// TIM1->CCR2=35;
		}
		else
		{
			TIM1->CCR1=45;                                                     //调试
			TIM1->CCR2=10;
		}
		PID_direction.flag=3;
		HAL\_GPIO\_WritePin(Bee_GPIO_Port,Bee_Pin,GPIO_PIN_SET);
	}

判断三叉路,也是通过标志位和定时器的计数实现的,具体的思路前面也已经说过了,这里通过定时器计数判断主要是为了稳定过三叉,并且不误触发。
//判断三岔
		if(PID_direction.three_forks==1)
		{
			PID_direction.count++;
			if(PID_direction.count==20)          //调试
			{
				PID_direction.Kp = 0.022;//0.015
				PID_direction.count=0;
				PID_direction.three_forks=0;
				PID_direction.target_speed=40;
				PID_direction.run_time_three=1;
				HAL\_GPIO\_WritePin(Bee_GPIO_Port,Bee_Pin,GPIO_PIN_RESET);
			}
			if(PID_direction.cir==1)
			{
				PID_direction.Kp = 0.023;
				PID_direction.run_time_three=0;
				PID_direction.target_speed=40;
				PID_direction.timer++;
				if(PID_direction.timer==250)
				{
					PID_direction.Kp = 0.015;
					PID_direction.target_speed=70;
					PID_direction.cir=0;
					PID_direction.timer=0;
				}
			}
		}
		if(PID_direction.run_time_three==1)
		{
			//低速出三岔
			PID_direction.timer++;
			if(PID_direction.timer==580)
			{
				PID_direction.Kp = 0.015;
				PID_direction.into_three++;
				PID_direction.target_speed=70;
				PID_direction.timer=0;
				PID_direction.run_time_three=0;
				PID_direction.three_on=0;//允许进入了
			}
		}

以上代码的执行均放在了10ms一次的定时器中断回调函数内部

#include "ISR\_CallBack.h"

KEY key[4]={0,0,0};
uchar stop_sta,stop_falg0=0,stop_falg1,stop_falg2;
void HAL\_TIM\_PeriodElapsedCallback(TIM_HandleTypeDef \*htim)
{
	if(htim->Instance == TIM2)
	{
	     	//用户代码
	     	/\*
 ......
 ......
 ......
 \*/
	}
}

PID控制:

这里需要提到的是,某组织方提高的三轮车车模质量不敢恭维,没有编码器,要加编码器也要大改一下车身很麻烦,况且作为一个校赛,自己并没有花太多精力,于是直接开环跑了,校赛足矣。

电机的初始化

void MOTO\_init(void)
{
	HAL\_TIM\_PWM\_Start(&htim1,TIM_CHANNEL_1);
	HAL\_TIM\_PWM\_Start(&htim1,TIM_CHANNEL_2);
	HAL\_GPIO\_WritePin(STBY_GPIO_Port,STBY_Pin,GPIO_PIN_SET);
	
	HAL\_GPIO\_WritePin(Moto1_GPIO_Port,Moto1_Pin,GPIO_PIN_SET);
	HAL\_GPIO\_WritePin(Moto2_GPIO_Port,Moto2_Pin,GPIO_PIN_RESET);
	HAL\_GPIO\_WritePin(Moto3_GPIO_Port,Moto3_Pin,GPIO_PIN_SET);
	HAL\_GPIO\_WritePin(Moto4_GPIO_Port,Moto4_Pin,GPIO_PIN_RESET);
	TIM1->CCR1=0;
	TIM1->CCR2=0;
	HAL\_GPIO\_WritePin(Bee_GPIO_Port,Bee_Pin,GPIO_PIN_RESET);
}

用的位置式PID足够了,进行了一个简单的判断分段

//水平电感
	if(PID_direction.flag==1){
	PID_direction.Pwm=PID_direction.Kp\*PID_direction.err+PID_direction.Ki\*PID_direction.integral+ \
	PID_direction.Kd\*(PID_direction.err-PID_direction.err_last);
	}
	
	//竖直电感
	if(PID_direction.flag==2){
	PID_direction.Pwm2=PID_direction.Kp2\*PID_direction.err2+PID_direction.Ki2\*PID_direction.integral2+ \
	PID_direction.Kd2\*(PID_direction.err2-PID_direction.err_last2);
	}
	
	PID_direction.err_last=PID_direction.err;
	PID_direction.err_last2=PID_direction.err2;

PID的初始化

void PID\_Init(void){
	
	PID_direction.Kp = 0.015;//0.015
	PID_direction.Ki = 0;
	PID_direction.Kd = 0.075;//
	
	PID_direction.integral=0;
	PID_direction.time=0;
	PID_direction.cir=0;
	PID_direction.target_speed=70;
	PID_direction.three_forks=0;
	PID_direction.run_time_three=0;
	PID_direction.three_on=0;
	PID_direction.into_three=0;
}

限幅和取绝对值函数

uint16\_t Limit(uint16\_t pwm){		
	if (pwm <= 50){
		return 50;
	}
	else if(pwm >= 950){
		return 950;
	}
	else {
		return pwm;
	}
}		

float Abs(float a){


**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**
![img](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/3cd561624b26410bad93c452d64968e5~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5py65Zmo5a2m5Lmg5LmL5b-DQUk=:q75.awebp?rk3s=f64ab15b&x-expires=1771863589&x-signature=ahQN%2F%2B%2BtmyBhC03byoXResQIiAg%3D)
![img](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/65534e359eef46b1918d2983bbf64f24~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5py65Zmo5a2m5Lmg5LmL5b-DQUk=:q75.awebp?rk3s=f64ab15b&x-expires=1771863589&x-signature=67AAzuS8KQwuvZqh%2Fgs9unJDbBs%3D)

**[如果你需要这些资料,可以戳这里获取](https://gitee.com/vip204888)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**

**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**