一、设计要求
(1)基本部分:作品要利用CPU最小系统外至少5部分硬件电路以同时实现功能,包括但不限于LED、KB、BP、LCD、EEPROM、UART;
(2)创优部分:包括但不限于驱动外接模块、实现算法逻辑、自创硬件;
二、作品介绍
该电子万年历使用AT89C52为核心,采用LCD1602液晶屏显示,动态显示时间,同时以蜂鸣器作为闹钟声音,融合超声波测距模块(HC-SRO4)实现对闹钟的关闭;具体功能如下:
1、显示年月日周,显示格式为“年-月-日 周”;
2、显示时分秒,显示格式为“时-分-秒”;
3、通过按键控制来设置时间以及闹钟,并显示闹钟;
4、显示超声波测距的结果,当闹钟响起,只需要手掌靠近单片机10cm以内即可关闭闹钟;
主体功能原理简介:
1、闹钟模块(蜂鸣器):
充当电子万年历中的闹铃,接通电源后振荡器产生音频信号通过电磁线圈,使电磁线圈产生磁场,振动膜片在电磁线圈和磁铁的相互作用下,周期性振动发声;可通过改变频率控制蜂鸣器音调;
2、控制模块(独立按键):
这里使用独立按键来对电子万年历进行控制,按下K3停止计时进入设置状态,K1移动设置点,K2使设置点所在的时间加一,K4使用设置点的时间单位来设置闹钟,比如K1移动到小时,按下K2小时数加一,按下K4会设置一个1小时的闹钟,再次按下K3恢复计时;
独立按键的原理是按下接通电路,松开断开电路,但这个过程的信号波形并不是完美的矩形波,在按下与松开这俩个过程均存在一定的抖动;
3、液晶显示模块(LCD1602):
这里使用LCD1602液晶显示模块来显示时间,闹钟,以及超声波测距的距离,并实时更新变化;
在液晶模块的初始化中需要先设置其显示模式,并且在液晶模块显示字符时光标是自动右移的,无须人工干预;要显示字符时要求先输入显示字符的地址,并且液晶显示屏中第一个字符的地址为40H,若要输入第二行,则需要将需要显示字符的地址位置加80H,即第二行第一个字符应该写入的数据是(40H+80H);
在其初始化过程中,若要写入命令,则需要将使能端RS电平拉低,若是写入数据,则将RS电平设置为高电平;
LCD1602一些关键指令设置:
4、时钟模块(DS1302):
这里时钟模块主要负责计时,按照开始设定的时间,通电后就开始走动;内含31个字节静态RAM;采用串行数据传送方式,数据以BCD的形式传送,因此在程序中要将字符数据转换成BCD码的形式,即加上0X30;在I/O数据输入输出过程中,控制指令输入下一个SCLK时钟的上升沿时,数据被写入DS1302数据输入从0开始;也同样在控制指令字之后的下一个SCLK脉冲的下降沿读取DS1302的数据,读取也是从低位0到高位7;
并且需要注意的是,一次读写操作至少读写俩个字符,一个是控制字节,一个是命令,告诉单片机是读还是写;
5、超声波模块(HC-SRO4):
超声波模块主要负责测试开发板与周围障碍物距离,便于控制闹钟的关闭,而不需要使用按键去控制,这样显得更加方便和智能;
给它提高一个10uS以上得脉冲触发信号该模块内部将发出8个40kHZ周期电平并检测回波;然后通过发送信号与收到回波的时间间隔来计算距离;
C语言代码:
#include"reg52.h"
#include"intrins.h"
#超声波接线说明 Trig ------- P2^1;Echo ------- P2^0;VCC -------- +5v; GND -------- GND
typedef unsigned int u16; //对数据类型进行声明定义
typedef unsigned char u8;
//---秒分时日月周年 最低位读写位;-------//
u8 code READ_RTC_ADDR[7] = {
0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d};
u8 code WRITE_RTC_ADDR[7] = {
0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};
//---DS1302时钟初始化2013年1月1日星期二12点00分00秒。---//
//---存储顺序是秒分时日月周年,存储格式是用BCD码---//
u8 TIME[7] = {
1, 0, 0x18, 0x26, 0x12, 0x00, 0x20};
u8 CLOCK=0;
unsigned char code ASCII[15] = {
'0','1','2','3','4','5','6','7','8','9','.','-','M'};
//PIN口定义
#define LCD1602_DATAPINS P0
sbit LCD1602_E=P2^7;
sbit LCD1602_RW=P2^5;
sbit LCD1602_RS=P2^6;
//---定义ds1302使用的IO口---//
sbit DSIO=P3^4;
sbit RST=P3^5;
sbit SCLK=P3^6;
//超声波接口
sbit Trig = P2^1;
sbit Echo = P2^0;
//管脚接线
sbit beep=P1^5;
sbit K1=P3^1;
sbit K2=P3^0;
sbit K3=P3^2;
sbit K4=P3^3;
u8 SetState,SetPlace;
u16 dis=0;
u8 noise_time0,noise_time1;
unsigned long S=0;
bit flag =0;
u8 disbuff[4] ={
0,0,0,0};
//闹钟标志
u8 rang_flag=0;
u8 r_flag=0;
u8 r1_flag=0;
//延时函数
void delay(u16 i) //当i=1,大约延时10us
{
while(i--);
}
//DS1302写入命令(地址加数据)
void Ds1302Write(u8 addr, u8 dat)
{
u8 n;
RST = 0;
_nop_();
SCLK = 0;//先将SCLK置低电平。
_nop_();
RST = 1; //然后将RST(CE)置高电平。
_nop_();
for (n=0; n<8; n++)//开始传送八位地址命令
{
DSIO = addr & 0x01;//数据从低位开始传送
addr >>= 1;
SCLK = 1;//数据在上升沿时,DS1302读取数据
_nop_();
SCLK = 0;
_nop_();
}
for (n=0; n<8; n++)//写入8位数据
{
DSIO = dat & 0x01;
dat >>= 1;
SCLK = 1;//数据在上升沿时,DS1302读取数据
_nop_();
SCLK = 0;
_nop_();
}
RST = 0;//传送数据结束
_nop_();
}
//DS1302读取一个地址数据
int Ds1302Read(u8 addr)
{
u8 n,dat,dat1;
RST = 0;
_nop_();
SCLK = 0;//先将SCLK置低电平。
_nop_();
RST = 1;//然后将RST(CE)置高电平。
_nop_();
for(n=0; n<8; n++)//开始传送八位地址命令
{
DSIO = addr & 0x01;//数据从低位开始传送
addr >>= 1;
SCLK = 1;//数据在上升沿时,DS1302读取数据
_nop_();
SCLK = 0;//DS1302下降沿时,放置数据
_nop_();
}
_nop_();
for(n=0; n<8; n++)//读取8位数据
{
dat1 = DSIO;//从最低位开始接收
dat = (dat>>1) | (dat1<<7);
SCLK = 1;
_nop_();
SCLK = 0;//DS1302下降沿时,放置数据
_nop_();
}
RST = 0;
_nop_(); //以下为DS1302复位的稳定时间,必须的。
SCLK = 1;
_nop_();
DSIO = 0;
_nop_();
DSIO = 1;
_nop_();
return dat;
}
//初始化DS1302
void Ds1302Init()
{
u8 n;
Ds1302Write(0x8E,0X00); //禁止写保护,就是关闭写保护功能
for (n=0; n<7; n++)//写入7个字节的时钟信号:分秒时日月周年
{
Ds1302Write(WRITE_RTC_ADDR[n],TIME[n]);
}
Ds1302Write(0x8E,0x80); //打开写保护功能
}
//读取时钟信息
void Ds1302ReadTime()
{
u8 n;
for (n=0; n<7; n++)//读取7个字节的时钟信号:分秒时日月周年
{
TIME[n] = Ds1302Read(READ_RTC_ADDR[n]);
}
}
//LCD写入命令
void LcdWriteCom(u8 com) //写入命令
{
LCD1602_E = 0; //使能
LCD1602_RS = 0; //选择发送命令
LCD1602_RW = 0; //选择写入
LCD1602_DATAPINS = com; //放入命令
delay(100); //等待数据稳定
LCD1602_E = 1; //写入时序
delay(500); //保持时间
LCD1602_E = 0;
}
//LCD写入数据
void LcdWriteData(u8 dat) //写入数据
{
LCD1602_E = 0; //使能清零
LCD1602_RS = 1; //选择输入数据
LCD1602_RW = 0; //选择写入
LCD1602_DATAPINS = dat; //写入数据
delay(100);
LCD1602_E = 1; //写入时序
delay(500); //保持时间
LCD1602_E = 0;
}
//LCD屏幕初始化
void LcdInit() //LCD初始化子程序
{
LcdWriteCom(0x38); //开显示
LcdWriteCom(0x0c); //开显示不显示光标
LcdWriteCom(0x06); //写一个指针加1
LcdWriteCom(0x01); //清屏
LcdWriteCom(0x80); //设置数据指针起点
}
//外部中断0
void Int0Configuration()
{
//设置INT0
IT0=1;//跳变沿出发方式(下降沿)
EX0=1;//打开INT0的中断允许。
EA=1;//打开总中断
}
void Int0() interrupt 0
{
delay(1000);
if(K3==0)
{
SetState=~SetState;
SetPlace=0;
Ds1302Init();
}
}
void Conut(void)
{
dis=TH0*256+TL0;
TH0=0;
TL0=0;
S=(dis*1.7)/100; //算出来是CM
if((S>=700)||flag==1) //超出测量范围显示“-”
{
flag=0;
LcdWriteCom(0x89+0x40);
LcdWriteData('-');
LcdWriteData('-');
LcdWriteData('M');
}
else
{
disbuff[0]=S%1000/100;
disbuff[1]=S%1000%100/10;
disbuff[2]=S%1000%10 %10;
LcdWriteCom(0x89+0x40);
LcdWriteData('0'+disbuff[0]/16); //时
LcdWriteData('.'); //时
LcdWriteData('0'+(disbuff[1]&0x0f)); //时
LcdWriteData('0'+(disbuff[2]&0x0f));
LcdWriteData('M');
}
}
//T0中断用来计数器溢出,超过测距范围
void zd0() interrupt 1
{
flag=1; //中断溢出标志
}
void StartModule() //启动模块
{
Trig=1; //启动一次模块
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
Trig=0;
}
//显示函数
void LcdDisplay()
{
LcdWriteCom(0x80+0X40);
LcdWriteData('0'+TIME[2]/16); //时
LcdWriteData('0'+(TIME[2]&0x0f));
LcdWriteData('-');
LcdWriteData('0'+TIME[1]/16); //分
LcdWriteData('0'+(TIME[1]&0x0f));
LcdWriteData('-');
LcdWriteData('0'+TIME[0]/16); //秒
LcdWriteData('0'+(TIME[0]&0x0f));
LcdWriteCom(0x80);
LcdWriteData('2');
LcdWriteData('0');
LcdWriteData('0'+TIME[6]/16); //年
LcdWriteData('0'+(TIME[6]&0x0f));
LcdWriteData('-');
LcdWriteData('0'+TIME[4]/16); //月
LcdWriteData('0'+(TIME[4]&0x0f));
LcdWriteData('-');
LcdWriteData('0'+TIME[3]/16); //日
LcdWriteData('0'+(TIME[3]&0x0f));
LcdWriteCom(0x8D);
LcdWriteData('0'+(TIME[5]&0x07));
LcdWriteCom(0x8b);
LcdWriteData('0'+(CLOCK&0x07));
}
//闹钟函数
void noise()
{
if(TIME[SetPlace]-noise_time1==0)
{
rang_flag=1;
}
if(rang_flag==1) beep=~beep;
if(S<10)
{
CLOCK=0;
r_flag=0;
LcdWriteCom(0x8b);
LcdWriteData('0'+(CLOCK&0x07));
}
}
//主函数
void main(void)
{
u8 i;
TMOD=0x01; //设T0为方式1,GATE=1;
TH0=0;
TL0=0;
ET0=1; //允许T0中断
Int0Configuration();
LcdInit();
Ds1302Init();
while(1)
{
StartModule();
while(!Echo); //当RX为零时等待
TR0=1; //开启计数
while(Echo); //当RX为1计数并等待
TR0=0; //关闭计数
Conut(); //计算
delay(10000);
if(SetState==0)
{
Ds1302ReadTime();
}
else
{
if(K1==0) //检测按键K1是否按下
{
delay(10000); //消除抖动
if(K1==0)
{
SetPlace++;
if(SetPlace>=7)
SetPlace=0;
}
while((i<50)&&(K1==0)) //检测按键是否松开
{
delay(10000);
i++;
}
i=0;
}
if(K2==0) //检测按键K2是否按下
{
delay(10000); //消除抖动
if(K2==0)
{
TIME[SetPlace]++;
if((TIME[SetPlace]&0x0f)>9) //换成BCD码。
{
TIME[SetPlace]=TIME[SetPlace]+6;
}
if((TIME[SetPlace]>=0x60)&&(SetPlace<2)) //分秒只能到59
{
TIME[SetPlace]=0;
}
if((TIME[SetPlace]>=0x24)&&(SetPlace==2)) //小时只能到23
{
TIME[SetPlace]=0;
}
if((TIME[SetPlace]>=0x32)&&(SetPlace==3)) //日只能到31
{
TIME[SetPlace]=0;
}
if((TIME[SetPlace]>=0x13)&&(SetPlace==4)) //月只能到12
{
TIME[SetPlace]=0;
}
if((TIME[SetPlace]>=0x7)&&(SetPlace==5)) //周只能到7
{
TIME[SetPlace]=1;
}
}
while((i<50)&&(K2==0)) //检测按键是否松开
{
delay(10000);
i++;
}
i=0;
}
if(K4==0)
{
r_flag=1;
CLOCK++;
if((CLOCK&0x0f)>9) //换成BCD码。
{
CLOCK=CLOCK+6;
}
noise_time1=TIME[SetPlace]++;
}
}
LcdDisplay();
if(r_flag==1)
{
noise();
}
}
}
作品附图: