详细介绍如何从零开始制作51单片机控制的智能小车(四)———通过蓝牙模块实现数据传输以及通过手机蓝牙实现对小车运动状态的控制

文章目录[隐藏]

   我会通过本系列文章,详细介绍如何从零开始用51单片机去实现智能小车的控制,在本系列的上一篇文章中介绍了如何让小车实现自动避障,本文作为本系列的第四篇文章,主要介绍蓝牙模块的使用,如何通过蓝牙进行数据传输,并通过手机向蓝牙模块发送指令,从而达到使用手机控制智能小车的运动状态,本文以汇承HC-08蓝牙模块为例。

本系列文章链接:

-----------------------------------------------------------------------------

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

-----------------------------------------------------------------------------

一、蓝牙模块的选择和基本设定

   1、工作原理简单介绍

在这里插入图片描述

   以上图片来自汇承官方用户手册,HC-08模块用于代替全双工通信时的物理连线。左边的设备向模块发送串口数据,模块的 RXD 端口收到串口数据后,自动将数据以无线电波的方式发送到空中。右边的模块能自动接收到,并从 TXD 还原最初 左边设备所发的串口数据。从右到左也是一样的。

   2、测试模块是否正常工作,以及相关参数的设定

   建议新手朋友们,购买新手套餐,上面左图是测试架,用来实现模块与电脑的连接,右图为本文介绍的HC-08蓝牙模块,只需要把蓝牙模块插到测试架上,通过安卓数据线,就可以与电脑相接了,通过HID 串口助手(汇承官网有HC-08资料包,内部包含相关辅助工具及详细的资料) ,向蓝牙发送AT,若返回OK,则模块正常工作
   接着向蓝牙发送AT+RX 查看模块基本参数 ,主要关注两条信息,蓝牙是主机还是从机模式,设定的波特率是多少,默认出厂时是从机模式,波特率9600,恰好是本文我们需要的数值,若你单片机程序的通信波特率不是9600,可以通过HID 串口助手或者AT指令把蓝牙模块修改成你需要的波特率

   3、蓝牙模块与单片机的连接,与手机的通信

在这里插入图片描述

   在这里需要确定你用的蓝牙模块的工作电压是多少,汇承HC-08有贴片装和带底板装两种,建议新手一定要买带底板的,如上图所示,可以省去很多麻烦。我们使用的51单片机供电电压是5v,而贴片装电压3.3v,不能直接与单片机连接,虽然本文介绍的这款单片机最小系统有3.3v的输出引脚,但是它串口TX RX依然是工作在5v电压下,而贴片装蓝牙模块的TX,RX需要工作在3.3v电压下,蓝牙模块的 RX 端需要串接一个 220Ω~1KΩ的电阻再接到单片机上,TX可以直接连接,对新手来说有点小麻烦,所以大家买的时候,直接买带底板的,支持3.2v-6v,也就是说带底板的可以直接与51单片机连接, 连接时只需要连4根线 Vcc连Vcc,GND连接GND,蓝牙模块TX接单片机 Rx (对于前文介绍的这款的单片机,RX为P30管脚,TX为P31管脚,如下图所示),蓝牙模块RX接单片机 Tx。
   蓝牙模块不能直接与手机连接需要通过HC-COM这个app进行连接,给模块上电后,开启手机蓝牙功能,打开手机上的HC-COM,点击扫描设备,找到蓝牙模块进行连接,连接成功显示connected。

   关于以上的第一部分,因为大家选择的蓝牙模块型号或者商家不同,会有一定的差异,在上文仅就本文需要用的地方进行了简单的介绍,蓝牙模块详细的用法步骤可以去看你购买的蓝牙的用户手册

二、蓝牙模块串口通信程序的编写以及蓝牙遥控的实现

   1、与定时器相关函数的编写

   我们知道我们使用STC89C52单片机,只有两个定时器,在前文我们用定时器0,来实现电机的PWM输出,定时器1,来触发溢出中断,用于超声波模块障碍物的检测,而现在我们用蓝牙进行无线串口的通信,也需要用定时器,怎么办呢? 定时器0肯定是不能动的,只能对定时器1进行复用,我们设定一个变量char Work_Mode=0; 进行工作模式的选择 为0时为手机或者电脑等上位机对小车进行蓝牙遥控 ,为1时小车自动避障模式,在一开始的时候,我们把它设为0,此时对定时器1初始化为蓝牙模块所需的工作模式,在我们需要转化成自动避障模式时,通过手机发送指令让Work_Mode变为1,此时我们把定时器1初始化为超声波避障所需的工作模式,由于此步是在while(1)循环中进行的,但是初始化只需要初始一次,不能一直初始化,所以需要再设定一个变量Work_Mode2,初始值设为0,当执行一次把定时器1初始化为超声波避障所需的工作模式的初始化后,就让该变量为1,这样就可以实现只初始化一次了,相关代码如下:
void Timer1Init()    //定时器1设定为为自动避障模式所需的初始化
{
	TMOD=0X11;//选择为定时器1模式,工作方式1,仅用TR1打开启动。选择为定时器0模式,工作方式1,仅用TR1打开启动
	TH1=0;	
	TL1=0;	
	ET1=1;//打开定时器1中断允许
	EA=1;//打开总中断
	TR1=1;//打开定时器			
}



void Timer1Init2()   //定时器1设定为为蓝牙遥控模式所需的初始化
{
		SCON=0X50;			//设置为工作方式1,8位数据,可变波特率
	  TMOD |=0X20;			//设置计数器工作方式2
	  PCON=0X00;			//波特率不加倍
	  TH1=0XFd;		    //计数器初始值设置,9600  @11.0592MHz
	  TL1=0XFd;
	  TR1=1;					//打开计数器
	  ES = 1;         //开串口中断
    EA = 1;         //开总中断
}
char Work_Mode=0;   //工作模式的选择  为0时,为手机或者电脑等上位机对小车进行蓝牙遥控 ,为1时小车自动避障模式
char Work_Mode2=0;


void main()
	{
	Timer0Init();
	Timer1Init2();	
	Left_Speed_Ratio=5;   //设置左电机车速为最大车速的50%
	Right_Speed_Ratio=5;	设置右电机车速为最大车速的50%
	while(1)
		{
		  if(Work_Mode==1)
			{
			   	ES=0;   //关闭串口中断				  
				 if(Work_Mode2==0)
				 {
					Timer1Init();
                    Work_Mode2=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();          }
		     	 }
					
			}
			else
				
			{
				
			switch(receive_real_data) 
        { 
        
		      case '1': run(); break; 
		      case '2': left(); break; 
		      case '3': right(); break; 
	     	  case '4': back(); break; 
		      case '5': Speed_add(); break; 
	     	  case '6': Speed_reduce(); break; 
			 case '7': stop(); break; 
	        case '8': Work_Mode=1; break; 
	        }
			
				
		    LED=1;
     }	
		}
}

   2、对上面的主函数的解释(蓝牙遥控的实现思路)

   本篇文章的功能是在已经完成自动避障的基础上进行扩展的,也就是让小车可以具备自动避障和蓝牙遥控两种工作模式,所以当Work_Mode=1时的程序,也就是工作在自动避障模式的程序,在对定时器1完成设定为自动避障模式所需的初始化后,跟本系列第三篇文章介绍的是相同的,已详细介绍过了,在这就不介绍了,一开始的时候Work_Mode=0,工作在蓝牙遥控模式,我设定的功能如上面的主函数所示,当手机或者电脑或者其他上位机对单片机(通过蓝牙模块)发送1的时候,让小车前行。发送2的时候 让小车左转,发送3的时候让小车右转,发送4的时候,让小车后退,发送5的时候,让小车加速,发送6的时候让小车减速,发送7的时候,停车,发送8的时候,把Work_Mode置为1,使小车进入自动避障模式,此时蓝牙遥控失去作用,按下单片机上的复位键,程序将重新加载,重新进入蓝牙遥控模式。新增的加速减速函数如下:
void  Speed_add() //加速函数
{
	if(Left_Speed_Ratio<10) //限幅
		{
		Left_Speed_Ratio++;
	  Right_Speed_Ratio++;	
    }
}

void  Speed_reduce() //减速函数
{
	if(Left_Speed_Ratio>0)  //限幅
		{
		Left_Speed_Ratio--;
	  Right_Speed_Ratio--;	
    }
}

   3、串口中断函数的编写

    上面呢我们已经介绍了,通过发送一些设定好了的指令,实现蓝牙遥控,那么怎么把指令发送出去,以及单片机是如何实现接收的呢?,这就要通过串口中断来实现了,下面是官方给的一个串口中断的参考代码,是有问题的,对于HC-COM来说,是不能正常工作的
void Com_Int(void) interrupt 4
{

  uchar receive_data;
	
  EA = 0;
	
  if(RI == 1) //当硬件接收到一个数据时,RI会置位
	{ 		
		RI = 0;
		receive_data = SBUF;//接收到的数据
			
		if(receive_data == '1')	 
		{
				LED =0;//接收到1亮灯
		}
		else
		{
				LED =1; //其他情况灯灭
		}
		
	}
	  SBUF=receive_data;//将接收到的数据放入到发送寄存器
	  while(!TI);			 //等待发送数据完成
	  TI=0;						 //清除发送完成标志位		
		EA = 1;
}
    按照上面的代码,当我们利用手机上的app HC-COM向单片机发送1的的时候,接收的数据receive_data=1,此时呢LED应该等于0,也就是LED会被点亮,但是实际上它只会闪一下,这就说明,我们通过HC-COM发送1的时候,单片机先接收到我们发的1,之后又接收到其他的信息,在上面的程序中,我们让单片机把接收到的信息又通过蓝牙模块发送给手机,利用HC-COM进行显示,如下图所示:
    乍一看,我们发送1的时候,只返回了一个1 ,发送0的时候,只返回了一个0,那单片机接收的其他信息是从何而来的呢?,为啥手机没有显示返回的其他信息呢?对此我进行了大量的实验,最终发现这个app,它一次发送实际上是发送20位,什么意思呢? 当我们输入一个1点击发送的时候,它实际上发送的是1000 0000 0000 0000 0000,也就是说当我们输入的数据的位数不足20位时它会自动补零,这就解释了为什么LED灯不会常亮,只会闪一下,因为在接收完我们发送的1后,它又接收了19个0,把这20个数据返回我们手机上的时候,它这个app把它补得这19个0又以空格的格式进行显示,而不是显示0(有点坑)。
    为了让LED常亮,我们需要输入20个1,如上图所示,也就是不给它补零的机会。这种方法呢用起来十分的不方便,而且本文我们要通过HC-COM发送指令,实现对小车的遥控,也就说需要我们快速的输入指令,这种方法是不行的,怎么办呢?,对大部分人了说不具备修改这个app的能力,那么只能修改单片机的接收和发送函数了,因此我把上面的串口中断函数进行了简单的修改,如下:
void Com_Int(void) interrupt 4
{
    EA = 0;	
  
  if(RI == 1) //当硬件接收到一个数据时,RI会置位
	{ 
   	LED=0;
		RI = 0;
		receive_data = SBUF;//接收到的数据
		if(receive_data!=0)
		receive_real_data=receive_data;
	 
		SBUF=receive_real_data;//将接收到的数据放入到发送寄存器
	  while(!TI);			 //等待发送数据完成
	  TI=0;						 //清除发送完成标志位
		
		
}
    EA = 1;	
}
    只有接收到的数据不为0时,才赋值给新的变量receive_real_data,我们通过判断receive_real_data的值,来控制小车,而不是直接用接收到的值receive _data,同样我们返回到手机的数据,也改为receive_real_data。这样我们只需要发送一位的 1、2、3、4、5、6、7、8、等就可以对小车进行控制,用起来很方便。

三、各文件完整的程序代码

   1、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;  
char Work_Mode=0;   //工作模式的选择  为0时,为手机或者电脑等上位机对小车进行蓝牙遥控 ,为1时小车自动避障模式
char Work_Mode2=0;
unsigned char receive_data=0;
unsigned char receive_real_data=0;
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=0X11;//选择为定时器1模式,工作方式1,仅用TR1打开启动。选择为定时器0模式,工作方式1,仅用TR1打开启动
	TH1=0;	
	TL1=0;	
	ET1=1;//打开定时器1中断允许
	EA=1;//打开总中断
	TR1=1;//打开定时器			
}



void Timer1Init2()
{
		SCON=0X50;			//设置为工作方式1,8位数据,可变波特率
	  TMOD |=0X20;			//设置计数器工作方式2
	  PCON=0X00;			//波特率不加倍
	  TH1=0XFd;		    //计数器初始值设置,9600  @11.0592MHz
	  TL1=0XFd;
	  TR1=1;					//打开计数器
	  ES = 1;         //开串口中断
    EA = 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>=500)   //500ms 启动一次超声波测距
{
	HC_SR04_time=0;
	StartModule();
}
} 

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


void Com_Int(void) interrupt 4
{
    EA = 0;	
  
  if(RI == 1) //当硬件接收到一个数据时,RI会置位
	{ 
   	LED=0;
		RI = 0;
		receive_data = SBUF;//接收到的数据
		if(receive_data!=0)
		receive_real_data=receive_data;
	 
		SBUF=receive_real_data;//将接收到的数据放入到发送寄存器
	  while(!TI);			 //等待发送数据完成
	  TI=0;						 //清除发送完成标志位
		
		
}
    EA = 1;	
}

void main()
	{
	Timer0Init();
	Timer1Init2();	
	Left_Speed_Ratio=5;   //设置左电机车速为最大车速的50%
	Right_Speed_Ratio=5;	设置右电机车速为最大车速的50%
	while(1)
		{
		  if(Work_Mode==1)
			{
			   	ES=0;   //关闭串口中断
				  
				 if(Work_Mode2==0)
				 {
					
					Timer1Init();
           Work_Mode2=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();          }
			
			 }
					
			}
			else
				
			{
				
			switch(receive_real_data) 
        { 
        
		      case '1': run(); break; 
		      case '2': left(); break; 
		      case '3': right(); break; 
	     	  case '4': back(); break; 
		      case '5': Speed_add(); break; 
	     	  case '6': Speed_reduce(); break; 
				  case '7': stop(); break; 
	        case '8': Work_Mode=1; break; 
	        }
			
				
		    LED=1;
     }	
		}
}


   2、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();
 } 


void  Speed_add() //加速函数
{
	if(Left_Speed_Ratio<10)
		{
		Left_Speed_Ratio++;
	  Right_Speed_Ratio++;	
    }
}

void  Speed_reduce() //减速函数
{
	if(Left_Speed_Ratio>0)
		{
		Left_Speed_Ratio--;
	  Right_Speed_Ratio--;	
    }
}


   3、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;
		 }
	}	

   4、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);
void Timer1Init2();
void  Speed_add() ;
void  Speed_reduce();


#endif

四、控制效果的视频演示

      手机蓝牙控制效果视频展示链接

或者直接访问如下网址:https://www.bilibili.com/video/bv1t54y1Q7zF

五、智能小车的一些优化方向及思路

    到这里本系列文章就要告一段落了,本系列的四篇文章介绍了,如何从零开始,慢慢的,一步步的实现了,一个51单片机控制的一个具备自动避障和蓝牙遥控双工作模式的一个小车,本来还想利用本系列第五篇文章介绍一下如何利用上位机对小车的一些变量,比如速度的大小、各个传感器是否检测到障碍物、小车的运动状态,进行监控,并将其绘制成图像,进行分析,但是之后的一段时间我会比较忙,就先不进行详细介绍了,怎么实现呢?有兴趣的可以看一下下面这篇博文,链接如下:

   详细介绍如何从0开始写一个数据通信,将数据从单片机发送到上位机(或者虚拟示波器)进行数据或图像显示,以及常见问题或注意事项解答,本文主要以匿名上位机为例,适合新手和小白

    上面这篇文章介绍的很详细,大家照着去实现上面的功能应该不难,除此之外,在本系列的第三篇文章介绍了,这个智能小车在检测前面的障碍上尚有不足,有兴趣的可以尝试一下4个漫反射光电传感器+超声波模块的避障方案。

    本文介绍的内容,已经实现了手机跟单片机之间双向的数据传输,主要用的是手机向单片机发送控制指令,单片机返回发送的指令,其实除了发送控制指令,还可以发送查询指令,比如说我们向单片机发送一个9,单片机接收到9之后,不对小车的运动进行控制,返回的值也不让它返回9,我们可以让它返回当前小车的运动信息,比如当前的车速、各传感器的工作状态、小车的运动状态等等

    当然还有很多的优化方案及思路,大家可以自行进行思考,和继续优化,当大家感觉研究的差不多之后,就可以研究一下更高级的车了,无论是从硬件上,还是控制算法上都有非常大的提升空间,毕竟本文介绍的只是一个先手入门级的小车模型

附小车照片:

  本文介绍的内容的完整代码的keil文件和蓝牙HC—08模块的资料包(包括HC-COM这个app在内)我会放到附件里,还是那句话,我放的时候都是免费的,但是它会自己涨,需要的可以评论区留言,我直接发给你
   欢迎大家积极交流,本文未经允许谢绝转载

版权声明:本文为CSDN博主「慕羽★」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44339029/article/details/106562519

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

本系列文章链接:

-----------------------------------------------------------------------------

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

-----------------------------------------------------------------------------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

   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();				
		}
}

三、自动避障的实现

   目前呢我们用于避障的传感器有检测左右两侧障碍物的漫反射光电管,检测前方障碍物的超声波模块,大体思路是这样的,当超声波模块没有检测到设定距离内的障碍物时,小车直行,当检测到前方障碍物时,若此时左边漫反射传感器没有检测到障碍物,则左转,当此时左边有障碍物时,若右边没有障碍物则右转,若右边也有障碍物则后退,大家看的可能比较乱,我整理成如下的逻辑表:
   表格中的有代表有障碍物,无代表无障碍物,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();
   经过测试后,这种方案呢并没有理想的那么好,因为超声波模块存在一些缺陷,比如偶然会产生误判,明明前方什么障碍物都没有,会在一瞬间突然检测到障碍物,又突然消失,而且在以下两种情况下检测不到障碍物
   如上图的这种情况呢,超声波模块检测不到返回的信号,产生误判
   如上图的这种情况呢,超声波模块检测不到侧前方的细柱型障碍物,产生误判
    以上两种缺陷呢,是超声波模块固有的缺陷,是无法通过超声波模块本身去纠正的,解决方法呢,可以在超声波模块的两侧分别再加一个朝前的检测前方障碍物的漫反射光电传感器,这样呢可以克服以上的几个缺陷,经过我的实际测试,漫反射光电传感器几乎不会产生误判,除了检测距离有点短之外,可以说是比较好的,这样呢就是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单片机控制的智能小车(四)———通过蓝牙模块实现数据传输以及对小车状态的控制”
   欢迎大家积极交流,本文未经允许谢绝转载

版权声明:本文为CSDN博主「慕羽★」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44339029/article/details/106487524

   从本文开始,在之后的一段时间里,我会通过本系列文章,详细介绍如何从零开始用51单片机去实现智能小车的控制,本文作为本系列的第一篇文章,主要介绍如何让小车动起来。

本系列文章链接:

-----------------------------------------------------------------------------

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

-----------------------------------------------------------------------------

一、硬件的选择

   1、底盘和电机

   底盘的形状呢,大家可以按照自己的需要自主选取,至于电机关注一下工作电压,转速,电机类型就差不多,对于新手,可以尝试以下样式(4WD智能小车底盘,附带4个直流减速电机,电机接线需要自己焊接),也就是本文例子采用的底盘和电机,组装简单,使用方便,特别适合新手。

在这里插入图片描述

   2、电机驱动模块

    L298N电机驱动模块,绝对是新手的首选,但是此系列也包含了很多类型,本文采用的是L298N双H桥驱动 红色版 ,除了性能外,我选择它是因为它具备了5v的输出接口,可以用来给单片机供电。大家可以用两个这种驱动,也可以用一个,另一个用个便宜点的。
    由于家中有一个如下式样的L298N驱动,所以为了不让资源浪费,另一个,我就用如下型号的L298N驱动。

   3、单片机最小系统

    关于最小系统,大家只要选用自己熟悉的就行了,没什么特别的讲究,我采用的样式如下(芯片采用的是STC89C52)

   4、电源

    这一部分大家根据需要自己选择即可,选的时候注意一下电压和容量就行,我选用的是常见的 9v 650mAh(容量不是很大,但是这种电池比较常见,充一次电跑2~3小时应该不是问题),我选的是USB充电款,充电很方便,电池盒我选用的是如下的这种拆卸方便的款式,缺点就是不附带电源开关。

   5、杜邦线

    这是必备的辅件,就不多说了,公对公,母对母,公对母(这一种一般用的多一些)都要买一些,家里常备物品。

二、硬件的连接

    本文涉及到的硬件连接为单片机、电源、 电机、 两个电机驱动L298N之间的连接,在这里我介绍一种参考的连接方式,大家可以自己设计连接方案
    如上图所示,四个电机的正负极分别接两个L298N的绿色电机接口,至于到底哪个接正极,哪个接负极,根据你电机安装的方式而定,建议先把电机的两根线焊上,然后把底盘安装起来,这样电机的安装方式就确定了,先随便把两个L298N的4个绿色电机接口跟电机相接,等到把其他信号线接好后,再判断对错并调节,调节方法如下:在程序中让小车往前跑,观察车轮的转向,往前转的车轮的线不用变,把往后转的电机对应的L298N绿色接口的两根线换一下就行了。
   如上图所示,L298N的左边数第一个蓝色端口 是5V输出,把其中一个L298N的该接口接到单片机的5v接口上,另一L298N该接口可以空着,左边数第二个蓝色端口是GND需要同时与单片机的GND与电源的负极相接,左边数第三个蓝色端口是L298N的电源输入端口,与电源的正极相接,我采用的是9v的电源。
   剩下的就是L298N的信号线与单片机的连接了,介绍如上图所示,在这里我采用的是双驱的接法,也就是左边两个点击用同一个信号控制,右边两个电机用同一个信号控制,单片机的I/O口自行选择,与程序配合起来就行,我选用的是 ENA接P16 ENB 接P17 IN1接P34 IN2接P35 IN3接P36 IN4接P37 若改为4驱所需的I/O将扩大一倍。

   收到部分读者说这部分连线还是不太明白,于是我简单画了以下的草图,帮助大家理解上面的内容(字不好看,请见谅)

   实物图片如下:

三、程序的编写

   1、工程的建立

   编译环境根据自己习惯和需要选择,本文以KEIL C51为例,由于本次设计的小车控制并不复杂,所以我把工程中用到的所有头文件、函数的定义、sbit定义的位变量都放到了一个头文件中,取名为car.h(名字大家随意取即可),C文件呢我建议大家把各个部分别写在不同的文件中,比如我把与电机驱动有关的函数放到了motor_control.c(名字任意取)文件中,控制方案和延时函数,中断函数放到了主函数main.c(名字任意取)文件中,后续随着功能增加还会增设其他的C文件,只要所有的C文件均包含以上共同的头文件car.h,也就互相建立了联系。

   2、根据L298N与单片机的接线,编写电机控制函数

   虽然说本文选用的车型四个电机可以独立控制,但是为了简单化,方便化,我们让左边的两个电机采用共同的信号控制,右边的两个电机采用共同的信号控制,大家若需要可以自主改为4路独立的信号控制,根据本文第二部分——硬件的连接部分的介绍,我们选用了单片机的P34 P35 I/O口作为左电机的方向控制信号,单片机的P36 P37 I/O口作为右电机的方向控制信号,单片机的P16 I/O口作为左电机的PWM输出控制信号,单片机的P17 I/O口作为右电机的PWM输出控制信号。
   以上6个I/O口的位定义如下(为方便各文件调用,我们把它放到统一的h文件car.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;
    左右电机的状态控制函数如下:
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;} 

  4、PWM调速输出函数的编写:

    对于新手来说,如果理解不了以下两个函数,那只需要知道如何使用就行了,即通过修改push_val_left的值就可以调节左电机的转速,通过修改push_val _right的值就可以调节右电机的转速,push_val_left和push_val_right的值均位于1到10之间,值越大电机转速越快
bit Left_moto_stop =1;
bit Right_moto_stop =1;
unsigned char pwm_val_left =0;
unsigned char push_val_left =0; 
unsigned char pwm_val_right =0;
unsigned char push_val_right=0;


void pwm_out_left_moto(void)     //左电机调速
{ 
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)   //右电机调速
{ 
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; 
} 

  5、小车姿态控制函数的编写:

   理解了 左右电机的状态控制函数,编写小车姿态控制函数就很简单了,大家稍微想一下小车左右轮的状态,小车会怎么运行,就理解了,比如 左右电机都正转,那小车运行状态肯定是前行。每个函数的前两行是左右电机转速的设置。

unsigned char Left_Speed_Ratio;  //左电机转速的设定值
unsigned char Right_Speed_Ratio; //右电机转速的设定值



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_stp();
} 

 void right(void) //小车右转
{ 
push_val_left =Left_Speed_Ratio;
push_val_right =Right_Speed_Ratio;
Right_moto_stp();
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();
 } 

  6、与定时器中断有关函数的编写

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

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



void timer0()interrupt 1 using 2  //定时器中断函数,此处配置为1ms产生一次中断,对PWM的输出进行控制
{ 
TH0=0XFC;	//给定时器赋初值,定时1ms
TL0=0X18;
time++; 
pwm_val_left++; 
pwm_val_right++; 
pwm_out_left_moto(); 
pwm_out_right_moto(); 
} 
 

  7、延时函数的编写

   关于延时函数,大家只要会用就行,可以用单片机小精灵等辅助软件生成,以下为延时1秒的函数
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_();  
}

  8、主函数内容的编写

   关于主函数的内容,首先要调用定时器中断初始化函数,其次要设置左右电机的速度参数,本文的主要内容是让车动起来,所以主函数内要调用本部分第5步中编写的小车姿态控制函数,对其进行检验,为了便于观察两个状态之间加了5秒的延时,代码如下:
void main()
	{
	Timer0Init();  
	Left_Speed_Ratio=5;   //设置左电机车速为最大车速的50%
	Right_Speed_Ratio=5;	设置右电机车速为最大车速的50%
	while(1)
		{
	run();
	delay1s(); delay1s();  delay1s();  delay1s();  delay1s();
	back();
	delay1s(); delay1s();  delay1s();  delay1s();  delay1s();
    left();
    delay1s(); delay1s();  delay1s();  delay1s();  delay1s();
    right();
    delay1s(); delay1s();  delay1s();  delay1s();  delay1s();
	stop();		
	delay1s(); delay1s();  delay1s();  delay1s();  delay1s();
    rotate();
	delay1s(); delay1s();  delay1s();  delay1s();  delay1s();		
					
		}
}

四、本文例子完整的C文件和H文件代码

   1、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_stp();
} 

 void right(void) //小车右转
{ 
push_val_left =Left_Speed_Ratio;
push_val_right =Right_Speed_Ratio;
Right_moto_stp();
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();
 } 

   2、main.c文件完整代码如下:

#include <car.h>

extern unsigned char Left_Speed_Ratio;
extern unsigned char Right_Speed_Ratio;
unsigned int time=0; 
extern unsigned char pwm_val_left;
extern unsigned char pwm_val_right;

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

	TH0=0XFC;	//给定时器赋初值,定时1ms
	TL0=0X18;	
	ET0=1;//打开定时器0中断允许
	EA=1;//打开总中断
	TR0=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(); 
} 

void main()
	{
	Timer0Init();  
	Left_Speed_Ratio=5;   //设置左电机车速为最大车速的50%
	Right_Speed_Ratio=5;	设置右电机车速为最大车速的50%
	while(1)
		{
	run();
	delay1s(); delay1s();  delay1s();  delay1s();  delay1s();
	back();
	delay1s(); delay1s();  delay1s();  delay1s();  delay1s();
    left();
    delay1s(); delay1s();  delay1s();  delay1s();  delay1s();
    right();
    delay1s(); delay1s();  delay1s();  delay1s();  delay1s();
	stop();		
	delay1s(); delay1s();  delay1s();  delay1s();  delay1s();
    rotate();
	delay1s(); delay1s();  delay1s();  delay1s();  delay1s();		
					
		}
}


   3、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;


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 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);

五、本文例子实物视频演示

       实物视频演示视频链接

    点击上面的链接即可查看本文介绍内容的视频演示,内容依次为(即主函数中程序的内容):前进5秒 、后退5秒、左转5秒、右转5秒、停转5秒、转圈5秒。附视频网址:

    https://www.bilibili.com/video/bv1N5411x7zL

   本文到这里就结束了,本文完整的工程文件我会放在附件里,需要者自取,我放的时候都是免费的,但是过段时间它会自己涨…,欢迎大家继续阅读本系列的后续文章“详细介绍如何从零开始制作51单片机控制的智能小车(二)———超声波模块、漫反射光电管、4路红外传感器的介绍和使用”

   本系列文章的附件已经支持自主下载,附件获取方式如下(推荐通过Gitee免费下载):
https://blog.csdn.net/qq_44339029/article/details/114887405

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

版权声明:本文为CSDN博主「慕羽★」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44339029/article/details/106312542

   我会通过本系列文章,详细介绍如何从零开始用51单片机去实现智能小车的控制,在本系列的上一篇文章中介绍了如何让小车实现自动避障,本文作为本系列的第四篇文章,主要介绍蓝牙模块的使用,如何通过蓝牙进行数据传输,并通过手机向蓝牙模块发送指令,从而达到使用手机控制智能小车的运动状态,本文以汇承HC-08蓝牙模块为例。

本系列文章链接:

-----------------------------------------------------------------------------

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

-----------------------------------------------------------------------------

一、蓝牙模块的选择和基本设定

   1、工作原理简单介绍

在这里插入图片描述

   以上图片来自汇承官方用户手册,HC-08模块用于代替全双工通信时的物理连线。左边的设备向模块发送串口数据,模块的 RXD 端口收到串口数据后,自动将数据以无线电波的方式发送到空中。右边的模块能自动接收到,并从 TXD 还原最初 左边设备所发的串口数据。从右到左也是一样的。

   2、测试模块是否正常工作,以及相关参数的设定

   建议新手朋友们,购买新手套餐,上面左图是测试架,用来实现模块与电脑的连接,右图为本文介绍的HC-08蓝牙模块,只需要把蓝牙模块插到测试架上,通过安卓数据线,就可以与电脑相接了,通过HID 串口助手(汇承官网有HC-08资料包,内部包含相关辅助工具及详细的资料) ,向蓝牙发送AT,若返回OK,则模块正常工作
   接着向蓝牙发送AT+RX 查看模块基本参数 ,主要关注两条信息,蓝牙是主机还是从机模式,设定的波特率是多少,默认出厂时是从机模式,波特率9600,恰好是本文我们需要的数值,若你单片机程序的通信波特率不是9600,可以通过HID 串口助手或者AT指令把蓝牙模块修改成你需要的波特率

   3、蓝牙模块与单片机的连接,与手机的通信

在这里插入图片描述

   在这里需要确定你用的蓝牙模块的工作电压是多少,汇承HC-08有贴片装和带底板装两种,建议新手一定要买带底板的,如上图所示,可以省去很多麻烦。我们使用的51单片机供电电压是5v,而贴片装电压3.3v,不能直接与单片机连接,虽然本文介绍的这款单片机最小系统有3.3v的输出引脚,但是它串口TX RX依然是工作在5v电压下,而贴片装蓝牙模块的TX,RX需要工作在3.3v电压下,蓝牙模块的 RX 端需要串接一个 220Ω~1KΩ的电阻再接到单片机上,TX可以直接连接,对新手来说有点小麻烦,所以大家买的时候,直接买带底板的,支持3.2v-6v,也就是说带底板的可以直接与51单片机连接, 连接时只需要连4根线 Vcc连Vcc,GND连接GND,蓝牙模块TX接单片机 Rx (对于前文介绍的这款的单片机,RX为P30管脚,TX为P31管脚,如下图所示),蓝牙模块RX接单片机 Tx。
   蓝牙模块不能直接与手机连接需要通过HC-COM这个app进行连接,给模块上电后,开启手机蓝牙功能,打开手机上的HC-COM,点击扫描设备,找到蓝牙模块进行连接,连接成功显示connected。

   关于以上的第一部分,因为大家选择的蓝牙模块型号或者商家不同,会有一定的差异,在上文仅就本文需要用的地方进行了简单的介绍,蓝牙模块详细的用法步骤可以去看你购买的蓝牙的用户手册

二、蓝牙模块串口通信程序的编写以及蓝牙遥控的实现

   1、与定时器相关函数的编写

   我们知道我们使用STC89C52单片机,只有两个定时器,在前文我们用定时器0,来实现电机的PWM输出,定时器1,来触发溢出中断,用于超声波模块障碍物的检测,而现在我们用蓝牙进行无线串口的通信,也需要用定时器,怎么办呢? 定时器0肯定是不能动的,只能对定时器1进行复用,我们设定一个变量char Work_Mode=0; 进行工作模式的选择 为0时为手机或者电脑等上位机对小车进行蓝牙遥控 ,为1时小车自动避障模式,在一开始的时候,我们把它设为0,此时对定时器1初始化为蓝牙模块所需的工作模式,在我们需要转化成自动避障模式时,通过手机发送指令让Work_Mode变为1,此时我们把定时器1初始化为超声波避障所需的工作模式,由于此步是在while(1)循环中进行的,但是初始化只需要初始一次,不能一直初始化,所以需要再设定一个变量Work_Mode2,初始值设为0,当执行一次把定时器1初始化为超声波避障所需的工作模式的初始化后,就让该变量为1,这样就可以实现只初始化一次了,相关代码如下:
void Timer1Init()    //定时器1设定为为自动避障模式所需的初始化
{
	TMOD=0X11;//选择为定时器1模式,工作方式1,仅用TR1打开启动。选择为定时器0模式,工作方式1,仅用TR1打开启动
	TH1=0;	
	TL1=0;	
	ET1=1;//打开定时器1中断允许
	EA=1;//打开总中断
	TR1=1;//打开定时器			
}



void Timer1Init2()   //定时器1设定为为蓝牙遥控模式所需的初始化
{
		SCON=0X50;			//设置为工作方式1,8位数据,可变波特率
	  TMOD |=0X20;			//设置计数器工作方式2
	  PCON=0X00;			//波特率不加倍
	  TH1=0XFd;		    //计数器初始值设置,9600  @11.0592MHz
	  TL1=0XFd;
	  TR1=1;					//打开计数器
	  ES = 1;         //开串口中断
    EA = 1;         //开总中断
}
char Work_Mode=0;   //工作模式的选择  为0时,为手机或者电脑等上位机对小车进行蓝牙遥控 ,为1时小车自动避障模式
char Work_Mode2=0;


void main()
	{
	Timer0Init();
	Timer1Init2();	
	Left_Speed_Ratio=5;   //设置左电机车速为最大车速的50%
	Right_Speed_Ratio=5;	设置右电机车速为最大车速的50%
	while(1)
		{
		  if(Work_Mode==1)
			{
			   	ES=0;   //关闭串口中断				  
				 if(Work_Mode2==0)
				 {
					Timer1Init();
                    Work_Mode2=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();          }
		     	 }
					
			}
			else
				
			{
				
			switch(receive_real_data) 
        { 
        
		      case '1': run(); break; 
		      case '2': left(); break; 
		      case '3': right(); break; 
	     	  case '4': back(); break; 
		      case '5': Speed_add(); break; 
	     	  case '6': Speed_reduce(); break; 
			 case '7': stop(); break; 
	        case '8': Work_Mode=1; break; 
	        }
			
				
		    LED=1;
     }	
		}
}

   2、对上面的主函数的解释(蓝牙遥控的实现思路)

   本篇文章的功能是在已经完成自动避障的基础上进行扩展的,也就是让小车可以具备自动避障和蓝牙遥控两种工作模式,所以当Work_Mode=1时的程序,也就是工作在自动避障模式的程序,在对定时器1完成设定为自动避障模式所需的初始化后,跟本系列第三篇文章介绍的是相同的,已详细介绍过了,在这就不介绍了,一开始的时候Work_Mode=0,工作在蓝牙遥控模式,我设定的功能如上面的主函数所示,当手机或者电脑或者其他上位机对单片机(通过蓝牙模块)发送1的时候,让小车前行。发送2的时候 让小车左转,发送3的时候让小车右转,发送4的时候,让小车后退,发送5的时候,让小车加速,发送6的时候让小车减速,发送7的时候,停车,发送8的时候,把Work_Mode置为1,使小车进入自动避障模式,此时蓝牙遥控失去作用,按下单片机上的复位键,程序将重新加载,重新进入蓝牙遥控模式。新增的加速减速函数如下:
void  Speed_add() //加速函数
{
	if(Left_Speed_Ratio<10) //限幅
		{
		Left_Speed_Ratio++;
	  Right_Speed_Ratio++;	
    }
}

void  Speed_reduce() //减速函数
{
	if(Left_Speed_Ratio>0)  //限幅
		{
		Left_Speed_Ratio--;
	  Right_Speed_Ratio--;	
    }
}

   3、串口中断函数的编写

    上面呢我们已经介绍了,通过发送一些设定好了的指令,实现蓝牙遥控,那么怎么把指令发送出去,以及单片机是如何实现接收的呢?,这就要通过串口中断来实现了,下面是官方给的一个串口中断的参考代码,是有问题的,对于HC-COM来说,是不能正常工作的
void Com_Int(void) interrupt 4
{

  uchar receive_data;
	
  EA = 0;
	
  if(RI == 1) //当硬件接收到一个数据时,RI会置位
	{ 		
		RI = 0;
		receive_data = SBUF;//接收到的数据
			
		if(receive_data == '1')	 
		{
				LED =0;//接收到1亮灯
		}
		else
		{
				LED =1; //其他情况灯灭
		}
		
	}
	  SBUF=receive_data;//将接收到的数据放入到发送寄存器
	  while(!TI);			 //等待发送数据完成
	  TI=0;						 //清除发送完成标志位		
		EA = 1;
}
    按照上面的代码,当我们利用手机上的app HC-COM向单片机发送1的的时候,接收的数据receive_data=1,此时呢LED应该等于0,也就是LED会被点亮,但是实际上它只会闪一下,这就说明,我们通过HC-COM发送1的时候,单片机先接收到我们发的1,之后又接收到其他的信息,在上面的程序中,我们让单片机把接收到的信息又通过蓝牙模块发送给手机,利用HC-COM进行显示,如下图所示:
    乍一看,我们发送1的时候,只返回了一个1 ,发送0的时候,只返回了一个0,那单片机接收的其他信息是从何而来的呢?,为啥手机没有显示返回的其他信息呢?对此我进行了大量的实验,最终发现这个app,它一次发送实际上是发送20位,什么意思呢? 当我们输入一个1点击发送的时候,它实际上发送的是1000 0000 0000 0000 0000,也就是说当我们输入的数据的位数不足20位时它会自动补零,这就解释了为什么LED灯不会常亮,只会闪一下,因为在接收完我们发送的1后,它又接收了19个0,把这20个数据返回我们手机上的时候,它这个app把它补得这19个0又以空格的格式进行显示,而不是显示0(有点坑)。
    为了让LED常亮,我们需要输入20个1,如上图所示,也就是不给它补零的机会。这种方法呢用起来十分的不方便,而且本文我们要通过HC-COM发送指令,实现对小车的遥控,也就说需要我们快速的输入指令,这种方法是不行的,怎么办呢?,对大部分人了说不具备修改这个app的能力,那么只能修改单片机的接收和发送函数了,因此我把上面的串口中断函数进行了简单的修改,如下:
void Com_Int(void) interrupt 4
{
    EA = 0;	
  
  if(RI == 1) //当硬件接收到一个数据时,RI会置位
	{ 
   	LED=0;
		RI = 0;
		receive_data = SBUF;//接收到的数据
		if(receive_data!=0)
		receive_real_data=receive_data;
	 
		SBUF=receive_real_data;//将接收到的数据放入到发送寄存器
	  while(!TI);			 //等待发送数据完成
	  TI=0;						 //清除发送完成标志位
		
		
}
    EA = 1;	
}
    只有接收到的数据不为0时,才赋值给新的变量receive_real_data,我们通过判断receive_real_data的值,来控制小车,而不是直接用接收到的值receive _data,同样我们返回到手机的数据,也改为receive_real_data。这样我们只需要发送一位的 1、2、3、4、5、6、7、8、等就可以对小车进行控制,用起来很方便。

三、各文件完整的程序代码

   1、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;  
char Work_Mode=0;   //工作模式的选择  为0时,为手机或者电脑等上位机对小车进行蓝牙遥控 ,为1时小车自动避障模式
char Work_Mode2=0;
unsigned char receive_data=0;
unsigned char receive_real_data=0;
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=0X11;//选择为定时器1模式,工作方式1,仅用TR1打开启动。选择为定时器0模式,工作方式1,仅用TR1打开启动
	TH1=0;	
	TL1=0;	
	ET1=1;//打开定时器1中断允许
	EA=1;//打开总中断
	TR1=1;//打开定时器			
}



void Timer1Init2()
{
		SCON=0X50;			//设置为工作方式1,8位数据,可变波特率
	  TMOD |=0X20;			//设置计数器工作方式2
	  PCON=0X00;			//波特率不加倍
	  TH1=0XFd;		    //计数器初始值设置,9600  @11.0592MHz
	  TL1=0XFd;
	  TR1=1;					//打开计数器
	  ES = 1;         //开串口中断
    EA = 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>=500)   //500ms 启动一次超声波测距
{
	HC_SR04_time=0;
	StartModule();
}
} 

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


void Com_Int(void) interrupt 4
{
    EA = 0;	
  
  if(RI == 1) //当硬件接收到一个数据时,RI会置位
	{ 
   	LED=0;
		RI = 0;
		receive_data = SBUF;//接收到的数据
		if(receive_data!=0)
		receive_real_data=receive_data;
	 
		SBUF=receive_real_data;//将接收到的数据放入到发送寄存器
	  while(!TI);			 //等待发送数据完成
	  TI=0;						 //清除发送完成标志位
		
		
}
    EA = 1;	
}

void main()
	{
	Timer0Init();
	Timer1Init2();	
	Left_Speed_Ratio=5;   //设置左电机车速为最大车速的50%
	Right_Speed_Ratio=5;	设置右电机车速为最大车速的50%
	while(1)
		{
		  if(Work_Mode==1)
			{
			   	ES=0;   //关闭串口中断
				  
				 if(Work_Mode2==0)
				 {
					
					Timer1Init();
           Work_Mode2=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();          }
			
			 }
					
			}
			else
				
			{
				
			switch(receive_real_data) 
        { 
        
		      case '1': run(); break; 
		      case '2': left(); break; 
		      case '3': right(); break; 
	     	  case '4': back(); break; 
		      case '5': Speed_add(); break; 
	     	  case '6': Speed_reduce(); break; 
				  case '7': stop(); break; 
	        case '8': Work_Mode=1; break; 
	        }
			
				
		    LED=1;
     }	
		}
}


   2、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();
 } 


void  Speed_add() //加速函数
{
	if(Left_Speed_Ratio<10)
		{
		Left_Speed_Ratio++;
	  Right_Speed_Ratio++;	
    }
}

void  Speed_reduce() //减速函数
{
	if(Left_Speed_Ratio>0)
		{
		Left_Speed_Ratio--;
	  Right_Speed_Ratio--;	
    }
}


   3、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;
		 }
	}	

   4、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);
void Timer1Init2();
void  Speed_add() ;
void  Speed_reduce();


#endif

四、控制效果的视频演示

      手机蓝牙控制效果视频展示链接

或者直接访问如下网址:https://www.bilibili.com/video/bv1t54y1Q7zF

五、智能小车的一些优化方向及思路

    到这里本系列文章就要告一段落了,本系列的四篇文章介绍了,如何从零开始,慢慢的,一步步的实现了,一个51单片机控制的一个具备自动避障和蓝牙遥控双工作模式的一个小车,本来还想利用本系列第五篇文章介绍一下如何利用上位机对小车的一些变量,比如速度的大小、各个传感器是否检测到障碍物、小车的运动状态,进行监控,并将其绘制成图像,进行分析,但是之后的一段时间我会比较忙,就先不进行详细介绍了,怎么实现呢?有兴趣的可以看一下下面这篇博文,链接如下:

   详细介绍如何从0开始写一个数据通信,将数据从单片机发送到上位机(或者虚拟示波器)进行数据或图像显示,以及常见问题或注意事项解答,本文主要以匿名上位机为例,适合新手和小白

    上面这篇文章介绍的很详细,大家照着去实现上面的功能应该不难,除此之外,在本系列的第三篇文章介绍了,这个智能小车在检测前面的障碍上尚有不足,有兴趣的可以尝试一下4个漫反射光电传感器+超声波模块的避障方案。

    本文介绍的内容,已经实现了手机跟单片机之间双向的数据传输,主要用的是手机向单片机发送控制指令,单片机返回发送的指令,其实除了发送控制指令,还可以发送查询指令,比如说我们向单片机发送一个9,单片机接收到9之后,不对小车的运动进行控制,返回的值也不让它返回9,我们可以让它返回当前小车的运动信息,比如当前的车速、各传感器的工作状态、小车的运动状态等等

    当然还有很多的优化方案及思路,大家可以自行进行思考,和继续优化,当大家感觉研究的差不多之后,就可以研究一下更高级的车了,无论是从硬件上,还是控制算法上都有非常大的提升空间,毕竟本文介绍的只是一个先手入门级的小车模型

附小车照片:

  本文介绍的内容的完整代码的keil文件和蓝牙HC—08模块的资料包(包括HC-COM这个app在内)我会放到附件里,还是那句话,我放的时候都是免费的,但是它会自己涨,需要的可以评论区留言,我直接发给你
   欢迎大家积极交流,本文未经允许谢绝转载

版权声明:本文为CSDN博主「慕羽★」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44339029/article/details/106562519

慕羽★

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐