详细介绍如何从零开始制作51单片机控制的智能小车(三)———用超声波模块和漫反射光电传感器实现小车的自动避障

238
0
2020年11月27日 09时20分

   我会通过本系列文章,详细介绍如何从零开始用51单片机去实现智能小车的控制,在本系列的上一篇文章中介绍了3种可用来让小车实现避障的传感器,本文作为本系列的第三篇文章,主要介绍如何让车实现自动避障。

 

本系列文章:

—————————————————————————–

 

   详细介绍如何从零开始制作51单片机控制的智能小车(一)———让小车动起来
   详细介绍如何从零开始制作51单片机控制的智能小车(二)———超声波模块、漫反射光电管、4路红外传感器的介绍和使用
   详细介绍如何从零开始制作51单片机控制的智能小车(三)———用超声波模块和漫反射光电传感器实现小车的自动避障
   详细介绍如何从零开始制作51单片机控制的智能小车(四)———通过蓝牙模块实现数据传输以及通过手机蓝牙实现对小车运动状态的控制
   详细介绍如何从零开始制作51单片机控制的智能小车(五)———对本系列第四篇文章介绍的手机蓝牙遥控加减速异常的错误的介绍及纠正

 

—————————————————————————–

 

一、避障思路及传感器的选择

 

   1、利用4路红外寻迹避障传感器模块实现避障

 

   可以让一路传感器检测左边的障碍物,一路传感器检测右边的障碍物,两路传感器检测前方的障碍物,如下图所示:

 

微信图片_20201122171310

 

这种方法呢实现起来是最简单的,对于0基础的可以尝试一下,障碍物检测距离一般在20cm左右,在车速不太快,而且在室内(也就是无阳光直射)时是可行的,也可以很好的实现避障。

 

缺点呢或者说不足之处,也就很明确了,在室外(或者阳光直射时)传感器受阳光干扰可能不能正常工作,检测距离20cm左右,车速过快时,在检测到障碍物时,不能直接转弯(因为车速过快,还没来得及转弯可能就撞上了),需要往回倒一下,再转弯。

 

   2、利用4个漫反射光电传感器模块实现避障

 

   可以让一个传感器检测左边的障碍物,一个传感器检测右边的障碍物,两路传感器检测前方的障碍物。就像上面的4路红外寻迹避障传感器的摆放方向一样

 

这种方法呢,避障思路跟利用4路红外寻迹避障传感器模块实现避障是相同的,只是换了传感器类型而已,但是漫反射光电传感器抗阳光干扰能力强,克服了4路红外寻迹避障传感器模块在室外(或者阳光直射时)传感器受阳光干扰可能不能正常工作的缺点。

 

缺点呢或者说不足之处,检测距离也在20cm附近,依然没能克服车速过快时,在检测到障碍物时,不能直接转弯的不足,而且呢漫反射光电传感器模块体积、重量、价格都要高一些。

 

   3、利用超声波模块和舵机云台实现避障

 

   避障思路呢大体是这样的,正常情况下超声波模块检测前方障碍物,当检测到障碍物时,小车停止前行,舵机云台转动,让超声波模块分别转向小车的左右两侧检测左右两侧是否有障碍物,来决定下一步的转向,超声波模块重新超前,开始转向。

 

微信图片_20201122171329

 

这种方法呢,利用超声波模块测距远,一般常见的超声波模块测距最远在4.5米左右,完全不用担心车速过快来不及反应的情况,而且基本不受阳光干扰(当环境温度升高时,声音的传播速度会加快,对测距产生一定的误差,但是误差很小很小,对于超声波模实现避障来说完全可以忽略)也就是说超声波模块很好的解决了以上两个问题。

 

缺点呢或者说不足之处,也很明确,从避障思路我们可以看出这种方案小车不能连续的运动,当遇到障碍物时,小车就会停下来判断下一步该往哪个方向转弯,除此之外,超声波模块用起来比前两种难一些。

 

   4、利用超声波模块和2个漫反射光电传感器实现避障(也就是本文采用的方法)

 

   避障思路呢就是利用超声波模块检测前面的障碍物,两个漫反射光电传感器分别检测左右两侧的障碍物。

 

微信图片_20201122171348

 

这种思路结合了这两种传感器的优点,互补了他们的不足,即同时克服阳光直射时传感器不能正常工作、车速过快时,在检测到障碍物时,不能直接转弯、遇到障碍物需要停车判断 这三种不足可以说是一种比较理想的方案。唯一的缺点呢,就是超声波模块本身用起来难度要大一些,当然对富有挑战精神的人,也就不算缺点了

 

二、超声波测距程序的编写

 

   1、概述

 

   我将在本系列第一篇博文中介绍的程序的基础上进行超声波测距程序的编写的介绍,前提是理解超声波模块的测距的工作流程(不明白的可以去看本系列第二篇文章的介绍),我们需要用到两个定时器,而STC89C52单片机只有两个定时器,前面呢我们已经把定时器0中断用来控制电机的PWM输出,所以我们把超声波测距的启动信号也合并到定时器0里面,让定时器1来检测超声波模块有无信号返回。

 

   2、I/O口触发测距的实现程序的编写

 

   超声波模块工作的第一步呢就是让块Trig 管脚所接的单片机I/O口置为高电平,而且需要持续10us以上,(本例中我采用的是让单片机P14 I/O口 接在了超声波模块的Trig 管脚上,让单片机P15 I/O口 接在了超声波模块的Echo管脚上,大家可以根据实际情况自己选择,只要跟程序对应起来就行),此处有一点需要注意就是为了避免下一次超声波模块发出的信号,对上一次的返回信号产生影响,超声波模块发出信号的时间间隔要在60ms以上,本例中我配置的定时器0是每1ms产生一次中断,设置一个变量HC_SR04_time,定时器0每产生一次中断,该变量累加1,每当加到250时,让该变量清零,同时让超声波模块发出检测信号,也就是说每250ms 超声波模块发出一次检测信号(数值呢大家可以自行调节),发出检测信号只需要让Trig为高电平并且持续10us以上 ,再让Trig为低电平,这样超声波模块就会自动发出一次检测信号了,我把这个过程写在函数StartModule() 里面本部分相关程序如下:

 

sbit Trig= P1^4; //产生脉冲引脚
sbit Echo= P1^5; //回波引脚

void  StartModule() 		         //启动超声波模块
  {
	  Trig=1;			                
	  _nop_();     //此语句是空语句,用来延时,也就是满足高电平持续10us以上的要求
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_();
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_();
	  Trig=0;
  }

 

void Timer0Init()      //定时器0初始化函数
{
	TMOD|=0X01;//选择为定时器0模式,工作方式1,仅用TR0打开启动。

	TH0=0XFC;	//给定时器赋初值,定时1ms
	TL0=0X18;	
	ET0=1;//打开定时器0中断允许
	EA=1;//打开总中断
	TR0=1;//打开定时器			
}

 

void timer0()interrupt 1 using 2   //定时器0中断函数
{ 
TH0=0XFC;	//给定时器赋初值,定时1ms
TL0=0X18;
time++; 
pwm_val_left++; 
pwm_val_right++; 
pwm_out_left_moto(); 
pwm_out_right_moto(); 
	
//以下内容是新加的:
HC_SR04_time++;
if(HC_SR04_time>=250)   //250ms 启动一次超声波测距
{
	HC_SR04_time=0;
	StartModule();
}
} 

 

   3、检测有无障碍物的程序编写以及检测距离的调节方法

 

   上面呢我们已经利用定时器0来通过 Trig口让超声波模块每250ms发出一次检测信号,接下来我们需要用定时器1来检测有没有信号返回,也就是前方有没有障碍物,怎么实现呢,通过前面的介绍我们知道,只要Echo管脚为高电平就有信号返回,通过定时器1来测量高电平持续的时间,通过公式:测试距离=(高电平时间*声速(340M/s)/2) 就可以计算障碍物距离传感器的距离了,当Echo变为高电平时,就让定时器1清零,并打开定时器1,开始计数,当Echo变为0时,利用公式计算障碍物距离,计算完后令定时器1清零 并关闭定时器1 这样只要有信号返回,即只要Echo被置为1 定时器1就会被及时的清零,也就不会触发定时器1溢出中断,若是没有信号返回(也就是没有障碍物,或者障碍物超出了传感器的测量范围,本文用的传感器测量最大值为4.5米),Echo就不会被置1,定时器1也就不会被清零,从而产生溢出中断,在溢出中断里放一个变量flag ,若产生中断则让其置1,这样就通过定时器是否产生溢出中断,即flag是否为1,来判断是否检测到障碍物了。

 

   有一点需要注意,这样检测的是距离传感器最大检测距离内有无障碍物,但是我们把它用来让小车避障,我不能距离障碍物4.5米就让小车转弯吧,这就需要通过程序调节检测距离了,只需要增加一个判断条件就行了,我们利用公式算出来障碍物的距离S ,将flag=1或者S>我们设定的检测距离,都视为没有障碍物处理就可以了,如 if(flag==1||S>50) 就是把50cm作为检测距离,检测的是50cm内有无障碍物,50呢是暂定的,在后期调车的时候,根据实际情况进行调节

 

   本部分有关程序如下(完整的工程文件会在介绍完避障程序后一并给出):

 

void Timer1Init()   //定时器1初始化
{
	TMOD|=0X10;//选择为定时器1模式,工作方式1,仅用TR1打开启动。
	TH1=0;	
	TL1=0;	
	ET1=1;//打开定时器1中断允许
	EA=1;//打开总中断
	TR1=1;//打开定时器			
}

 

void Timer1() interrupt 3   //定时器1溢出中断
{
	flag=1;    //若定时器1溢出则flag置1
}

 

 void Conut(void)    //计算障碍物距离函数
	{
	 measure_time=TH1*256+TL1;
	 TH1=0;
	 TL1=0;
	 S=(measure_time*1.87)/100;     //这就是计算距离的公式,参数1.87根据实际情况调节,算出来是CM
	 if(flag==1||S>50)		    //超出测量或者超出设定的距离
	 {
	  flag=0;
	 LED=1;         //此处是测试用的,我用的单片机在P00上接了个LED
	 }
   else 
	 LED=0;         
	
	}	

 

void main()    //主函数
	{
	Timer0Init();
	Timer1Init();	
	Left_Speed_Ratio=5;   //设置左电机车速为最大车速的50%
	Right_Speed_Ratio=5;	设置右电机车速为最大车速的50%
	while(1)
		{
		
			if(Echo==1)
			{
				TH1=0;	
	            TL1=0;
				TR1=1;			    //开启计数	
				while(Echo);			//当RX为1计数并等待	
				TR1=0;				//关闭计数	
			    Conut();			//计算			
		    }			
//	run();
//	delay1s(); delay1s();  delay1s();  delay1s();  delay1s();				
		}
}

 

三、自动避障的实现

 

   目前呢我们用于避障的传感器有检测左右两侧障碍物的漫反射光电管,检测前方障碍物的超声波模块,大体思路是这样的,当超声波模块没有检测到设定距离内的障碍物时,小车直行,当检测到前方障碍物时,若此时左边漫反射传感器没有检测到障碍物,则左转,当此时左边有障碍物时,若右边没有障碍物则右转,若右边也有障碍物则后退,大家看的可能比较乱,我整理成如下的逻辑表:

 

微信图片_20201122171459

 

   表格中的有代表有障碍物,无代表无障碍物,X代表可以有也可以没有障碍物 若前方有障碍物则 M_sensor=0 没有则 M_sensor=1,若左侧有障碍物 ,则 L_sensor=0 ,没有则 L_sensor=1 若右侧有障碍物则 R_sensor=0 没有则 R_sensor=1

 

程序如下:

 

 if(M_sensor==1)
				{  run(); }
		  else
			  {
			      if(L_sensor==1)
				       {     left();       }
			
			      else if(R_sensor==1)
				       {    right() ;      }
			      else
						   {    back();          }
			
			 }
	

 

   在测试中我发现,当电池电量较低时,原来的转弯方式(一侧电机停转,另一侧电机前进)转弯效率较低,所以说把转弯的函数修改为一侧后退,另一侧前进

 

代码如下:

 

void left(void)   //小车左转
{ 
push_val_left =Left_Speed_Ratio; 
push_val_right =Right_Speed_Ratio;
Right_moto_go(); 
Left_moto_back();
} 

 void right(void) //小车右转
{ 
push_val_left =Left_Speed_Ratio;
push_val_right =Right_Speed_Ratio;
Right_moto_back();
Left_moto_go();

 

   经过测试后,这种方案呢并没有理想的那么好,因为超声波模块存在一些缺陷,比如偶然会产生误判,明明前方什么障碍物都没有,会在一瞬间突然检测到障碍物,又突然消失,而且在以下两种情况下检测不到障碍物

 

微信图片_20201122171555

 

如上图的这种情况呢,超声波模块检测不到返回的信号,产生误判

 

微信图片_20201122171634

 

   如上图的这种情况呢,超声波模块检测不到侧前方的细柱型障碍物,产生误判

 

    以上两种缺陷呢,是超声波模块固有的缺陷,是无法通过超声波模块本身去纠正的,解决方法呢,可以在超声波模块的两侧分别再加一个朝前的检测前方障碍物的漫反射光电传感器,这样呢可以克服以上的几个缺陷,经过我的实际测试,漫反射光电传感器几乎不会产生误判,除了检测距离有点短之外,可以说是比较好的,这样呢就是4路漫反射光电传感器为主,超声波模块呢只是辅助检测前方的较远的的障碍物,补充漫反射传感器检测距离有限的缺陷,这种方案呢我就不详细介绍了,有兴趣的可以自己尝试一下

 

四、完整的各文件代码

 

   main.c文件

 

#include <car.h>

extern unsigned char Left_Speed_Ratio;
extern unsigned char Right_Speed_Ratio;
unsigned int time=0; 
unsigned int HC_SR04_time=0;
extern unsigned char pwm_val_left;
extern unsigned char pwm_val_right;
 bit   flag =0;
extern char M_sensor;  
 
void delay1s(void)   
{
    unsigned char a,b,c;
    for(c=167;c>0;c--)
        for(b=171;b>0;b--)
            for(a=16;a>0;a--);
    _nop_();  
}
void delay1ms(void)   
{
    unsigned char a,b,c;
    for(c=1;c>0;c--)
        for(b=142;b>0;b--)
            for(a=2;a>0;a--);
}

void Timer0Init()
{
	TMOD|=0X01;//选择为定时器0模式,工作方式1,仅用TR0打开启动。

	TH0=0XFC;	//给定时器赋初值,定时1ms
	TL0=0X18;	
	ET0=1;//打开定时器0中断允许
	EA=1;//打开总中断
	TR0=1;//打开定时器			
}

void Timer1Init()
{
	TMOD|=0X10;//选择为定时器1模式,工作方式1,仅用TR1打开启动。
	TH1=0;	
	TL1=0;	
	ET1=1;//打开定时器1中断允许
	EA=1;//打开总中断
	TR1=1;//打开定时器			
}

void timer0()interrupt 1 using 2 
{ 
TH0=0XFC;	//给定时器赋初值,定时1ms
TL0=0X18;
time++; 
pwm_val_left++; 
pwm_val_right++; 
pwm_out_left_moto(); 
pwm_out_right_moto(); 
	
HC_SR04_time++;
if(HC_SR04_time>=300)   //300ms 启动一次超声波测距
{
	HC_SR04_time=0;
	StartModule();
}
} 

void Timer1() interrupt 3
{
	flag=1;    //若定时器1溢出则flag置1
}



void main()
	{
	Timer0Init();
	Timer1Init();	
	Left_Speed_Ratio=6;   //设置左电机车速为最大车速的50%
	Right_Speed_Ratio=6;	设置右电机车速为最大车速的50%
	while(1)
		{
		
			if(Echo==1)
				{
				TH1=0;	
	      TL1=0;
				TR1=1;			    //开启计数	
				while(Echo);			//当RX为1计数并等待	
				TR1=0;				//关闭计数	
			  Conut();			//计算		
			}
				
		  if(M_sensor==1)
				{  run(); }
		  else
			  {
			      if(L_sensor==1)
				       {     left();       }
			
			      else if(R_sensor==1)
				       {    right() ;      }
			      else
						   {    back();          }
			
			 }
	
			
			
		}
}


 

   motor_control.c文件

 

#include <car.h>


unsigned char pwm_val_left =0;
unsigned char push_val_left =0; 
unsigned char pwm_val_right =0;
unsigned char push_val_right=0;
unsigned char Left_Speed_Ratio;
unsigned char Right_Speed_Ratio;

bit Left_moto_stop =1;
bit Right_moto_stop =1;


void Left_moto_go()  //左电机正转
{p34=0;p35=1;} 
void Left_moto_back() //左电机反转
{p34=1;p35=0;} 
void Left_moto_stp()  //左电机停转
 {p34=1;p35=1;} 
void Right_moto_go()  //右电机正转
{p36=0;p37=1;} 
void Right_moto_back() //右电机反转
{p36=1;p37=0;}  
void Right_moto_stp()  //右电机停转
{p36=1;p37=1;} 


void pwm_out_left_moto(void)    //左电机PWM
{ 
if(Left_moto_stop) 
{ 
if(pwm_val_left<=push_val_left) 
Left_moto_pwm=1; 
else 
Left_moto_pwm=0; 
if(pwm_val_left>=10) 
pwm_val_left=0; 
} 
else 
Left_moto_pwm=0; 
} 

void pwm_out_right_moto(void)    //右电机PWM
{ 
if(Right_moto_stop) 
{ 
if(pwm_val_right<=push_val_right) 
Right_moto_pwm=1; 
else 
Right_moto_pwm=0; 
if(pwm_val_right>=10) 
pwm_val_right=0; 
} 
else 
Right_moto_pwm=0; 
} 


void run(void)     //小车前行
{ 
push_val_left =Left_Speed_Ratio;    
push_val_right =Right_Speed_Ratio; 
Left_moto_go(); 
Right_moto_go(); 
 } 

 

void back(void)   //小车后退
{ 
push_val_left =Left_Speed_Ratio; 
push_val_right =Right_Speed_Ratio; 
Left_moto_back();
Right_moto_back();
 } 



void left(void)   //小车左转
{ 
push_val_left =Left_Speed_Ratio; 
push_val_right =Right_Speed_Ratio;
Right_moto_go(); 
Left_moto_back();
} 

 void right(void) //小车右转
{ 
push_val_left =Left_Speed_Ratio;
push_val_right =Right_Speed_Ratio;
Right_moto_back();
Left_moto_go();
} 

void stop(void)  //小车停止
{ 
push_val_left =Left_Speed_Ratio; 
push_val_right =Right_Speed_Ratio; 
Left_moto_stp();
Right_moto_stp();
 } 

void rotate(void) //小车原地转圈
{ 
push_val_left =Left_Speed_Ratio; 
push_val_right =Right_Speed_Ratio; 
Left_moto_back();
Right_moto_go();
 } 



 

   HC_SR04.c文件

 

#include <car.h>

float  S=0;
extern bit  flag;
unsigned int  measure_time;
char M_sensor; 
 void  StartModule() 		         //启动超声波模块
  {
	  Trig=1;			                
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_();
	  _nop_(); 
	  _nop_(); 
	  _nop_(); 
	  _nop_();
	  Trig=0;
  }
	
 void Conut(void)
	{
	 measure_time=TH1*256+TL1;
	 TH1=0;
	 TL1=0;
	 S=(measure_time*1.87)/100;     //算出来是CM
	 if(flag==1||S>50||S<2)		    //超出测量
	 {
	  flag=0;
	 LED=1;
	 M_sensor=1;
	 }
   else
		 {
	 LED=0;
	 M_sensor=0;
		 }
	}	

	


 

   car.h文件

 

#ifndef __car_H
#define __car_H

#include <reg52.h>
#include <intrins.h>

sbit Left_moto_pwm=P1^6 ;
sbit Right_moto_pwm=P1^7;
sbit p34=P3^4;
sbit p35=P3^5; 
sbit p36=P3^6;
sbit p37=P3^7;
sbit Trig= P1^4; //产生脉冲引脚
sbit Echo= P1^5; //回波引脚
sbit LED=P0^0;
sbit L_sensor=P2^0;
sbit R_sensor=P2^1;
void Left_moto_go() ;
void Left_moto_back() ;
void Left_moto_stp() ;
void Right_moto_go();
void Right_moto_back(); 
void Right_moto_stp(); 
void delay(unsigned int k) ;
void delay1s(void) ;
void delay1ms(void);
void pwm_out_left_moto(void) ;
void pwm_out_right_moto(void);
void run(void);
void back(void);
void left(void);
void right(void);
void stop(void);
void rotate(void);
void  StartModule() ;
void Timer1Init();
void Timer0Init();
void Conut(void);


#endif

 

本文到这里就结束了,本文介绍的内容的完整的keil文件会放在附件里,需要者自取,我放的时候都是免费的,但是过段时间它会自己涨…需要的在评论区留言我可以直接发给你,欢迎大家继续阅读本系列的后续文章“详细介绍如何从零开始制作51单片机控制的智能小车(四)———通过蓝牙模块实现数据传输以及对小车状态的控制”

   欢迎大家积极交流,本文未经允许谢绝转载

发表评论

后才能评论