本次编程实验以IAP15F2K61S2为单片机主控芯片,其编程使用与STC15F2K60S2完全相同,头文件为STC15F2K60S2.H
。若用于51系列单片机,以reg52.h
为头文件,则读者需将程序中可能涉及的定时器初始化程序和LED亮灭程序和数码管显示程序,根据自身所用单片机原理图和手册进行修改。
超声波测距,即使用超声波测量物体间的距离。超声波从测量点发射,接触到被测物体后,产生回波,测量超声波从发出到接收到回波的时间,乘以声速,再除以2,就可以得到测量距离。由于超声波指向性强,能量消耗缓慢,在介质中传播的距离较远,因而超声波经常用于距离的测量,并且在测量精度方面能达到工业实用的要求,在移动机器人研制上得到了广泛的应用。
常用的超声波模块是4针的HC-SR04,四个引脚分别为:+VCC,Trig(触发端),Echo(接收端),GND。如下图所示:
将HC-SR04模块与单片机连接后,给予Trig端高电平触发测距,高电平信号至少保持10us;之后模块自动发送 8 个 40khz 的方波,自动检测是否有信号返回;方波发射后,Echo端自动置高电平,一旦检测到有回波返回,Echo端自动置低电平。从而,Echo端高电平持续的时间就是超声波从发射到返回的时间。测量距离=(高电平时间*声速(340M/S))/2。时序图如下:
为了防止发射信号对回波信号产生干扰,每次测量时间间隔应大于60ms。
使用定时器1来计算Echo端高电平时间,代码如下:
ultra.h
#ifndef __ULTRA_H
#define __ULTRA_H
#include
#include
sbit trig=P1^0;
sbit echo=P1^1;
void Timer1_Init(void); //定时器1初始化
void ultra_start(); //发射信号
unsigned int ultra_measure(); //Echo端高电平时间计算函数
#endif
ultra.c
#include "ultra.h"
void Delay10us(void) //@11.0592MHz
{
unsigned char data i;
_nop_();
i = 25;
while (--i);
}
void Timer1_Init(void) //@11.0592MHz
{
AUXR &= 0xBF; //定时器时钟12T模式
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10; //设置定时器模式
TL1 = 0x00; //设置定时初始值
TH1 = 0x00; //设置定时初始值
TF1 = 0; //清除TF1标志
TR1 = 0; //定时器1停止计时
}
void ultra_start() //发射超声波
{
trig=0;
trig=1;
Delay10us();
trig=0;
}
/*
* @brief Echo端高电平时间测量函数
* @param
* @reval ultra_time:Echo端高电平时间持续时间
* @note:
*/
unsigned int ultra_measure()
{
unsigned int ultra_time=0;
TL1 = 0x00; //设置定时初始值
TH1 = 0x00; //设置定时初始值
ultra_start(); //发射超声波
while(!echo); //等待Echo端置高电平
TR1=1; //定时器1开始计时
while(echo&&(!TF1)); //如果检测到回波或定时器溢出
TR1=0; //停止计时
TF1=0;
ultra_time=TH1*256+TL1; //TH1为高8位,TL1为低8位,因此需将TH1左移8位,即乘以256
return ultra_time; //返回Echo端高电平时间持续时间
}
注意到ultra_measure()
里面存在while()
循环,一旦循环时间过长,使用delay()
函数来显示数码管的方式就容易出现闪烁,因此采取定时器扫描数码管的方式。详情见定时器扫描8位数码管(含滚动显示)
smg.h
#ifndef __SMG_H
#define __SMG_H
#include
#define outputp0(y,x) P0=x,P2&=0x1f,P2|=y,P2&=0x1f;//P2高三位用于选择数据输出通道。P0用于输出数据
extern unsigned char Seg_sy[]; //声明外部变量,使main.c文件可以调用
void showbit(unsigned char pos,dat,dot);
void showloop();
#endif
smg.c
#include "smg.h"
unsigned char Seg_sy[]={11,11,11,11,11,11,11,11}; //数码管段选索引数组
unsigned char code Seg_Table[]={ //共阳段码表
0xc0,
0xf9,
0xa4,
0xb0,
0x99,
0x92,
0x82,
0xf8,
0x80,
0x90,
0xbf, //"-"
0xff //8段全灭
};
/*
* @brief 1位数码管显示函数
* @param pos:位选;dat:显示数字;dot:小数点选择位,1有0无
* @reval
* @note:
*/
void showbit(unsigned char pos,dat,dot)
{
outputp0(0xc0,0x01<<pos); //位选
outputp0(0xe0,Seg_Table[11]); //段选,这两行用于消影
outputp0(0xc0,0x01<<pos); //位选
outputp0(0xe0,Seg_Table[dat]+0x80*dot); //段选
}
/*
* @brief 8位数码管显示函数
* @param
* @reval
* @note:
*/
void showloop()
{
static unsigned char sy=0; //sy用于选择位选和位选对应的段选
if(sy==2)
showbit(sy,Seg_sy[sy],1); //数码管显示1位小数,第二位数码管显示测量距离个位数,第三位开始为小数,因此第二位数码管显示小数点
else
showbit(sy,Seg_sy[sy],0);
sy++; //每执行一次,sy加一,从而下次执行时刷新下一位数码管
sy%=8; //8位数码管,因此sy不超过8
}
定时器0用于扫描数码管。
timer0.h
#ifndef __TIMER0_H
#define __TIMER0_H
#include
void Timer0_Init(void); //2毫秒@11.0592MHz
#endif
timer0.c
#include "timer0.h"
void Timer0_Init(void) //2毫秒@11.0592MHz,用于扫描数码管
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0xCD; //设置定时初始值
TH0 = 0xF8; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; //使能定时器0中断
EA=1;
}
main.c
#include
#include "math.h"
#include "smg.h"
#include "ultra.h"
#include "timer0.h"
typedef unsigned char u8;
long int distance=0; //测量距离
int tultra=0; //测量周期
void test() //测试程序
{
if(tultra>=50) //每100ms测量一次
{
u8 i=0;
tultra=0; //清零
distance=ultra_measure()*0.17; //ultra_measure()*0.017为初始距离(CM),为了便于显示1位小数,将其乘以10
if(distance>=40&&distance<=3500) //如果在测量范围之内(4cm-350cm)
{
for(i=0;i<4;i++)
Seg_sy[i]=(distance/(long int)pow(10,3-i))%10; //显示测量距离,1位小数
}
else if(distance<40||distance>3500) //如果超出测量范围
{
for(i=0;i<4;i++)
Seg_sy[i]=10; //显示"---.-"
}
}
}
void main()
{
outputp0(0x80,0xff); //关闭8个LED
outputp0(0xa0,0x00); //关闭蜂鸣器继电器等
Timer0_Init();
Timer1_Init();
while(1)
{
test();
}
}
void Timer0_Isr(void) interrupt 1
{
TL0 = 0xCD; //设置定时初始值
TH0 = 0xF8; //设置定时初始值
showloop(); //每隔2ms扫描一次数码管
tultra++; //测量周期+1
}
实际测量距离时,由于硬件性能和测试场地限制等,测量距离容易出现波动,需要稳定测量一段时间。当然,也可以通过短时间内多次测量取平均值的方法来减小误差,请读者自行思考。
有任何问题和补充,欢迎评论区交流。