前言
关于蓝桥杯比赛时会提供的资料前几篇都有提到,这里就不在赘述了,只放一个下载链接:
单片机资源数据包_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下载,或者点击下边的蓝字进行下载/查看
对于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();
}
}