DIY智能小车篇(二):功能模块之驱动、巡线、避障

237
0
2020年12月20日 09时24分

前言

本篇主要针对实现小车各功能的过程中具体的实现原理、程序算法以及遇到的问题进行统一的解释与说明。至此,结合上一篇的结构模块,已经完成了该智能小车项目的所有工作,其中必然还有很多可以优化的地方,但考虑到做此项目的最终目的是更深入的理解嵌入式编程,所以此项目到此为止,要去开拓更广阔的的天地啦。

驱动控制

驱动模块PWM配置

要想实现对小车的速度控制,就需要对L298N电机驱动模块的使能引脚输入PWM波,改变占空比的大小便可以调节小车速度的快慢

该PWM配置如下:

 

/**
 * @brief: 初始化PWM输出配置,利用TIM3同时输出4路PWM波,控制4个电机转速
 * @param: arr-自动重装载值		psc-时钟预分频系数
 * @retval	NONE
 * @Others:GPIO_PinAFConfig函数必须分步进行复用,不能用一个复用函数并在一起,不然只有一路输出
**/
void TIM3_PWM_Init(u16 arr,u16 psc)
{		 					 
	GPIO_InitTypeDef GPIO_InitStructure;

	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);  	//TIM3时钟使能    
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); 	//使能GPIOA时钟	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); 	//使能GPIOB时钟	
	
	//GPIO_PinAFConfig函数必须分步进行复用,不能用一个复用函数并在一起,不然只有一路输出
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_TIM3); //GPIOA6复用为定时器3
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_TIM3); //GPIOA7复用为定时器3
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource0,GPIO_AF_TIM3); //GPIOB0复用为定时器3
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource1,GPIO_AF_TIM3); //GPIOB1复用为定时器3
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;       
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;        //复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;	//速度100MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;      //推挽复用输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;        //上拉
	GPIO_Init(GPIOA,&GPIO_InitStructure);               //初始化PF9

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;         
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;        //复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;	//速度100MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;      //推挽复用输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;        //上拉
	GPIO_Init(GPIOB,&GPIO_InitStructure);  	  

	TIM_TimeBaseStructure.TIM_Prescaler=psc;  //定时器分频
	TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
	TIM_TimeBaseStructure.TIM_Period=arr;   //自动重装载值
	TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
	
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);//初始化定时器3
	
	//初始化TIM3 PWM模式	 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低

	TIM_OC1Init(TIM3, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM3OC1
	TIM_OC2Init(TIM3, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM3OC2
	TIM_OC3Init(TIM3, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM3OC3
	TIM_OC4Init(TIM3, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM3OC4

	TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3在CCR1上的预装载寄存器
	TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3在CCR1上的预装载寄存器
	TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3在CCR1上的预装载寄存器
	TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3在CCR1上的预装载寄存器
	
	TIM_ARRPreloadConfig(TIM3,ENABLE);
 
	TIM_Cmd(TIM3, ENABLE);  //使能TIM3
										  
}  

 

驱动实现

现在相当于已经完成了驱动模块的使能,要想使小车运动,需要对IN1~IN4引脚的电平进行操作
step1:IO口初始化

 

/**
 * @brief: 初始化控制电机正反转及刹车的IO口
 * @param: NONE
 * @retval:	NONE
 * @Others: 选择IO时注意不要和STM32的硬件接口冲突,比如我一开始选择的IO占用了
						LCD屏的一个接口,导致LCD初始化不正常
**/
void CAR_Init(void)
{    	 
  GPIO_InitTypeDef  GPIO_InitStructure;

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//使能GPIOF时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);//使能GPIOG时钟
  //IO初始化设置
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_3 | GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_15 | GPIO_Pin_13 | GPIO_Pin_14;//LED0和LED1对应IO口
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化GPIO
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化GPIO
	
	GPIO_SetBits(GPIOF,GPIO_Pin_1 | GPIO_Pin_3 | GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_15 | GPIO_Pin_13 | GPIO_Pin_14);
	GPIO_SetBits(GPIOG,GPIO_Pin_1);
}

 

step2:驱动功能函数:包括前进、后退、转向、停车

 

/**
 * @brief: 档位速度转换函数,设置1-4档,将档位线性的转换为相应的PWM波输出
 * @param: 档位值
 * @retval:pulse
 * @Others: NONE
**/
u16 gear_trans(u16 gear)
{
	u16 Pulse=0;
	switch(gear)
	{
		case 0x00:
			Pulse=500;
			break;
		case 0x01:
			Pulse=350;
			break;
		case 0x02:
			Pulse=300;
			break;
		case 0x03:
			Pulse=200;
			break;
		case 0x04:
			Pulse=100;
			break;
		default:
			break;
	}
	return Pulse;
}

/**
 * @brief: 前进函数,控制小车前进
 * @param: 档位值
 * @retval:NONE
 * @Others: NONE
**/
void drive(u16 gear)
{
		u16 Pulse;
		Pulse=gear_trans(gear);
	
		GPIO_ResetBits(GPIOF,GPIO_Pin_1); 
		GPIO_SetBits(GPIOF,GPIO_Pin_3);   
	
		GPIO_SetBits(GPIOF,GPIO_Pin_5);	  
		GPIO_ResetBits(GPIOF,GPIO_Pin_7); 
	
		GPIO_SetBits(GPIOF,GPIO_Pin_15);	   
		GPIO_ResetBits(GPIOG,GPIO_Pin_1); 
	
		GPIO_SetBits(GPIOF,GPIO_Pin_14);	  
		GPIO_ResetBits(GPIOF,GPIO_Pin_13); 
	
		TIM_SetCompare1(TIM3,Pulse);	//修改比较值,修改占空比
		TIM_SetCompare2(TIM3,Pulse);	//修改比较值,修改占空比
		TIM_SetCompare3(TIM3,Pulse);	//修改比较值,修改占空比
		TIM_SetCompare4(TIM3,Pulse);	//修改比较值,修改占空比

}
/**
 * @brief: 后退函数
 * @param: 档位值
 * @retval:NONE
 * @Others: NONE
**/
void reverse(u16 gear)	
{
		u16 Pulse;
		Pulse=gear_trans(gear);
	
		GPIO_SetBits(GPIOF,GPIO_Pin_1); 
		GPIO_ResetBits(GPIOF,GPIO_Pin_3); 
	
		GPIO_ResetBits(GPIOF,GPIO_Pin_5);	  
		GPIO_SetBits(GPIOF,GPIO_Pin_7); 
	
		GPIO_ResetBits(GPIOF,GPIO_Pin_15);	 
		GPIO_SetBits(GPIOG,GPIO_Pin_1); 
	
		GPIO_ResetBits(GPIOF,GPIO_Pin_14);	
		GPIO_SetBits(GPIOF,GPIO_Pin_13); 
	
		TIM_SetCompare1(TIM3,Pulse);	//修改比较值,修改占空比
		TIM_SetCompare2(TIM3,Pulse);	//修改比较值,修改占空比
		TIM_SetCompare3(TIM3,Pulse);	//修改比较值,修改占空比
		TIM_SetCompare4(TIM3,Pulse);	//修改比较值,修改占空比

}
/**
 * @brief: 停车函数
 * @param: NONE
 * @retval:NONE
 * @Others: NONE
**/
void stop(void)
{
		GPIO_ResetBits(GPIOF,GPIO_Pin_1); 
		GPIO_ResetBits(GPIOF,GPIO_Pin_3);   
	
		GPIO_ResetBits(GPIOF,GPIO_Pin_5);	 
		GPIO_ResetBits(GPIOF,GPIO_Pin_7); 
	
		GPIO_ResetBits(GPIOF,GPIO_Pin_15);	  
		GPIO_ResetBits(GPIOG,GPIO_Pin_1); 
	
		GPIO_ResetBits(GPIOF,GPIO_Pin_14);	 
		GPIO_ResetBits(GPIOF,GPIO_Pin_13); 
}
/**
 * @brief: 左转函数,利用一侧车轮抱死转弯
 * @param: 转弯档位
 * @retval:NONE
 * @Others: NONE
**/
void left_move(u16 gear_change)
{
		u16 Pulse;
		Pulse=gear_trans(gear_change);
	
		GPIO_ResetBits(GPIOF,GPIO_Pin_1); 
		GPIO_SetBits(GPIOF,GPIO_Pin_3);   
	
		GPIO_SetBits(GPIOF,GPIO_Pin_5);	  
		GPIO_ResetBits(GPIOF,GPIO_Pin_7); 
	
		GPIO_SetBits(GPIOF,GPIO_Pin_15);	   
		GPIO_ResetBits(GPIOG,GPIO_Pin_1);
	
		GPIO_SetBits(GPIOF,GPIO_Pin_14);	 
		GPIO_ResetBits(GPIOF,GPIO_Pin_13); 
	
		TIM_SetCompare1(TIM3,500);	//修改比较值,左侧车轮抱死
		TIM_SetCompare2(TIM3,500);	//修改比较值,左侧车轮抱死
		TIM_SetCompare3(TIM3,Pulse);	//修改比较值,修改占空比
		TIM_SetCompare4(TIM3,Pulse);	//修改比较值,修改占空比
}
/**
 * @brief: 右转函数,利用一侧车轮抱死转弯
 * @param: 转弯档位
 * @retval:NONE
 * @Others: NONE
**/
void right_move(u16 gear_change)
{
		u16 Pulse;
		Pulse=gear_trans(gear_change);
	
		GPIO_ResetBits(GPIOF,GPIO_Pin_1); 
		GPIO_SetBits(GPIOF,GPIO_Pin_3);  
	
		GPIO_SetBits(GPIOF,GPIO_Pin_5);	  
		GPIO_ResetBits(GPIOF,GPIO_Pin_7); 
	
		GPIO_SetBits(GPIOF,GPIO_Pin_15);	
		GPIO_ResetBits(GPIOG,GPIO_Pin_1);
	
		GPIO_SetBits(GPIOF,GPIO_Pin_14);	 
		GPIO_ResetBits(GPIOF,GPIO_Pin_13); 
	
		TIM_SetCompare1(TIM3,Pulse);	//修改比较值,修改占空比
		TIM_SetCompare2(TIM3,Pulse);	//修改比较值,修改占空比
		TIM_SetCompare3(TIM3,500);	//修改比较值,右侧车轮抱死
		TIM_SetCompare4(TIM3,500);	//修改比较值,右侧车轮抱死
}
/**
 * @brief: 左转函数,利用一侧前进一侧后退转弯
 * @param: 转弯档位
 * @retval:NONE
 * @Others: NONE
**/
void left_move_2(u16 gear_change)
{
		u16 Pulse;
		Pulse=gear_trans(gear_change);
	
		GPIO_SetBits(GPIOF,GPIO_Pin_1);  
		GPIO_ResetBits(GPIOF,GPIO_Pin_3);  
	
		GPIO_ResetBits(GPIOF,GPIO_Pin_5);	  
		GPIO_SetBits(GPIOF,GPIO_Pin_7); 
	
		GPIO_SetBits(GPIOF,GPIO_Pin_15);	  
		GPIO_ResetBits(GPIOG,GPIO_Pin_1); 
	
		GPIO_SetBits(GPIOF,GPIO_Pin_14);	 
		GPIO_ResetBits(GPIOF,GPIO_Pin_13); 
	
		TIM_SetCompare1(TIM3,Pulse);	//修改比较值,修改占空比
		TIM_SetCompare2(TIM3,Pulse);	//修改比较值,修改占空比
		TIM_SetCompare3(TIM3,Pulse);	//修改比较值,修改占空比
		TIM_SetCompare4(TIM3,Pulse);	//修改比较值,修改占空比
}
/**
 * @brief: 右转函数,利用一侧前进一侧后退转弯
 * @param: 转弯档位
 * @retval:NONE
 * @Others: NONE
**/
void right_move_2(u16 gear_change)
{
		u16 Pulse;
		Pulse=gear_trans(gear_change);
	
		GPIO_ResetBits(GPIOF,GPIO_Pin_1);  
		GPIO_SetBits(GPIOF,GPIO_Pin_3);  
	
		GPIO_SetBits(GPIOF,GPIO_Pin_5);	  
		GPIO_ResetBits(GPIOF,GPIO_Pin_7); 
	
		GPIO_ResetBits(GPIOF,GPIO_Pin_15);	 
		GPIO_SetBits(GPIOG,GPIO_Pin_1); 
	
		GPIO_ResetBits(GPIOF,GPIO_Pin_14);	   
		GPIO_SetBits(GPIOF,GPIO_Pin_13); 
	
		TIM_SetCompare1(TIM3,Pulse);	//修改比较值,修改占空比
		TIM_SetCompare2(TIM3,Pulse);	//修改比较值,修改占空比
		TIM_SetCompare3(TIM3,Pulse);	//修改比较值,修改占空比
		TIM_SetCompare4(TIM3,Pulse);	//修改比较值,修改占空比
}
/**
 * @brief: 前进函数,主要用于遥控模式
 * @param: pulse值
 * @retval:NONE
 * @Others: NONE
**/
void drive_pulse(int pulse)
{	
		GPIO_ResetBits(GPIOF,GPIO_Pin_1); 
		GPIO_SetBits(GPIOF,GPIO_Pin_3);  
	
		GPIO_SetBits(GPIOF,GPIO_Pin_5);	   
		GPIO_ResetBits(GPIOF,GPIO_Pin_7);
	
		GPIO_SetBits(GPIOF,GPIO_Pin_15);	  
		GPIO_ResetBits(GPIOG,GPIO_Pin_1);
	
		GPIO_SetBits(GPIOF,GPIO_Pin_14);	  
		GPIO_ResetBits(GPIOF,GPIO_Pin_13);
	
		TIM_SetCompare1(TIM3,pulse);	//修改比较值,修改占空比
		TIM_SetCompare2(TIM3,pulse);	//修改比较值,修改占空比
		TIM_SetCompare3(TIM3,pulse);	//修改比较值,修改占空比
		TIM_SetCompare4(TIM3,pulse);	//修改比较值,修改占空比
}

/**
 * @brief: 后退函数,主要用于遥控模式
 * @param: pulse值
 * @retval:NONE
 * @Others: NONE
**/
void reverse_pulse(int pulse)
{
		GPIO_SetBits(GPIOF,GPIO_Pin_1); 
		GPIO_ResetBits(GPIOF,GPIO_Pin_3);  
	
		GPIO_ResetBits(GPIOF,GPIO_Pin_5);	  
		GPIO_SetBits(GPIOF,GPIO_Pin_7);
	
		GPIO_ResetBits(GPIOF,GPIO_Pin_15);	
		GPIO_SetBits(GPIOG,GPIO_Pin_1); 
	
		GPIO_ResetBits(GPIOF,GPIO_Pin_14);	 
		GPIO_SetBits(GPIOF,GPIO_Pin_13); 
	
		TIM_SetCompare1(TIM3,pulse);	//修改比较值,修改占空比
		TIM_SetCompare2(TIM3,pulse);	//修改比较值,修改占空比
		TIM_SetCompare3(TIM3,pulse);	//修改比较值,修改占空比
		TIM_SetCompare4(TIM3,pulse);	//修改比较值,修改占空比
}

/**
 * @brief: 转弯函数,主要用于遥控模式
 * @param: pulse1-左侧,pulse2-右侧
 * @retval:NONE
 * @Others: NONE
**/
void turn_pulse(int pulse1,int pulse2)
{
	 GPIO_ResetBits(GPIOF,GPIO_Pin_1); 
		GPIO_SetBits(GPIOF,GPIO_Pin_3);   
	
		GPIO_SetBits(GPIOF,GPIO_Pin_5);	   
		GPIO_ResetBits(GPIOF,GPIO_Pin_7);
	
		GPIO_SetBits(GPIOF,GPIO_Pin_15);	
		GPIO_ResetBits(GPIOG,GPIO_Pin_1); 
	
		GPIO_SetBits(GPIOF,GPIO_Pin_14);	  
		GPIO_ResetBits(GPIOF,GPIO_Pin_13); 
	
		TIM_SetCompare1(TIM3,pulse1);	//修改比较值,修改占空比
		TIM_SetCompare2(TIM3,pulse1);	//修改比较值,修改占空比
		TIM_SetCompare3(TIM3,pulse2);	//修改比较值,修改占空比
		TIM_SetCompare4(TIM3,pulse2);	//修改比较值,修改占空比
}

 

 

写到这里,你已经可以让你的小车动起来了,舒坦

巡线功能

这里我采用了4个巡线传感器,左右两侧各一个,中间部分布两个,采用的传感器个数不同、分布位置不同,那相应的实现巡线的算法也就不同,具体算法问题可以查阅相关资料

巡线传感器初始化

对需要接受巡线传感器信号的IO口进行配置

 

/**
 * @brief: 循迹传感器所接IO口初始化
 * @param: NONE
 * @retval:NONE
 * @others: 最初循迹模块上面开关指示灯常亮的原因是选错了IO口,用了PC0和PC2
					  换成PF2和PF4就好啦,why?
**/
void Trace_Init(void)
{
	GPIO_InitTypeDef   GPIO_InitStructure;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); 	//使能GPIOF时钟	
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_2 | GPIO_Pin_4 | GPIO_Pin_6; 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;        //复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;	//速度100MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;  //推挽复用输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;       //上拉
	GPIO_Init(GPIOF,&GPIO_InitStructure);           
	
}

 

信号接收与传递

对巡线传感器采集到的线路信息进行采集并记录

 

/**
 * @brief: 循迹模块扫描函数,参照STM32按键实验
 * @param: mode,==1表示支持连续按,==0表示不支持连续按
 * @retval: 4个循迹模块的编号,返回1表示1号传感器压线
 * @others: NONE
**/

u8 TRACE_Scan(u8 mode)
{	 
	static u8 trace_up=1;//按键按松开标志
	if(mode)trace_up=1;  //支持连按		  
	if(trace_up&&(TRACE1==1||TRACE2==1||TRACE3==1||TRACE4==1))
	{
		delay_ms(10);//去抖动 
		trace_up=0;
		if(TRACE1==1)return 1;
		else if(TRACE2==1)return 2;
		else if(TRACE3==1)return 3;
		else if(TRACE4==1)return 4;
	}
	else if(TRACE1==0&&TRACE2==0&&TRACE3==0&&TRACE4==0)trace_up=1;
		return 0;// 无按键按下
}

 

巡线实现

此处只进行了矩形巡线的编写,如果需要实现其他形状线路的巡线,可以查阅相关资料

 

/**
 * @brief: 循迹执行模块(矩形巡线),根据检测到的传感器信号做出相应动作
 * @param: NONE
 * @retval:NONE
 * @others: 由于此项目主要用来练习编程能力,so此处只编写了矩形巡线
						并没有对其他线型情况以及各种算法做深入探究
**/
void TRACE_Implement(void)
{
		extern u16 GEAR;
		u8 Edge_Flag=0;//矩形90度角检测,检测到后此标志位置1
		u8 trace_scan; 
		trace_scan=TRACE_Scan(0);//得到压线的传感器的编号		
	  if(trace_scan)
		{						   
			switch(trace_scan)
			{				 
				case TRACE1_SCAN:	//检测到最左侧传感器
					left_move_2(GEAR+2);//以较大速度左转
					Edge_Flag	=	1;//90度标志位置1
					delay_ms(150);//转向90度结束
					left_move(GEAR);//小左转
					break;
				case TRACE2_SCAN: //检测到最右侧传感器
					right_move_2(GEAR+2);
					Edge_Flag	=	1;
					delay_ms(150); 
					right_move(GEAR);
					LED0=!LED0;
					break;
				case TRACE3_SCAN:	//检测到中间左侧传感器
					if(Edge_Flag==1)//说明刚刚经过90度弯
					{
						right_move(GEAR);//中间左侧传感器压线时向右转
						Edge_Flag=0;
					}
					else
					{
						left_move(GEAR);//没有经过90度弯,中间左侧传感器压线时向左转
					}
					LED1=!LED1;
					break;
				case TRACE4_SCAN:	//检测到中间右侧传感器
					if(Edge_Flag==1)
					{
						left_move(GEAR);
						Edge_Flag=0;
					}
					else
					{
						right_move(GEAR);
					}
					LED0=!LED0;
					LED1=!LED1;
					break;
			}
		}else delay_ms(10); 
}

 

避障功能

如前所述,我们利用舵机和超声波传感器搭建了避障云台,现在需要对其进行初始化并完成测距和避障

舵机PWM配置

舵机的角度是由PWM波来控制的,给舵机输入不同占空比的PWM波,便可以使其到达不同的角度位置

 

/**
 * @brief: 配置驱动避障云台舵机的PWM输出
 * @param: arr-自动重装载值		psc-时钟预分频系数
 * @retval:NONE
 * @others: NONE
**/
void TIM4_PWM_Init(u16 arr,u16 psc)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;

	/* 开启时钟 */
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);

	GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_TIM4); //GPIOB6复用为定时器4
	
	/*  配置GPIO的模式和IO口 */
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;// PB6
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;     
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	//TIM4定时器初始化
	TIM_TimeBaseInitStructure.TIM_Period = arr; //PWM 频率=72000/(199+1)=36Khz//设置自动重装载寄存器周期的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = psc;//设置用来作为TIM4时钟频率预分频值
	TIM_TimeBaseInitStructure.TIM_ClockDivision = 0;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//TIM向上计数模式
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);

	//PWM初始化	根据TIM_OCInitStruct中指定的参数初始化外设TIM4
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//PWM输出使能
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;

	TIM_OC1Init(TIM4,&TIM_OCInitStructure);
	//注意此处初始化时TIM_OC1Init而不是TIM_OCInit,否则会出错。因为固件库的版本不一样
	TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);//使能TIM4在CCR1上的预装载寄存器
	
	TIM_Cmd(TIM4,ENABLE);//使能TIM4外设
}

 

超声波传感器初始化配置

 

/**
 * @brief: 超声波传感器初始化,
 * @param: NONE
 * @retval:NONE
 * @others: NONE
**/

void HCSR04_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;    
    EXTI_InitTypeDef EXTI_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseInitStructure;
		NVIC_InitTypeDef   NVIC_InitStructure;

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG , ENABLE);
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);//使能SYSCFG时钟
	
    GPIO_InitStructure.GPIO_Pin = TRIG_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_Init(SONAR_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = ECHO_PIN;                   
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; 
    GPIO_Init(SONAR_PORT, &GPIO_InitStructure); 
	
    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOG, GPIO_PinSource3);  // 中断线以及中断初始化配置

    EXTI_ClearITPendingBit(EXTI_Line3);
	
	  /* 外部中断初始化,用于接收检测信号 */
    EXTI_InitStructure.EXTI_Line = EXTI_Line3;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; 
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);     
		
		/* 外部中断优先级设置 */
		NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;//外部中断3
		NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
		NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中断通道
		NVIC_Init(&NVIC_InitStructure);

		RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
		
		/* TIM2中断初始化,用于计数从而计时 */
    TIM_TimeBaseInitStructure.TIM_Prescaler = 83;//分频系数83,频率为1MHz,理论测量精度0.34mm
		TIM_TimeBaseInitStructure.TIM_Period = 50000;//计数周期50000,相当于0.05s,最大测量范围17m
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
		
		TIM_ITConfig(  //使能或者失能指定的TIM中断
		TIM2, //TIM2
		TIM_IT_Update  |  //TIM 中断源
		TIM_IT_Trigger,   //TIM 触发中断源 
		ENABLE  //使能
		);
		
		TIM_Cmd(TIM2, DISABLE); 

    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
		
		/* TIM2中断优先级设置 */
		NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;  //TIM2中断
		NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  
		NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  
		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
		NVIC_Init(&NVIC_InitStructure);  

		RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
		
		/* TIM5中断初始化,用于启动测距函数 */
    TIM_TimeBaseInitStructure.TIM_Prescaler = 83;
    TIM_TimeBaseInitStructure.TIM_Period = 100;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM5, &TIM_TimeBaseInitStructure);
		
		TIM_ITConfig(  //使能或者失能指定的TIM中断
		TIM5, //TIM5
		TIM_IT_Update  |  //TIM 中断源
		TIM_IT_Trigger,   //TIM 触发中断源 
		ENABLE  //使能
		);
		
		TIM_Cmd(TIM5, ENABLE); 

    TIM_ClearFlag(TIM5, TIM_FLAG_Update);
		
		/* TIM5中断优先级设置 */
		NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;  //TIM5中断
		NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
		NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; 
		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
		NVIC_Init(&NVIC_InitStructure); 
}

 

测距功能

根据HC-SR05的使用说明,需要触发一个不小于10us的高电平,下面的函数实现这个功能

 

/*Distance measure--------------------------------------------------*/
/**
 * @brief: 启动测距功能函数,触发超声波传感器
 * @param: NONE
 * @retval:NONE
 * @others: 此函数内部的延时函数必须是可重入的,因为放在了TIM5的中断函数内部
						STM32例程里面的延时函数不可重入,so 此处自定义了可重入的延时函数
**/
void HCSR04_StartMeasure(void)
{
    GPIO_SetBits(SONAR_PORT, TRIG_PIN);
    delay_us_unpre(40);   //  The width of trig signal must be greater than 10us.
													//出现在定时器5的中断函数中,所以不能用系统延时
	                        //  自定义普通延时函数,已解决延时失效问题
    GPIO_ResetBits(SONAR_PORT, TRIG_PIN);
		//printf("Test start!");//printf函数也是不可重入函数,开启后导致中断失效
}

 

对计数器计数值进行处理得到测距距离

 

/**
 * @brief: 测距函数
 * @param: TIM2计数值
 * @retval:测距距离,units:cm
 * @others: NONE
**/
float HCSR04_GetDistance(u32 count)
{
		float distance;
    // distance = measurement/2/1000*340 = measurement/59 (cm)  measurement-units:us
    distance = (float)count / 58.8 ; 
    return distance;
}

 

避障的实现

此处涉及一定的算法,理解起来很简单的算法,即什么时候就算检测到障碍物,检测到障碍物之后要进行什么样的操作等等,当然可以用不同的方式实现避障,可以自行研究和优化

 

/**
 * @brief: 利用得到的测距距离进行相应操作完成避障任务
 * @param: NONE
 * @retval:NONE
 * @others: 最初的实时性得不到解决是因为该函数用到了大量延时,现已改为根据测距距离
						进行相应操作的实现方式,实时性问题得以解决

**/
void Obstacle_avoid(void)
{
	extern u16 GEAR;
	if(UltrasonicWave_Distance<30)
	{
		reverse(GEAR);
		while(UltrasonicWave_Distance<=40);
		stop();
		TIM_SetCompare1(TIM4, 181);//探测仪左转45度
		delay_ms(500);
		if(UltrasonicWave_Distance<30)
		{
				TIM_SetCompare1(TIM4, 191);//探测仪右转45度
				delay_ms(500);
				if(UltrasonicWave_Distance<30)
				{
						reverse(GEAR);
						while(UltrasonicWave_Distance<=40);
						right_move_2(GEAR+1);
						delay_ms(500);
						TIM_SetCompare1(TIM4, 186);//探测仪回零位
						drive(GEAR);
				}
				else
				{		
						right_move_2(GEAR+1);
						delay_ms(500);
						TIM_SetCompare1(TIM4, 186);//探测仪回零位
						drive(GEAR);
				}
		}
		else
		{
				left_move_2(GEAR+1);
				delay_ms(500);
				drive(GEAR);			
				TIM_SetCompare1(TIM4, 186);//探测仪回零位
				
				
		}
		
		
	}
}

 

 

 

发表评论

后才能评论