具有测距、温度补充、实时时钟、记忆、阈值警报、串口数据发送等等功能,通过LCD1602显示,按键进行相关操作。
LCD1602显示共有五个界面,按键一用于切换显示界面。
此界面测距为连续测距模式,LCD1602不间断刷新测量距离和温度,一旦测量距离小于设置的阈值,单片机将会发出警报。此界面按下按键4可进入与上位机连接状态,当上位机发送1至单片机后,mcu立刻将测量数据发送至上位机。
此界面为阈值设置界面,按键二用于改变设置位(上限还是下限),设置位会闪烁显示,以此提示。按键三四用于加减。
此界面测距为单次测距模式,按键二按下测量一次,按键三按下则将当前测量距离和测量时间(如23:59:45时测量距离为45cm)保存到EEPROM里面,最多可以记录十次,第十一次将会覆盖第一次数据。
此界面测距为回放模式,按下按键二三切换回放序号,LCD1602会显示在界面三时记录的距离和测量时间。
上电后,LCD1602会显示DS1302的初始时间设置,此时需要通过按键二三四进行时间设置。设置完成后,按下按键一,系统进入界面一,开始工作。
核心代码main.c如下,项目工程见超声波测距系统
各模块原理和代码见我的CSDN博客专栏单片机
#include "main.h"
typedef unsigned char u8;
//P2用于选择P0输出通道,P0输出数据
#define outputp0(y,x) P0=x,P2&=0x1f,P2=y,P2&=0x1f;
//按键值,按键值缓存值
u8 kbdnum=0,kbdtemp=0;
//显示页面值,距离阈值设置选择位,保存次数值,时间设置选择位,测量数据保存起始地址;
u8 show_page=0,distance_set_flag=0,save_times=1,set_time_flag=0,save_distance_addr=0x04;
//测量阈值上限,测量阈值下限,LCD闪烁位
int distance_h=350,distance_l=6,LCD_showflag=0;
//测量阈值上下限数组,十六进制,便于进行保存到EEPROM或从EEPROM读取的操作
u8 distance_limit[4]={0};
//测量温度
float temper=0;
//时间保存中间量
u8 time_s_temp=0;
//主函数延时函数,t ms
void main_Delay1ms(int t) //@11.0592MHz
{
unsigned char data i, j;
while(t--)
{
_nop_();
_nop_();
_nop_();
i = 11;
j = 190;
do
{
while (--j);
} while (--i);
}
}
/*
* @brief 按键值读取工作函数
* @param
* @reval
* @note:
*/
void work_kbd()
{
if(kbdnum) //如果有按键按下
{
if(kbdnum==13) //key1按下
{
show_page++;
show_page%=4; //切换显示界面
if(show_page==0) //如果为界面0,连续测距模式
{
TR0=0;
temper=rd_temper();
TR0=1;
work_ultra(); //界面0
LCD_ShowString(1,1,"Distance: . CM"); //显示相应界面
LCD_ShowString(2,1,"Temper: . C ");
}
else if(show_page==1) //如果为界面1,测量阈值设置模式
{
outputp0(0xa0,0x00); //关闭蜂鸣器,防止在界面0警报状态下进入界面1导致蜂鸣器一直响
distance_set_flag=0;LCD_showflag=0; //复位距离阈值设置位和LCD闪烁位
LCD_ShowString(1,1,"High: CM "); //显示相应界面
LCD_ShowString(2,1,"Low: CM ");
}
else if(show_page==2) //如果为界面2,逐次测量模式
{
save_distance_addr=4;save_times=1; //复位数据保存起始地址和保存次数
LCD_ShowString(1,1,"Distance: . CM"); //显示相应界面
LCD_ShowString(2,1,"Time: - - ");
}
else if(show_page==3) //如果为界面3,回放模式
{
save_distance_addr=4;save_times=1; //复位数据保存起始地址和保存次数
LCD_ShowString(1,1,"Save_Dis: . CM"); //显示相应界面
LCD_ShowString(2,1,"Time: - - ");
TR0=0;
at24c02_read_multi(read_time,save_distance_addr,3);
distance=at24c02_read(save_distance_addr+3)*256+at24c02_read(save_distance_addr+4);
TR0=1; //将第一次保存数据读取出来
}
}
else if(kbdnum==14) //key2按下
{
if(show_page==1) //如果是界面1,测量阈值设置模式
distance_set_flag=!distance_set_flag; //改变距离阈值设置位,用于切换选择设置上限或下限
else if(show_page==2) //如果是界面2,逐次测量模式
{
TR0=0;
temper=rd_temper();
TR0=1;
work_ultra(); //执行一次距离测量
}
else if(show_page==3) //如果是界面3,回放模式
{
if(save_distance_addr<=48) //用于选取下一个数据保存地址
{
save_distance_addr+=5;
save_times++;
}
else if(save_distance_addr>48) //超出数据保存最大地址,则返回最小地址
{
save_distance_addr=4;
save_times=1;
}
TR0=0; //将保存数据读取出来
at24c02_read_multi(read_time,save_distance_addr,3);
distance=at24c02_read(save_distance_addr+3)*256+at24c02_read(save_distance_addr+4);
TR0=1;
}
}
else if(kbdnum==15) //key3按下
{
if(show_page==1) //如果是界面1,测量阈值设置模式
{
if(distance_set_flag==0) //如果是设置距离上限
{
distance_h++; //加一
if(distance_h>350) //防超过最大值
distance_h--;
}
else if(distance_set_flag==1) //如果是设置距离下限
{
distance_l++; //加一
if(distance_l>=distance_h) //防超过距离上限
distance_l--;
}
}
else if(show_page==2) //如果是界面2,逐次测量模式,将测量数据进行保存
{
TR0=0;
time_s_temp=read_time[0];read_time[0]=read_time[2]; //用于DS1302读取时间顺序为秒分时,而人习惯为时分秒,因此将秒与时交换位置
read_time[2]=time_s_temp;
at24c02_write_multi_page(read_time,save_distance_addr,3); //保存测量时间
save_distance_addr+=3;
at24c02_write(save_distance_addr++,distance/256); //保存测量距离
at24c02_delay5ms();
at24c02_write(save_distance_addr++,distance%256); //保存测量距离
TR0=1;
if(save_distance_addr>=54) //数据读取地址达到最大,返回最小地址
save_distance_addr=4;
save_times++; //读取的数据对应的保存次序
if(save_times>10) //如果次序超过最大次数10,则返回第一次
save_times=1;
}
else if(show_page==3) //如果是界面3,回放模式
{
if(save_distance_addr>=9)
{
save_distance_addr-=5; //读取上一次保存数据
save_times--;
}
else if(save_distance_addr<9) //如果读取数据地址达到最小地址,则返回最大地址
{
save_distance_addr=49;
save_times=10;
}
TR0=0; //将数据读取出来
at24c02_read_multi(read_time,save_distance_addr,3);
distance=at24c02_read(save_distance_addr+3)*256+at24c02_read(save_distance_addr+4);
TR0=1;
}
}
else if(kbdnum==16) //key4按下
{
if(show_page==0) //如果是界面0,连续测距模式
{
LCD_ShowString(1,1,"Please connect "); //等待与上位机连接
LCD_ShowString(2,1," to computer! ");
while(receivebit==0); //一旦连接
for(save_distance_addr=4;save_distance_addr<=53;save_distance_addr++)
{
sendbit(at24c02_read(save_distance_addr)); //将保存数据全部发送至上位机
}
LCD_ShowString(1,1,"Datas sent OK! "); //提示发送完成
LCD_ShowString(2,1," ");
main_Delay1ms(3000);
LCD_ShowString(1,1,"Distance: . CM"); //回到连续测距显示
LCD_ShowString(2,1,"Temper: . C ");
receivebit=0; //复位接收值
}
else if(show_page==1) //如果是界面1,测量阈值设置模式
{
if(distance_set_flag==0) //如果是设置距离上限
{
distance_h--; //减一
if(distance_h<=distance_l) //防小于距离下限
distance_h++;
}
else if(distance_set_flag==1) //如果是设置距离下限
{
distance_l--; //减一
if(distance_l<6) //防止小于最小值
distance_l++;
}
}
else if(show_page==2) //如果是界面2,逐次测距模式
{
show_page=0; //切换回界面0,连续测距模式
LCD_ShowString(1,1,"Distance: . CM"); //显示相应界面
LCD_ShowString(2,1,"Temper: . C ");
}
}
if((kbdnum==15||kbdnum==16)&&show_page==1) //如果在测量阈值设置模式改变了距离上限或下限的值,则将新值进行保存
{
distance_limit[0]=distance_h/256; //将距离上下限转换为16进制
distance_limit[1]=distance_h%256;
distance_limit[2]=distance_l/256;
distance_limit[3]=distance_l%256;
TR0=0;
at24c02_write_multi(distance_limit,0x00,4); //一次写入多个数据
TR0=1;
}
kbdnum=0; //清零按键值
}
}
/*
* @brief 界面0,连续测距模式
* @param
* @reval
* @note:
*/
void page_0()
{
TR0=0;
temper=rd_temper(); //测量温度
TR0=1;
if(tultra>=60) //每60ms测量一次
{
work_ultra(); //测量距离
}
if(distance>=(distance_l*10)&&distance<=(distance_h*10)) //如果在测量范围之内
{
LCD_ShowString(1,1,"distance:"); //正常显示测量距离
LCD_ShowNum(1,10,distance/10,3);
LCD_ShowNum(1,14,distance%10,1);
LCD_ShowChar(1,13,'.');
LCD_ShowString(1,15,"CM");
outputp0(0xa0,0x00); //蜂鸣器关
}
else if(distance<(distance_l*10)||distance>(distance_h*10)) //如果超出测量范围
{
LCD_ShowString(1,1,"Error! "); //测量距离显示“ERROR!”
outputp0(0xa0,0x40); //蜂鸣器开
}
LCD_ShowSignedNum(2,8,temper,3);
LCD_ShowNum(2,13,(int)(temper*100)%100,2); //显示温度
}
/*
* @brief 界面1,测量阈值设置模式
* @param
* @reval
* @note:
*/
void page_1()
{
if(LCD_showflag==0) //如果LCD闪烁位为0,正常显示
{
LCD_ShowNum(1,6,distance_h,3);
LCD_ShowNum(2,5,distance_l,3);
}
else if(LCD_showflag==1) //如果LCD闪烁位为1,则清空相应数据显示位置
{
if(distance_set_flag==0)LCD_ShowString(1,6," ");
else if(distance_set_flag==1)LCD_ShowString(2,5," ");
}
}
/*
* @brief 界面2,逐次测距模式
* @param
* @reval
* @note:
*/
void page_2()
{
TR0=0;
DS1302_read(DS1302_write_addr); //读取时间
TR0=1;
LCD_ShowNum(1,10,distance/10,3); //显示相应数据
LCD_ShowNum(1,14,distance%10,1);
LCD_ShowNum(2,6,read_time[2]/16*10+read_time[2]%16,2);
LCD_ShowNum(2,9,read_time[1]/16*10+read_time[1]%16,2);
LCD_ShowNum(2,12,read_time[0]/16*10+read_time[0]%16,2);
LCD_ShowNum(2,15,save_times,2);
}
/*
* @brief 界面3,回放模式
* @param
* @reval
* @note:
*/
void page_3()
{
LCD_ShowNum(1,10,distance/10,3); //显示相应数据
LCD_ShowNum(1,14,distance%10,1);
LCD_ShowNum(2,6,read_time[0]/16*10+read_time[0]%16,2); //由于数据以16进制保存,因此需要转换为10进制
LCD_ShowNum(2,9,read_time[1]/16*10+read_time[1]%16,2);
LCD_ShowNum(2,12,read_time[2]/16*10+read_time[2]%16,2);
LCD_ShowNum(2,15,save_times,2);
}
/*
* @brief 界面显示
* @param
* @reval
* @note:
*/
void page_show()
{
switch(show_page)
{
case 0:page_0();break;
case 1:page_1();break;
case 2:page_2();break;
case 3:page_3();break;
}
}
/*
* @brief 上电时间初始化
* @param
* @reval
* @note:
*/
void set_time()
{
outputp0(0x80,0xff); //关闭8位LED
outputp0(0xa0,0x00); //关闭蜂鸣器继电器等
LCD_Init(); //LCD1602初始化
rd_temper(); //由于DS18B20上电默认+85°,为了防止温度有极小时间错误显示,因此上电后即让DS18B20测量一次温度
main_Delay1ms(1000); //系统上电后延时一秒,确保各单位准备好工作
Timer0_Init(); //定时器0初始化
LCD_ShowString(1,1,"Please set time!"); //设置时间提示
LCD_ShowString(2,1,"Time: - - ");
while(kbdnum!=13) //如果key1按下,则退出时间设置
{
if(kbdnum==14) //如果key2按下
{
set_time_flag++;set_time_flag%=3; //时间设置选择位
kbdnum=0; //清零按键值
}
else if(kbdnum==15) //如果key3按下
{
DS1302_write_time[set_time_flag]++; //相应数据加一
if(DS1302_write_time[set_time_flag]==24&&set_time_flag==2) //防止秒分时超出各自上限
DS1302_write_time[set_time_flag]=0;
else if(DS1302_write_time[set_time_flag]==60)
DS1302_write_time[set_time_flag]=0;
kbdnum=0; //清零按键值
}
else if(kbdnum==16) //如果key4按下
{
if(DS1302_write_time[set_time_flag]>=1)
DS1302_write_time[set_time_flag]--; //相应数据减一
else
DS1302_write_time[set_time_flag]=59*(set_time_flag<2)+23*(set_time_flag==2); //防止秒分时小于各自下限
kbdnum=0; //清零按键值
}
if(LCD_showflag==0) //如果LCD闪烁位为0,正常显示
{
LCD_ShowNum(2,6,DS1302_write_time[2],2);
LCD_ShowNum(2,9,DS1302_write_time[1],2);
LCD_ShowNum(2,12,DS1302_write_time[0],2);
}
else if(LCD_showflag==1) //如果LCD闪烁位为1,则清空相应数据显示位置
{
switch(set_time_flag)
{
case 0:LCD_ShowString(2,12," ");break;
case 1:LCD_ShowString(2,9," ");break;
case 2:LCD_ShowString(2,6," ");break;
}
}
}
if(kbdnum==13) //key1按下
{
TR0=0;
for(set_time_flag=0;set_time_flag<3;set_time_flag++) //将设置时间从10进制转换为16进制
DS1302_write_time[set_time_flag]=DS1302_write_time[set_time_flag]/10*16+DS1302_write_time[set_time_flag]%10;
DS1302_set(DS1302_write_addr,DS1302_write_time); //设置DS1302时间
TR0=1;
kbdnum=0; //清零按键值
}
}
/*
* @brief 系统初始化
* @param
* @reval
* @note:
*/
void init()
{
Timer1_Init(); //定时器0初始化
Uart1Init(); //串口1初始化
LCD_ShowString(1,1,"distance: CM "); //上电默认界面0,显示相应界面
LCD_ShowString(2,1,"Temper: . C ");
//将保存的距离上下限读取并设置为距离阈值
at24c02_read_multi(distance_limit,0x00,4);
distance_h=distance_limit[0]*256+distance_limit[1];
distance_l=distance_limit[2]*256+distance_limit[3];
distance=distance_l*10+1; //防止未开始测量即报警
}
void main()
{
set_time(); //设置时间
init(); //系统初始化
receivebit=0; //清零接收位
while(1)
{
work_kbd(); //按键读取
page_show(); //界面显示
}
}
void Timer0_Isr(void) interrupt 1
{
static u8 kbd_longflag=0; //矩阵键盘长按标志位,1长按0短按
static int kbd_short_t=0,kbd_t=0,kbd_long_t=0,tlcd=0;
TL0 = 0x66; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
kbd_short_t++; //矩阵键盘按键短按时间计数
if(kbd_short_t>=(500*kbd_longflag+25*(!kbd_longflag))) //短按每25ms检测一次按键
{
if(!(colu1&colu2&colu3&colu4)) //长按检测,每25ms检测矩阵键盘按键是否仍在按下,如果是
{
kbd_t++; //矩阵键盘按键按下时间计数(以25ms为单位)
if(kbd_t>=80) //如果持续按下2s
{
kbd_t=80; //防止溢出
kbd_longflag=1; //切换为长按模式
}
}
else if(colu1&colu2&colu3&colu4) //长按检测,每25ms检测按键是否仍在按下,如果不是
{
kbd_t=0; //清零按键按下时间计数
kbd_longflag=0; //切换为短按模式
}
kbd_short_t=0; //清零短按时间计数
if(kbd_longflag==0) //如果是短按模式
{
kbdtemp=kbd_send(0);
if(kbdtemp) //按键以短按模式检测
kbdnum=kbdtemp;
}
}
kbd_long_t++; //按键长按时间计数
if(kbd_long_t>=500) //长按时每500ms检测一次按键
{
kbd_long_t=0; //清零按键长按时间计数
if(kbd_longflag==1) //如果是长按模式
{
if(kbd_send(1)) //按键以长按模式检测
kbdnum=kbd_send(1);
}
}
tultra++; //超声波工作时间间隔计数
tlcd++; //LCD闪烁位时间计数
if(tlcd>=500)
{
tlcd=0;
LCD_showflag=!LCD_showflag; //实现LCD1602闪烁显示
}
}