51单片机迷宫小车的设计


前言

本次迷宫小车设计,使用C51进行编程,实现小车的功能。本次完成的小车功能可分为两大模块,分别是硬件功能和软件功能。

总共有两部分来写,分别为:

硬件功能上实现了小车的底层驱动,如:红外检测,控制电机使小车进行直行,转弯,自动修正、红外的分时检测,发射与接收。

软件功能上使小车能完成整个迷宫的遍历,并且在遇到死角时,通过回溯,回到最近的一个岔路口,再进行遍历。以从起点出发,又回到起点为遍历结束的标志。遍历结束后,进行冲刺。遍历能够获取到迷宫的全部信息,通过获取到的迷宫信息,建立登高表。在建立完登高表后,从终点开始,找寻最短路径,进行最后的冲刺。

//一步步看来不及的可以直接下拉至文章最下方获取完整代码压缩包资源。

一、硬件模块的结构和功能

1、数码管电路模块。
功能:对小车进行在迷宫中行走的算法调试时使用,可以用来显示小车当前坐标。或者调试红外,显示红外的序号数。该电路模块的数码管显示运用了锁存器,打开所需要置位的数码管IO口号,然后给P0置数点亮数码管即可。赋值依次即可一直显示,不需要一直重复刷新。
2、红外电路模块:
功能:单片机点亮红外LED, LED向其指向方向发射光波,光波在碰撞到障碍物后反射回来,由红外接收头(这里是HS0038)接收信号并将该信号发送给单片机,单片机的I0口接收到信号,就表明前方遇到了障碍物,由此达到了检测障碍物的目的。
当我们使用红外传感器时,要考虑到环境因素的影响,例如太阳光中就包含红外光,尽管我们人眼无法察觉,但如果直接使用简单的红外发射/接收的话,在很多时候传感器就会.受到环境光的干扰,出现各种不可预知的情况。为了解决这种问题,我们采用了红外调制技术来排除干扰,具体做法就是:我们让红外发射管以38kHz的频率“闪动”,而红外接收器则是只有在收到了38kHz频率闪烁的红外光时才会做出反应,这样就有效的规避了大部分的环境光线干扰。迷宫电脑鼠安装有5个传感器组,如果每组传感器都直接接入单片机,每组传感器有一个发送红外信号引脚和一个接收信号引脚,则5组传感器需要占用12个单片机引脚。由于单片机引脚有限,为了节约传感器使用的引脚数,采用74HC138译码器将单片机的三位二进制编码译码为8位控制线,使用其中的5位输出控制线分别控制5个传感器组的分时工作(采用分时检测传感器,主要为了防止多组传感器之间的相互干扰),剩余3位保留,以后可以另作它用。这样使用3个单片机引脚即可控制5组传感器的红外发送工作。
3、(3)电机驱动电路模块:
功能:驱动电机转动,使小车能够实现前进,左转,右转。我们使用的28BYJ-48减速步进电机,该电机属于4相永磁式减速步进电机。该电机的参数为:型号: 28BYJ-4。驱动电压: 12V。传动比(减速比) : 1:16。步距角: 5.625° /16。驱动方式: 4相8拍。.ULN2003就是可以驱动步进电机的一一种芯片,它可以驱动高电压、大电流(50V、500mA 虽然不算太大但相比单片机的电压,还是大多了)的各种负载,16 脚SOP-16或DIP-16封装(我们用SOP-16),除了VCC和GND引脚之外就是7个输入口和7个输出口,提供反相输出,即输入高电平,输出低电平,这就与我们之前的需求相对应了,我们之前所提到的驱动方式是将步进电机的4相的接口按照一定顺序和时间间隔依次置位,那么我们就只需要用到7路反相输出中的4路即可。那么这样一来,我们在使用过程中若想让步进电机正常转动,其实就只要对单片机的P1口进行相等时间间隔的连续赋值就可以了,在设计中,P1口的高四位和低四位分别各控制-台电机,那么我们对P1口依次赋值为0x1,0x33,022,066,0×44,0xc,0x88,0x99,两次赋值的时间间隔大约3-5ms即可,这样两个电机就已经可以同时运行了。
4、下载电路模块:
功能:迷宫电脑鼠采用microUSB接口下载。STC89C52芯片可直接使用串口下载程序,然而现在多数电脑上已经没有串口,所以一般使用USB转串口芯片,目的是把电脑的USB口映射为串口使用。常用的USB转串口芯片有CH340、CP2102、PL2303、FT232等。这里采用的是CH340G芯片完成USB转串口功能。
5、充电电路模块:
功能:实现对电池的充电。用了内部PMOSFET架构,加上防倒充电路,所以不需安外部隔离二极官。热反馈可对充电电流进行自动调节,以便在大功率操作或高环境温度条件下对芯片温度加以限制。充电电压固定于4.2V, 而充电电流可通过-一个电阻器进行外部设置。当充电电流在达到最终浮充电压之后降至设定值1/10时,TP4056将自动终止充电循环。当输入电压(交流适配器或USB电源)被拿掉时,TP4056 自动进入一个低电流状态,将电池漏电流降至2uA以下。TP4056在有电源时也可置于停机模式,从而将供电电流降至55uA。TP4056的其他特点包括电池温度检测、欠压闭锁、自动再充电和两个用于指示充电、结束的LED状态引脚。
6、供电电路模块:
功能:电脑鼠使用单片锂电池供电,锂电池电压在3.0V-4.2V区间,一般为3.7V。单片机的工作电压是5V,步进电机的工作电压是12V。因此直接使用锂电池来供电,驱动能力不够。因此,电池对电路板供电需要加入升压及稳压电路,使用两片外置升压板,分别为3.7V升5V、3.7V 升12V,其中5V输出负责给单片机、传感器、数码管及外部扩展设备供电,12V输出负责给电机供电。如下方结构图为供电电路图,Power 为电池插口,SW开关用于切换电池的工作/充电状态,当SW处于SW-SPDT位置时,电池为系统供电,否则为电池充电。3-5OUT及3-120UT为两个XH2.54连接插座,需从此处来连接外置升压板。

二、软件模块的结构和功能

(1)自动修正。
(2)遍历。
(3)回溯。
(4)登高表建立。
(5)寻找最短路径。

三、硬件代码设计方法

硬件层:五个红外实现分时检测。其中左,前,右三个红外负责寻路,实现小车的遍历功能。左前和右前两个红外实现小车的自动修正。电机采用了四相八拍的方式,通过给P1口赋值,实现步进电机的转动。

四、代码

//一步步看来不及的可以直接下拉至文章最下方获取完整代码压缩包资源。

1.红外中断代码

timer.c文件

#include "timer.h"

void SetTime2(unsigned int xus)
{
	TH2=RCAP2H=(65536-xus)/256;
	TL2=RCAP2L=(65536-xus)%256;
}

void Timer2_Init()
{
	EA=1;//总中断允许
	ET2=1;
	SetTime2(5000);//5ms中断一次
	TR2=1;
}

timer.h文件

#ifndef _TIMER_H_
#define _TIMER_H_

#include "reg52.h"

void SetTime2(unsigned int xus);
void Timer2_Init();
//void SetTime1(unsigned int xus);
//void Timer1_Init()

#endif

中断代码(每隔t就进中断,然后进行红外是否检测到墙壁的判断)

void TIM2_IRQHandler()interrupt 5
{
	static bit ir=0;
	static char n=1;
	TF2=0;
	if(!ir)
	{
		MOUSE_IR_ON(n-1);
	}else{
		if(n==1)
		{
			if(irR1) ir1=0;
			else	ir1=1;
		}else if(n==2)
		{
			if(irR2) ir2=0;
			else	ir2=1;
		}else if(n==3)
		{
			if(irR3) ir3=0;
			else	ir3=1;
		}else if(n==4)
		{
			if(irR4) ir4=0;
			else	ir4=1;
		}else if(n==5)
		{
			if(irR5) ir5=0;
			else	ir5=1;
		}
	}
	if(ir)
	{
		n++;
	}
	if(n>5)
	{
		n=1;
	}
	ir=~ir;
}

2.驱动代码

代码如下(示例):

unsigned char i=0;
unsigned char j=0;
unsigned char a=0;
unsigned char b=0;
extern bit ir2;
extern bit ir5;
extern bit ir1;
extern bit ir4;
extern bit ir3;

//向左转
unsigned char code right[]={0x11,0x99,0x88,0xcc,0x44,0x66,0x22,0x33};//电机同时正转

//向右转
unsigned char code left[]={0x11,0x33,0x22,0x66,0x44,0xcc,0x88,0x99};//电机同时反转

//前进
unsigned char code str[]={0x11,0x93,0x82,0xc6,0x44,0x6c,0x28,0x39};//左正右反

//单独右轮前转
unsigned char code  testir2[]={0x10,0x90,0x80,0xc0,0x40,0x60,0x20,0x30};//左正右反

//单独左轮前转
unsigned char code testir5[]={0x01,0x03,0x02,0x06,0x04,0x0c,0x08,0x09};//左正右反

3.遍历代码

使小车能够完成从起点开始到记录每一格信息的过程。
算法介绍:以小车是否再次回到起点作为while内的循环判断条件,根据右手法则进行寻路,直至将每一个格子的信息都记录完。

void Detect_R()//遍历,//找终点
{
	stackX[top] = 0;
	stackY[top] = 0;//将起点坐标压入栈中
	x = 0;
	y = 0;
	flag=1;
	absolute = 0;
	while (flag/*top != 0*//*x!=m|y!=n*/)//如果没有遍历完成  //如果没有到终点,就一直while循环
	{
		//开始寻路,右手法则
		exz = absolute;
		Delay(100);
		switch (absolute)
		{//可以走的条件是:通路且没有被走过(高四位是否改变
		case 0:
			if (ir4 == 0 && ((Maze[x + 1][y] & 0xf0) == 0xf0))//若右边可以走
			{
				exc = 1;
				exz = (exz + exc) % 4;
				if ((ir1 == 0 && ((Maze[x][y + 1] & 0xf0) == 0xf0)) || (ir3 == 0 && ((Maze[x - 1][y] & 0xf0) == 0xf0)))//若满足前方或左方有一个或一个以上可以走的话,说明为岔路口,压入栈
				{
					push(x, y);//将该坐标入栈
				}
				x += 1;//要先判断是否为岔路口,是否压入栈,再改变x坐标,如果先改变坐标的话,压入栈的坐标就会不是岔路口的坐标
			}
			else//右边不可以走
			{
				if (ir1 == 0 && ((Maze[x][y + 1] & 0xf0) == 0xf0))//根据法则,判断前方能不能走
				{
					exc = 0;
					exz = (exz + exc) % 4;
					if (ir3 == 0 && ((Maze[x - 1][y] & 0xf0) == 0xf0))//若左边也能走,说明为岔路口,入栈
					{
						push(x, y);//将该坐标入栈
					}
					y += 1;
				}
				else//若前方不能走
				{
					if (ir3 == 0 && ((Maze[x - 1][y] & 0xf0) == 0xf0))//根据法则,最后看左方能不能走
					{
						exc = 3;
						exz = (exz + exc) % 4;
						x -= 1;
					}
					else//若三条路都不能走,说明是死角
					{
						deadangle = 1;
					}
				}
			}
			exz = exz;
			break;
		case 1:
			if (ir4 == 0 && ((Maze[x][y - 1] & 0xf0) == 0xf0))//若右边可以走
			{
				exz = 2;//能走右方,赋值2
				exc = 1;
				if ((ir1 == 0 && ((Maze[x + 1][y] & 0xf0) == 0xf0)) || (ir3 == 0 && ((Maze[x][y + 1] & 0xf0) == 0xf0)))//若满足前方或左方有一个或一个以上可以走的话,说明为岔路口,压入栈
				{
					push(x, y);//将该坐标入栈
				}
				y -= 1;
			}
			else//右边不可以走
			{
				if (ir1 == 0 && ((Maze[x + 1][y] & 0xf0) == 0xf0))//根据法则,判断前方能不能走
				{
					exz = 1;//能走前方,赋值0
					exc = 0;
					if (ir3 == 0 && ((Maze[x][y + 1] & 0xf0) == 0xf0))//若左边也能走,说明为岔路口,入栈
					{
						push(x, y);//将该坐标入栈
					}
					x += 1;
				}
				else//若前方不能走
				{
					if (ir3 == 0 && ((Maze[x][y + 1] & 0xf0) == 0xf0))//根据法则,最后看左方能不能走
					{
						exz = 0;//能走左方,赋值3;
						exc = 3;
						y += 1;
					}
					else//若三条路都不能走,说明是死角
					{
						deadangle = 1;
					}
				}
			}
			exz = exz;
			break;
		case 2:
			if (ir4 == 0 && ((Maze[x - 1][y] & 0xf0) == 0xf0))//若右边可以走
			{
				exc = 1;
				exz = (exz + exc) % 4;
				if ((ir1 == 0 && ((Maze[x][y - 1] & 0xf0) == 0xf0)) || (ir3 == 0 && ((Maze[x + 1][y] & 0xf0) == 0xf0)))//若满足前方或左方有一个或一个以上可以走的话,说明为岔路口,压入栈
				{
					push(x, y);//将该坐标入栈
				}
				x -= 1;
			}
			else//右边不可以走
			{
				if (ir1 == 0 && ((Maze[x][y - 1] & 0xf0) == 0xf0))//根据法则,判断前方能不能走
				{
					exc = 0;
					exz = (exz + exc) % 4;
					if (ir3 == 0 && ((Maze[x + 1][y] & 0xf0) == 0xf0))//若左边也能走,说明为岔路口,入栈
					{
						push(x, y);//将该坐标入栈
					}
					y -= 1;
				}
				else//若前方不能走
				{
					if (ir3 == 0 && ((Maze[x + 1][y] & 0xf0) == 0xf0))//根据法则,最后看左方能不能走
					{
						exc = 3;
						exz = (exz + exc) % 4;
						x += 1;
					}
					else//若三条路都不能走,说明是死角
					{
						deadangle = 1;
					}
				}
			}
			exz = exz;
			break;
		case 3:
			if (ir4 == 0 && ((Maze[x][y + 1] & 0xf0) == 0xf0))//若右边可以走
			{
				exc = 1;
				exz = (exz + exc) % 4;
				if ((ir1 == 0 && ((Maze[x - 1][y] & 0xf0) == 0xf0)) || (ir3 == 0 && ((Maze[x][y - 1] & 0xf0) == 0xf0)))//若满足前方或左方有一个或一个以上可以走的话,说明为岔路口,压入栈
				{
					push(x, y);//将该坐标入栈
				}
				y += 1;
			}
			else//右边不可以走
			{
				if (ir1 == 0 && ((Maze[x - 1][y] & 0xf0) == 0xf0))//根据法则,判断前方能不能走
				{
					exc = 0;
					exz = (exz + exc) % 4;
					if (ir3 == 0 && ((Maze[x][y - 1] & 0xf0) == 0xf0))//若左边也能走,说明为岔路口,入栈
					{
						push(x, y);//将该坐标入栈
					}
					x -= 1;
				}
				else//若前方不能走
				{
					if (ir3 == 0 && ((Maze[x][y - 1] & 0xf0) == 0xf0))//根据法则,最后看左方能不能走
					{
						exc = 3;
						exz = (exz + exc) % 4;
						y -= 1;
					}
					else//若三条路都不能走,说明是死角
					{
						deadangle = 1;
					}
				}
			}
			exz = exz;
			break;
		}
		//以上,寻完路,确定了要往哪走(相对方向)
		//以下,分为是死角,不是死角的操作
		if (deadangle == 0)//若不是死角
		{
			if (exc == 0)
			{
				GOSTR();
				Delay(300);
				Get_information();
			}
			if (exc == 1)
			{
				RIGHT_();
				Delay(300);
				GOSTR();
				Delay(300);
				Get_information();
			}
			if (exc == 3)
			{
				LEFT_();
				Delay(300);
				GOSTR();
				Delay(300);
				Get_information();
			}
		}
		else//如果是死角
		{
			back();//进行回溯的操作
		}
	}
	Maze[0][0]=Maze[0][0]&0xfe;
}

4.回溯代码

代码如下(示例):

void back()//回溯
{
	unsigned char back_enter = 0xff;//用来获取进入该格的方向(绝对方向),回溯的时候就按这个方向往回走一格
	while (x != stackX[top] || y != stackY[top])
	{
		back_enter = Maze[x][y] & 0xf0;//获取当时是如何进入该格子
		if (absolute == 0)
		{
			switch (back_enter)//绝对方向
			{
			case 0xe0:GOSTR();Delay(500); absolute = 0; break;
			case 0xd0:RIGHT_();/* Delay(500);*/GOSTR(); Delay(500);absolute = 1; break;
			case 0xb0:RIGHT_();/* Delay(500);*/RIGHT_(); Delay(500);GOSTR();Delay(500); absolute = 2; break;
			case 0x70:LEFT_();/*Delay(500);*/ GOSTR(); Delay(500);absolute = 3; break;
			}
		}
		else
		{
			if (absolute == 1)
			{
				switch (back_enter)//绝对方向
				{
				case 0xe0:LEFT_();/*Delay(500);*/ GOSTR(); Delay(500);absolute = 0; break;
				case 0xd0:GOSTR();Delay(500); absolute = 1; break;
				case 0xb0:RIGHT_();/*Delay(500); */GOSTR();Delay(500); absolute = 2; break;
				case 0x70:RIGHT_();/*Delay(500);*/ RIGHT_();Delay(500); GOSTR();Delay(500); absolute = 3; break;
				}
			}
			else
			{
				if (absolute == 2)
				{
					switch (back_enter)//绝对方向
					{
					case 0xe0:RIGHT_();/* Delay(500);*/RIGHT_(); Delay(500);GOSTR();Delay(500); absolute = 0; break;
					case 0xd0:LEFT_();/*Delay(500);*/ GOSTR(); Delay(500);absolute = 1; break;
					case 0xb0:GOSTR();Delay(500); absolute = 2; break;
					case 0x70:RIGHT_(); /*Delay(500);*/GOSTR();Delay(500); absolute = 3; break;
					}
				}
				else
				{
					switch (back_enter)//绝对方向
					{
					case 0xe0:RIGHT_();Delay(500); GOSTR(); Delay(500);absolute = 0; break;
					case 0xd0:LEFT_(); Delay(500);LEFT_();Delay(500); GOSTR(); Delay(500);absolute = 1; break;
					case 0xb0:LEFT_(); Delay(500);GOSTR(); Delay(500);absolute = 2; break;
					case 0x70: GOSTR();Delay(500); absolute = 3; break;
					}
				}
			}
		}
		switch (back_enter)
		{
		case 0xe0:y++; break;//从方向0进入的
		case 0xd0:x++; break;//从方向1进入的
		case 0xb0:y--; break;//从方向2进入的
		case 0x70:x--; break;//从方向3进入的
		}
		Delay(5);
	}
	top--;
	Delay(1000);
	deadangle = 0;
	if((x==0)&&(y==0))
	{
		top=0;
		flag=0;
		Maze[0][0]=Maze[0][0]&0xfe;
	}
}

5.登高表建立

代码如下(示例):

void search_1()
{
	x=0;
	y=0;
	Quene_Init();
	Record_Init();
	quenex1[tail] = 0;
	queney1[tail] = 0;
	count=1;
	record[0][0] = count;
	tail++;
  while (record[7][7] == 80)
	{
		count++;
		if (count % 2 == 0)//count为偶数时,前一个数的坐标存在第一组队列中
		{
			exc = tail;
			exz = head1;
			Quene_set1();
			Quenexy1_Init();	
		if((quenex2[head2]==0)&&(queney2[head2]==1))
		{
			LEFT_();
			Delay(500);
		}	
  	}
		else
		{
			exc = tailt;
			exz = head2;
			Quene_set2();
			Quenexy2_Init();	
		}
	}
	search_2();//寻找一条道路.
	Delay(1000);
	//找完路后,要小车按照规定的路走就可以了。
	the_end();//开始行进
}

6.完整代码:


总结

`懒orz不想写了,刚刚发现上传代码压缩包的话会要收费才能下载,所以我就上传云盘了,大家需要自取哦。

https://pan.baidu.com/s/1_nNr1WHtwsMDapWNQ2Xmeg
提取码:vodc

下载解压后点击template.uvproj工程文件即可,有哪里不清楚的可以私聊或者评论区问哦

物联沃分享整理
物联沃-IOTWORD物联网 » 设计51单片机迷宫小车

发表评论