蓝桥杯电子类单片机提升一——超声波测距

前言

单片机资源数据包_2023

一、超声波测距原理

二、超声波测距的应用

1.超声波的发射

2.单片机知识补充:定时器

3.超声波的接收与计时

4.距离的计算

1)定时器1为16位自动重载+1T@11.0592MHz

2)定时器1为16位自动重载+12T@11.0592MHz

3)定时器1为16位自动重载+1T@12MHz

4)定时器1为16位自动重载+12T@11.0592MHz

总结

5.代码完善

三、完整代码展示


前言

关于蓝桥杯比赛时会提供的资料前几篇都有提到,这里就不在赘述了,只放一个下载链接:

单片机资源数据包_2023

除了基础部分的按键、LED灯,数码管扫描,还有温度传感器,AD/DA转化,EEPROM存储器,RTC之外,还有三个模块考试的时候可能会考,分别是超声波,NE555和串口。近几年的题也是越来越难,这三个模块也逐渐出现在了省赛的舞台上(当然如果进国赛了,这几个模块就都可能考了)。提升篇主要针对这三个模块进行介绍。

由于这三个模块比赛时不会提供底层代码,所以许多都需要咱们自己来完成,所以不同人写的代码,差异性可能会更大。此外这些代码会涉及到单片机运行的底层知识,关于单片机基础部分的内容,提升篇也会尽可能介绍一部分(当然如果你不会也没关系,文章会教你如何用stc生成或者查数据手册,就算不知道原理,小背一背也是能自己实现的)

一、超声波测距原理

超声波测距的原理很简单,就是超声波发送装置先发送一段超声波,然后开始计时,一直等到接收端接收到反射回来的超声波,然后停止计时。记录的这段时间T,就是超声波碰到物体在返回的时间,超声波的速度一般取340m/s,那么物体距离超声波测距装置的距离S=V*T/2.很好理解,这里就不在画图进行过多介绍了。

发射

接收

这两张分别是在原理图上的超声波发射和接收电路,这里也不过多介绍。

在使用超声波之前,我们需要正确配置跳线帽。 也就是将J2 2x3的排针的A1和A相连,B1与B相连,这样超声波发送端就与P10相连了,接收端就与P11相连了。换言之,超声波的数据发送引脚TX=P10;数据接收引脚RX=P11。

二、超声波测距的应用

第一章已经介绍了超声波测距的原理,无非是发送,计时,接收信号+数据处理(把时间信息转化为距离信息),接下来咱们一一实现着三个步骤

1.超声波的发射

超声波的发送,就是需要我们通过TX引脚发送8个40KHz的超声波信号(具体为什么测距需要用40KHz的超声波,可以自行百度)。

我们知道,40KHz的信号,一个周期就是25微秒,那半个周期就约13us。理论上我们只需让TX置为1,然后延时13us,然后在让TX置为0,再延时13us就完成了一个40KHz信号的发射。放在一个for循环内,即可完成发送8个40KHz的信号。

但是,实际在用的时候,延时13us往往不够,难以读取到有效的数据(当然,读者可以自行测试,也就修改一个延时函数的功夫),这里把延时改为14us,如果读取的数据仍旧存在问题,如突然跳到一个特别大的值,然后又突然回归正常等,我们可以适当增加延时。对应到代码上的话,我们可以通过微调Delay14us内的i的值,来增大延时,下面是发送TX与RX的引脚定义与发送超声波的代码:

sbit TX=P1^0;

sbit RX=P1^1;

void Delay14us(void) //@11.0592MHz

{

unsigned char data i;

nop();

i = 45;//11.0592MHz的单片机,使用stc生成14us延时时,i等于36,这里稍微增大了i让延时增大

while (--i);

}

void send_wave(void)//发送超声波的函数

{

unsigned char i=0;

for(i=0;i<8;i++)//发送8个40KHz的超声波

{

TX=1;Delay14us();

TX=0;Delay14us();

}

}

2.单片机知识补充:定时器

在进行第二步:计时之前,需要先"科普"一下51系列单片机的基础知识。

单片机的数据手册可以通过新版的STC-ISP下载,或者点击下边的蓝字进行下载/查看

STC15用户手册(点击查看或下载)

对于15单片机的定时器,也有许多,这里只挑对于超声波有用的寄存器讲。

这有一段STC-ISP生成的代码,系统频率为11.0592MHz,定时器1,定时长度为1ms,定时器模式为16位自动重,时钟频率为1T的定时器1的初始化代码

void Timer1_Init(void) //1毫秒@11.0592MHz

{

AUXR |= 0x40; //定时器时钟1T模式

TMOD &= 0x0F; //设置定时器模式

TL1 = 0xCD; //设置定时初始值

TH1 = 0xD4; //设置定时初始值

TF1 = 0; //清除TF1标志

TR1 = 1; //定时器1开始计时

}

首先先解释一下为什么是定时器1,而不是定时器0呢,因为定时器0我们在比赛时百分百要用到,而其他定时器,或者说需不需要额外的定时器则是需要根据题目要求来判定,所以我们定时器0里面有我们需要运行的代码,我们可以开一个单独的定时器1来完成超声波,注意,超声波检查需要一个单独的定时器。

然后我们再看代码,第一行AUXR是辅助寄存器,用于控制时钟是1T还是12T,这个1T就是指不分频或者说1倍分频,12T就是指12分频。数据手册上也有,但是我们可以直接用STC-ISP来生成1T或者12T的代码,就不在赘述了。

对于第二行TMOD寄存器是用来控制定时器模式的,定时器主要有定时和计数功能。

定时就是我们经常使用的模式,计数器模式我们在NE555会用到。而在超声波测距时,我们恰恰是需要定时器去计时。

定时模式又有16位自动重载,16位自动重载,8位自动重载等模式(注意,不是所有8051系列单片机都有这些模式的),我们一般用16位自动重载,自动重载的意思就是,定时器溢出之后(定时器中断),定时器的TL和TH值会自动重新变为初始值,而不需要代码重置。

TL1 = 0xCD; //设置定时初始值

TH1 = 0xD4; //设置定时初始值

这两个就是刚才提到的TL和TH,这里是定时器1的TL和TH,所以是TL1和TH1,两个都是8位二进制数,合在一块刚好是16位二进制,其中,TH1是高八位,TL1是低八位,定时器开始计数时,他们就会自增1,直到溢出。根据它俩的初始值不同,我们可以设置出不同时间长度的定时器。

当定时器的TH1和TL1溢出时,TF1就会被置为1,同时引起定时器中断(当然前提是允许中断)。

当TR1=1时,定时器开始计时,TR1=0时,定时器停止计时。

综上,如果我们想用定时器1计时的话,就不能一开始就开启定时器计时,需要等我们发送完超声波之后再开始计时,也就是令TR1 =1,并且需要先将 TL1和TH1置为0,这样方便我们计算。等接收到信号之后,我们需要将TR1置为0停止计时,此时TH1和TL1存的数据,就是从开始计时到结束计时这一段的时间了,当然这个时间的单位可不是s呦。除此之外,如果长时间没有收到返回的超声波信号,一直等到TH1和TL1从0累加到溢出,也就是使TF1=1了,那说明没有检测到有效信号,我们也需要进行单独处理。

3.超声波的接收与计时

当发送完超声波之后,我们立即开始计时。当超声波接收端RX检测到信号时,会被置为1,也就是当检测到RX为1时,说明检测到超声波返回的信号了,我们也立刻停止计时,这段时间ultar_t就是超声波从发生到反射回来的时间,也就是超声波走了一个来回的时间。接下来,我们用代码来实现这个功能:

send_wave();

TR1=1;//开始计时

while((RX==1)&&(TF1==0));//计时的条件:没有收到反射回来的超声波,且定时器没有溢出

TR1=0;//停止计时

if(TF1==1)//定时器溢出,说明在一段时间内没收到反射回的超声波(代码/硬件/真的是因为距离太远都有可能导致没收到信号)

{

TF1=0;

ultar_t=0;//这个值可以随意设置,总之就是如果没有接收到反射回来的超声波时,要如何显示的值。。这里是接收到反射回来的超声波,就显示0

}

else//跳出那个while循环,不是因为TF1=1了,就是因为RX=0了,这个else的情况就是RX=0;也可以写成else if(RX==0);

{

ultar_t=TH1;

ultar_t<<=8;

ultar_t|=TL1;

}

注意这里对TF1=1的情况也进行了处理,也就是没检测到信号的情况。

4.距离的计算

我们已经求得了时间ultar_t,但是这个时间的单位不是S也不是ms等,需要先进行转化成真正的时间才能用于距离的计算,关于距离的计算又涉及到了单片机的知识,这里根据单片机设置的频率不同和定时器时钟模式不同分开介绍。

1)定时器1为16位自动重载+1T@11.0592MHz

void Timer1_Init(void) //@11.0592MHz

{

AUXR |= 0x40; //定时器时钟1T模式

TMOD &= 0x0F; //设置定时器模式

TL1 = 0x00; //设置定时初始值

TH1 = 0x00; //设置定时初始值

TF1 = 0; //清除TF1标志

//TR1 = 1; //定时器1开始计时

}

实际的时间=ultar_t*1/11059200(秒)

距离remote=ultar_t*1/11059200*340*100/2(厘米)

2)定时器1为16位自动重载+12T@11.0592MHz

void Timer1_Init(void) //1微秒@11.0592MHz

{

AUXR |= 0xBF; //定时器时钟1T模式

TMOD &= 0x0F; //设置定时器模式

TL1 = 0x00; //设置定时初始值

TH1 = 0x00; //设置定时初始值

TF1 = 0; //清除TF1标志

//TR1 = 1; //定时器1开始计时

}

实际的时间=ultar_t*1/11059200*12(秒)

距离remote=ultar_t*1/11059200*12*340*100/2(厘米)

3)定时器1为16位自动重载+1T@12MHz

初始化函数与1)相同

实际的时间=ultar_t*1/12000000*12(秒)

距离remote=ultar_t*1/12000000*12*340*100/2(厘米)

4)定时器1为16位自动重载+12T@11.0592MHz

初始化函数与2)相同

实际的时间=ultar_t*1/12000000*12(秒)

距离remote=ultar_t*1/12000000*12*340*100/2(厘米)

总结

实际的距离等于ultar_t*1/主频*分频倍数*340*100/2(厘米)

对于1T和12T,12T就是十二分频,简单点说就是定时器记录的最长时间变为1T的十二倍,同时精度也变为原来的12倍,但是其实1T模式下,最远检测的距离等于65535*1/11059200*340*100/2≈100cm,100cm已经很远了,我用这个超声波时,测到70就已经很难了,所以一般情况下1T模式,而且不对超过100cm进行处理,也足够了。

对于主频率,比赛的时候,有时候会要求使用12MHz的主频(近几年来,可谓是大概率会这样要求),我们需要通过isp更改单片机内部的设置。关于主频的设置,我们只需在下载程序之前调一下IRC频率,下载程序之后会把单片机内部的设置一同更改了。我们也可以使用"检测MCU"选项,检测当前单片机内部的一些设置。

5.代码完善

与LED灯闪烁类似,我们并不期望无时无刻地发送与接收,而是每隔一定时间发送接受一次,再加上刚才关于距离计算的代码,以及每轮之后将相关数据清空,我们可以总结出超声波测距过程的代码:

if(count_ultar==1)

{

count_ultar=0;

send_wave();

TR1=1;//开始计时

while((RX==1)&&(TF1==0));//计时的条件:没有收到反射回来的超声波,且定时器没有溢出

TR1=0;//停止计时

if(TF1==1)//定时器溢出,说明在一段时间内没收到反射回的超声波(代码/硬件/真的是因为距离太远都有可能导致没收到信号)

{

TF1=0;

ultar_t=0;//这个值可以随意设置,总之就是如果没有接收到反射回来的超声波时,要如何显示的值。。这里是接收到反射回来的超声波,就显示0

}

else//跳出那个while循环,不是因为TF1=1了,就是因为RX=0了,这个else的情况就是RX=0;也可以写成else if(RX==0);

{

ultar_t=TH1;

ultar_t<<=8;

ultar_t|=TL1;

}

remote=(unsigned int)(ultar_t*0.0015);//定时器1是1T。注意需要修改Timer1_Init()内的内容

//remote=(unsigned int)(ultar_t*0.0184);//定时器1是12T

ultar_t=0;

TH1=0;

TL1=0;

}

Delay100ms();

}

count_ultar为0时,200ms之后会被定时器置为1,也就是每200ms发送接收一次超声波。

unsigned int ultar_t=0;

bit count_ultar=0;

void Timer0_Isr(void) interrupt 1

{

if(count_ultar==0)

{

if(++count_200>200)

{

count_ultar=1;

count_200=0;

}

}

三、完整代码展示

现在,完成以下功能对刚才介绍的功能进行演示:

1.单片机主频率设置为11.0592MHz

2.通过超声波读取距离信息,并显示到前三位数码管上,单位:CM

main.c

cpp 复制代码
#include <stc15.h>
#include <intrins.h>


code unsigned char Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0xFF
};

#define NIXIE_CHECK()	P2|=0xC0;P2&=0xDF;P2&=0x1F;
#define NIXIE_ON()	P2|=0xE0;P2&=0xFF;P2&=0x1F;

void Timer0_Init(void);		//1毫秒@11.0592MHz
void Delay100ms(void);	//@11.0592MHz
void send_wave(void);
void Timer1_Init(void);		//1毫秒@11.0592MHz

unsigned char location=0;
unsigned char Nixie_num[]={10,10,10,10,10,10,10,10};
bit count_ultar=0;//时间标志位
unsigned int remote=0;//距离信息
unsigned int ultar_t=0;//超声波往返的时间信息,注意必须用uint,不能用uchar
sbit TX=P1^0;//定义超声波的TX
sbit RX=P1^1;//定义超声波的RX
void main()
{
	Timer0_Init();
	Timer1_Init();
	EA=1;
	Delay100ms();
	while(1)
	{
		if(count_ultar==1)
		{
			count_ultar=0;
			send_wave();
			TR1=1;//开始计时
			while((RX==1)&&(TF1==0));//计时的条件:没有收到反射回来的超声波,且定时器没有溢出
			TR1=0;//停止计时
			if(TF1==1)//定时器溢出,说明在一段时间内没收到反射回的超声波(代码/硬件/真的是因为距离太远都有可能导致没收到信号)
			{
				TF1=0;
				ultar_t=0;//这个值可以随意设置,总之就是如果没有接收到反射回来的超声波时,要如何显示的值。。这里是接收到反射回来的超声波,就显示0
			}
			else//跳出那个while循环,不是因为TF1=1了,就是因为RX=0了,这个else的情况就是RX=0;也可以写成else if(RX==0);
			{
				ultar_t=TH1;
				ultar_t<<=8;
				ultar_t|=TL1;
			}
			remote=(unsigned int)(ultar_t*0.0015);//定时器1是1T。注意需要修改Timer1_Init()内的内容
			//remote=(unsigned int)(ultar_t*0.0184);//定时器1是12T
			ultar_t=0;
			TH1=0;
			TL1=0;
		}
		//数码管显示距离信息
		Nixie_num[0]=remote/100%10;
		Nixie_num[1]=remote/10%10;
		Nixie_num[2]=remote/1%10;

		Delay100ms();
	}
}

unsigned int count_200=0;
void Timer0_Isr(void) interrupt 1
{
	P0=0x01<<location;NIXIE_CHECK();
	P0=Seg_Table[Nixie_num[location]];NIXIE_ON();
	
	if(++location==8)
		location=0;
	
	if(count_ultar==0)//如果标志位为0
	{
		if(++count_200>200)//200ms后,将时间标志位置为1
		{
			count_ultar=1;
			count_200=0;
		}
	}
}

void Timer0_Init(void)		//1毫秒@11.0592MHz
{
	AUXR |= 0x80;			//定时器时钟1T模式
	TMOD &= 0xF0;			//设置定时器模式
	TL0 = 0xCD;				//设置定时初始值
	TH0 = 0xD4;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
	ET0 = 1;				//使能定时器0中断
}
void Timer1_Init(void)		//1毫秒@11.0592MHz
{
	AUXR |= 0x40;			//定时器时钟1T模式
	//AUXR &= 0xBF;			//定时器时钟12T模式
	TMOD &= 0x0F;			//设置定时器模式
	TL1 = 0x00;				//设置定时初始值
	TH1 = 0x00;				//设置定时初始值
	TF1 = 0;					//清除TF1标志
	//TR1 = 1;				//定时器1开始计时
}

void Delay100ms(void)	//@11.0592MHz
{
	unsigned char data i, j, k;

	_nop_();
	_nop_();
	i = 5;
	j = 52;
	k = 195;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}
void Delay14us(void)	//@11.0592MHz
{
	unsigned char data i;

	_nop_();
	i = 45;//11.0592MHz的单片机,使用stc生成14us延时时,i等于36,这里稍微增大了i让延时增大
	while (--i);
}


void send_wave(void)//发送超声波的函数
{
	unsigned char i=0;
	for(i=0;i<8;i++)//发送8个40KHz的超声波
	{
		TX=1;Delay14us();
		TX=0;Delay14us();
	}	
}
相关推荐
南城花随雪。1 分钟前
单片机:实现FFT快速傅里叶变换算法(附带源码)
单片机·嵌入式硬件·算法
逝灮15 分钟前
【蓝桥杯——物联网设计与开发】基础模块8 - RTC
stm32·单片机·嵌入式硬件·mcu·物联网·蓝桥杯·rtc
互联网杂货铺1 小时前
Postman接口测试:全局变量/接口关联/加密/解密
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·postman
weixin_452600696 小时前
串行时钟保持芯片D1380/D1381,低功耗工作方式自带秒、分、时、日、日期、月、年的串行时钟保持芯片,每个月多少天以及闰年能自动调节
科技·单片机·嵌入式硬件·时钟·白色家电电源·微机串行时钟
古木20197 小时前
前端面试宝典
前端·面试·职场和发展
森旺电子10 小时前
51单片机仿真摇号抽奖机源程序 12864液晶显示
单片机·嵌入式硬件·51单片机
不过四级不改名67712 小时前
蓝桥杯嵌入式备赛教程(1、led,2、lcd,3、key)
stm32·嵌入式硬件·蓝桥杯
TT哇12 小时前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
Rorsion12 小时前
各种电机原理介绍
单片机·嵌入式硬件
chengooooooo14 小时前
代码随想录训练营第二十七天| 贪心理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和
算法·leetcode·职场和发展