超声波模块就是那最常见的HC-SR04
为了实用,不用每次断电后再设置距离,我后来又加了eeprom存储最新距离设定功能,自己做了个小东西又怕lcd焊接麻烦,所以把lcd代码都注释掉了,基本上应该取消注释就是带lcd显示了,保证能用,目前在用没出现问题
//更新保存上次设置距离到eeprom 保存间隔为每5分钟保存一次 下次开启自动启用上次设置数据
/********************************************************************************************************************
HC-SR04 超声波测距模块可提供 2cm-400cm 的非接触式距离感测功能, 测距精度可达高到 3mm;模块包括超声波发射器、接收器与控制电路。
基本工作原理:
(1)采用 IO 口 TRIG 触发测距,给至少 10us 的高电平信号;
(2)模块自动发送 8个40khz 的方波,自动检测是否有信号返回;
(3)有信号返回,通过IO口ECHO 输出一个高电平,高电平持续的时间就是超声波从发射到返回的时间。测试距离=(高电平时间*声速(340M/S))/2;
*********************************************************************************************************************/
#include<reg52.h>
#include<intrins.h>
///////////////////////////晶振12M///////////////////////////
#define uchar unsigned char
#define uint unsigned int
//sbit lcdrs=P3^5; //lcd数据命令选择端 rs为L(ledrs=0)则写命令 H(ledrs=1)则写数据
//sbit lcdrw=P3^6;
//sbit lcden=P3^7; //lcd使能 lcden=1 则写入
sbit TRIG = P2^7; //超声波的 TRIG端 //TRIG为控制端
sbit ECHO = P2^6; //超声波的 ECHO端 //ECHO 为接收端
sbit key1 = P2^2; //增加距离
sbit key2 = P2^1; //减少距离
sbit sbeep = P2^3; //蜂鸣器
#define ReadRomCom 0x01 //内部E2PROM读 写 擦除命令代码
#define PrgRomCom 0x02
#define EraseRomCom 0x03
#define SAVED 1 //内部eprom是否已经保存过的标记
#define NOTSAVED 0
#define WaitTime 0x01
sfr ISP_DATA=0xe2; //ISP寄存器地址
sfr ISP_ADDRH=0xe3;
sfr ISP_ADDRL=0xe4;
sfr ISP_CMD=0xe5;
sfr ISP_TRIG=0xe6;
sfr ISP_CONTR=0xe7;
//uchar DisplayData[3];
uchar alarm_dis=30; //警报距离
//unsigned char code ASCII[15] = {'0','1','2','3','4','5','6','7','8','9','.','-','M'};
uchar flag = 0; //标志定时器是否溢出
uint count=0; //计数来存档
///////////////////////eeprom//////////////////////////////////////
void ISP_IAP_enable() //打开 ISP IAP功能
{
EA=0; //关闭中断功能
ISP_CONTR=ISP_CONTR & 0X18; //0001 1000 ISP/IAP 控制寄存器复位 相当于寄存器指令清零?
ISP_CONTR=ISP_CONTR | WaitTime; //写入硬件延时 WaitTime 0-3(最后两位分别00 01 10 11) 0等待时间最长 3等待时间最短,过短容易出问题 10M系统时钟建议contr|10 20M系统时钟建议 |01
ISP_CONTR=ISP_CONTR | 0x80; //1000 0000 最高位 ISPEN=1 ISPEN:ISP/IAP功能允许位 1:允许编程改变Flash ISP_CONTR=0x60 01(1)(软件复位)0 0000 则可以实现从用户应用程序区软件复位到ISP程序区开始运行程序。
} //ISP_CONTR=0x20 00(1)(软件复位)0 0000 则可以实现从ISP程序区软件复位到用户应用程序区开始运行程序。
void ISP_IAP_disable() //关闭功能
{
ISP_CONTR=ISP_CONTR & 0x7f; //01111111 通过&操作符最高位清零 ISPEN=0
ISP_TRIG=0x00;
EA=1; //结束后继续开启中断功能
}
void ISP_trig() //ISP触发执行程序
{
ISP_IAP_enable();
ISP_TRIG=0x46; //ISP_IAP触发就是连续写入 0x46 0xb9
ISP_TRIG=0xb9;
_nop_(); //一个_nop_();函数延时一个机器周期的时间
}
uchar read_data_byte(uint byte_addr) //读取一个字节已保存数据
{
ISP_ADDRH=(uchar)(byte_addr>>8); //两个字节的地址分成高低两个部分
ISP_ADDRL=(uchar)(byte_addr&0x00ff);
ISP_CMD = ISP_CMD & 0xf8; // 11111000 清空低3位
ISP_CMD = ISP_CMD | ReadRomCom; //写入读命令
ISP_trig();
ISP_IAP_disable();
return(ISP_DATA); //返回读到的数据
}
char check_data(uint byte_addr,uchar value) //测试某个地址是否为自己预设的值来确定是否已经写入过数据
{
if(read_data_byte(byte_addr)==value)
return SAVED;
else
return NOTSAVED;
}
void save_data_byte(uint byte_addr,uchar b_data) //保存一个字节的数据在地址中
{
ISP_ADDRH=(uchar)(byte_addr>>8);
ISP_ADDRL=(uchar)(byte_addr&0x00ff);
ISP_CMD = ISP_CMD & 0xf8;
ISP_CMD = ISP_CMD | PrgRomCom;
ISP_DATA=b_data; //准备写入的数据
ISP_trig();
ISP_IAP_disable();
}
void erase_data(uint sector_addr) //擦除某个地址所属的整个扇区
{
uint iSectorAddr;
iSectorAddr=(sector_addr & 0xfe00); //1111 1110 0000 0000 &2000-2e00 0010 0000 0000 0000-0010 1110 0000 0000 扇区起始地址为左侧7位 通过&操作符清空右边9位得到扇区起始地址
ISP_ADDRH=(uchar)(iSectorAddr>>8); //右移8位得到高8位
ISP_ADDRL=0x00; //低8位为0
ISP_CMD = ISP_CMD & 0xf8;
ISP_CMD = ISP_CMD | EraseRomCom;
ISP_trig();
ISP_IAP_disable();
}
//////////////////////////////////////////////////////////////////////////
/*******************************************************************************
* 函 数 名 : delay
* 函数功能 : 延时函数,i=1时,大约延时10us
*******************************************************************************/
void delay(uint i)
{
while(i--);
}
void delayms(uint z)
{
uint x,y;
for(x=z;x>0;x--)
for(y=125;y>0;y--);
}
void beep() //蜂鸣器发声
{
sbeep=1;
delayms(100);
sbeep=0;
}
void init_time()
{
TMOD = 0x01; //选择定时器0工作 工作方式为方式1
TH0 = 0; //装初值0
TL0 = 0;
TF0 = 0; //中断溢出标志位
ET0 = 1; //开定时器中断
EA = 1; // 开总中断
}
//void write_com(unsigned char com)
//{
// lcdrs=0;
// P0=com;
// delayms(5);
// lcden=1;
// delayms(5);
// lcden=0;
//}
//void lcd_init()
//{
// lcden=0;
// lcdrw=0;
// write_com(0x38);//设置16X2显示,5X7点阵,8位数据接口
// write_com(0x0c);//设置开显示,不显示光标
// write_com(0x06);//写一个字符后地址指针加1
// write_com(0x01);//显示清零,数据指针清零
//}
//void write_data(unsigned char date)
//{
// lcdrs=1;
// P0=date;
// delayms(5);
// lcden=1;
// delayms(5);
// lcden=0;
//}
//void lcd_display_char(unsigned char line,unsigned char addr, unsigned char data1) //写数据 第几行 第几个开始 数据 data1如果是数字0-9 必须用'0'+数字计算ascii值
//{
// write_com(0x80+(line-1)*0x40+addr); //根据行数和add参数定位写数据位置
// write_data(data1); ////////////////输出字符****写完后光标自动下移一个,无需再定位地址****
//}
//void display(unsigned int num) //显示函数
//{
// if(num == -1|| num>999) //当超出范围 显示999
// {
// DisplayData[0] = ASCII[9];
// DisplayData[1] = ASCII[9];
// DisplayData[2] = ASCII[9];
// }
// else //显示 因为测距范围为2-400cm 故3位即可
// {
// DisplayData[0] = ASCII[num / 100]; //取百位
// DisplayData[1] = ASCII[num/10%10];//取十位
// DisplayData[2] = ASCII[num %10];//取个位
// }
// lcd_display_char(1, 1, DisplayData[0]);
// lcd_display_char(1, 2, ASCII[10]); //显示点
// lcd_display_char(1, 3, DisplayData[1]);
// lcd_display_char(1, 4, DisplayData[2]);
// lcd_display_char(1, 5, ASCII[12]); //显示M
//}
//void dis_alarm(uchar dis)
//{
// lcd_display_char(2,4,'0'+dis/100);
// lcd_display_char(2,5,ASCII[10]);
// lcd_display_char(2,6,'0'+dis%100/10);
// lcd_display_char(2,7,'0'+dis%10);
// lcd_display_char(2,8,ASCII[12]);
//}
void keyscan()
{
if(key1==0) //第一个按键
{
delayms(10);
if(key1==0)
{
alarm_dis++;
if(alarm_dis>100) //报警距离不得超过1米
alarm_dis=100;
}
while(!key1); //等待按键弹起
}
if(key2==0) //第二个按键在闪烁时增加 小时、分钟等数字 各数字不联动
{
delayms(10);
if(key2==0) //确认按下按键
{
if(--alarm_dis<5) //报警距离不得小于5cm
alarm_dis=5;
}
while(!key2); //等待按键弹起
}
}
void main()
{
uint distance,out_TH0,out_TL0;
uchar temp_dis;
sbeep=0;
if(check_data(0x2000,66)==SAVED) //已经保存过了
{
alarm_dis=read_data_byte(0x2010); //读取已经保存的数据
}
else //没保存过
{
erase_data(0x2000); //先擦除
save_data_byte(0x2000,66); //再写数据 0x2000位置写标记值66 如果有这个标记说明保存过了
save_data_byte(0x2010,alarm_dis);
beep();
}
// lcd_init();
// lcd_display_char(2,1,'A');
// lcd_display_char(2,2,'L');
// lcd_display_char(2,3,':');
// dis_alarm(alarm_dis);
TRIG = 0; // 先给控制端初始化为0
while(1)
{ /*超声波传感器的使用方法: 控制口发一个10US 以上的高电平,就可以在接收口等待高电平输出。 一有输出就可以开定时器计时,当此口变为低电平时就可以读定时器的,
此时就为此次测距的时间,方可算出距离。如此不断的周期测, 就可以达到移动测量的值了*/
init_time(); //初始化定时器
flag = 0; //置溢出标志位为0
TRIG = 1; //控制口发一个10US 以上的高电平
delay(2);
TRIG = 0;
while(!ECHO); //等待接收端出现高电平
TR0 = 1; //启动计时器 开始计时
while(ECHO); //等待高电平结束
TR0 = 0; //关闭低电平
out_TH0 = TH0; //取定时器的值
out_TL0 = TL0;
out_TH0 <<= 8; //右移8位
distance = out_TH0 | out_TL0; //合并为16位的值
distance /= 54; //11.0592M 12M为/58
if(flag == 1) //如果定时器溢出 则超出超声波测量范围
{
// display(-1);
flag = 0;
distance=100;
}
// else
// display(distance);
if(distance>100)
distance=100;
keyscan();
// dis_alarm(alarm_dis);
if(distance<=alarm_dis&&distance<100&&distance>5) //小于报警距离并且1米以内 报警 beep();
delayms(500);//500ms的周期,这里不是6ms
count++; //读取一次大概500ms 10次5s 120次1m 1200次10m
if(count==120) //1分钟确认一次有没有修改报警距离
{
count=0;
temp_dis=read_data_byte(0x2010); //读取已经保存的数据
if(temp_dis!=alarm_dis) //修改了数据才需要保存
{
erase_data(0x2000); //先擦除
save_data_byte(0x2000,66); //再写数据 0x2000位置写标记值66 如果有这个标记说明保存过了
save_data_byte(0x2010,alarm_dis);
}
}
}
}
void timer0() interrupt 1 //中断函数
{
flag=1; //溢出标志位置1
}
···