(1) 时钟芯片DS1302的工作原理:
DS1302在每次进行读、写程序前都必须初始化,先把SCLK端置“0”,接着把RST端置“1”,最后才给予SCLK脉冲;读/写时序图如图1.1所示。为DS1302的控制字,此控制字的位7必须置1,若为0则不能对DS1302进行读写数据。对于位6,若对程序进行读/写时RAM=1,对时间进行读/写时,CK=0。位1至位5指操作单元的地址。位0是读/写操作位,进行读操作时,该位为1;该位为0则表示进行的是写操作。控制字节总是从最低位开始输入/输出的。表-2为DS1302的日历、时间寄存器内容:“CH”是时钟暂停标志位,当该位为1时,时钟振荡器停止,DS1302处于低功耗状态;当该位为0时,时钟开始运行。“WP”是写保护位,在任何的对时钟和RAM的写操作之前,WP必须为0。当“WP”为1时,写保护位防止对任一寄存器的写操作。
(2) DS1302的控制字节
DS1302的控制字如表-1所示。控制字节的高有效位(位7)必须是逻辑1,如果它为0,则不能把数据写入DS1302中,位6如果0,则表示存取日历时钟数据,为1表示存取RAM数据;位5至位1指示操作单元的地址;最低有效位(位0)如为0表示要进行写操作,为1表示进行读操作,控制字节总是从最低位开始输出
表-1 DS1302的控制字格式
RAM RD
1 A4 A3 A2 A1 A0
/ CK /WR
(3) 数据输入输出(I/O)
在控制指令字输入后的下一个SCLK时钟的上升沿时,数据被写入DS1302,数据输入从低位即位0开始。同样,在紧跟8位的控制指令字后的下一个SCLK脉冲的下降沿读出DS1302的数据,读出数据时从低位0位到高位7。如下图1所示
图1.1 DS1302读/写时序图
(4) DS1302的寄存器
DS1302有12个寄存器,其中有7个寄存器与日历、时钟相关,存放的数据位为BCD码形式,其日历、时间寄存器及其控制字见表-2。
表-2 DS1302的日历、时间寄存器
写寄存器 | 读寄存器 | Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
80H | 81H | CH | 10秒 | 秒 | |||||
82H | 83H | 10分 | 分 | ||||||
84H | 85H | 12/ | 0 | 10 | 时 | 时 | |||
/PM | |||||||||
86H | 87H | 0 | 0 | 10日 | 日 | ||||
88H | H | 0 | 0 | 0 | 10月 | 月 | |||
8AH | 8BH | 0 | 0 | 0 | 0 | 0 | 星期 | ||
8CH | 8DH | 10 年 | 年 | ||||||
8EH | 8FH | WP | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
此外,DS1302 还有年份寄存器、控制寄存器、充电寄存器、时钟突发寄存器及与RAM相关的寄存器等。时钟突发寄存器可一次性顺序读写除充电寄存器外的所有寄存器内容。 DS1302与RAM相关的寄存器分为两类:一类是单个RAM单元,共31个,每个单元组态为一个8位的字节,其命令控制字为C0H~FDH,其中奇数为读操作,偶数为写操作;另一类为突发方式下的RAM寄存器,此方式下可一次性读写所有的RAM的31个字节,命令控制字为FEH(写)、FFH(读)。
2 方案论证与设计
2.1控制器部分方案设计
硬件控制电路主要用了ATC52芯片处理器、1602LCD显示器、DS1302实时时钟、DB18B20温度传感器。根据各自芯片的功能互相连接成电子万年历的控制电路。软件控制程序主要有主控程序、电子万年历的时间控制程序、时间显示及温度显示程序等组成。设计框图如图2.1所示
图2.1 框图设计
2.2 显示部分的方案论证
方案一:采用8段数码管虽经济实惠,但操作比液晶显示来说略显繁琐。
方案二:液晶显示方式。液晶显示效果出众,可以运用菜单项来方便操作,比较简单,所以,最后选择液晶显示方案。显示电路图如图2.2所示。
图2.2 液晶显示电路
2.3 实时时钟电路设计
图2.3是 DS1302与单片机的连接,其中Vcc1为后备电源,Vcc2为主电源。在主电源关闭的情况下,也能保持时钟的连续运行。DS1302由Vcc1或Vcc2两者中的较大者供电。当Vcc2大于Vcc1+0.2V时,Vcc2给DS1302供电。当Vcc2小于Vcc1时,DS1302由Vcc1供电。X1和X2是振荡源,外接32.768KHz晶振。
图2.3 DS1302时钟电路
2.4 温度采集模块设计
如图2.4所示,采用数字式温度传感器DS18B20,它是数字式温度传感器,具有测量精度高,电路连接简单特点,此类传感器仅需要一条数据线进行数据传输,使用P3.7与DS18B20的I/O口连接加一个上拉电阻,Vcc接电源,Vss接地。
图2.4 DS18B20温度采集模块
2.5 功能按钮设计
当按钮被按下时,该按钮对应的I/O口被拉为低电平,松开时按钮对应的I/O口由内部的上拉电阻将该I/O拉为高电平,如图2.5所示:
图2.5 键盘电路设计
2.6 总体电路图
3 软件设计流程
3.1 系统总流程图
系统总体流程图如图3.1所示,系统流程图设计分析如下:首系统初始化,系统运行,当有设置键按下时进入时间修改模式,无按键按下时读取时间温度等数据送入显示器显示。在时间修改模式下设置时间完成后再将时间送入显示器显示。
图3.1 系统流程图
3.2 温度程序流程图
温度读取程序流程图如图3.2所示。流程图分析:开始进入初始化DS18B20,就是通过主机拉低单线产生复位脉冲然后释放总线,如果有应答就发起ROM命令,当成功执行操作命令后,就使用温度转换,当温度转换完成后又初始化DS18B20是否有应答脉冲,若有就发起读暂存器和CRC命令,同时读出第一第二字节即温度数据。
图3.2 温度读取程序流程图
3.3 DS1302时钟程序流程图
时钟流程图如图3.3所示,流程图分析:DS1302开始计时时,首先进行初始化,当有中断信号时,读取时钟芯片的时间数据送入液晶显示。这时若有设置键按下,进行时间修改,完成后将时间数据送入1302芯片,若没有按键按下,直接送入EPROM中,送入液晶显示。
图3.3 时钟程序流程图
DS1302时钟部分子程序
void write_byte(uchar dat)//写一个字节
{
ACC=dat;
RST=1;
for(a=8;a>0;a--)
{
IO=ACC0;
SCLK=0; //产生上升沿写入数据,从低位写入
SCLK=1;
ACC=ACC>>1;
}
}
uchar read_byte()//读一个字节
{
RST=1;
for(a=8;a>0;a--)
{
ACC7=IO;
SCLK=1; //产生下降沿输出数据,先输出低位,保存到ACC中
SCLK=0;
ACC=ACC>>1;
}
return (ACC);
}
void write_1302(uchar add,uchar dat)//向1302芯片写函数,指定写入地址,数据
{
RST=0;
SCLK=0;
RST=1;
write_byte(add);
write_byte(dat);
SCLK=1;
RST=0;
}
uchar read_1302(uchar add)//从1302读数据函数,指定读取数据来源地址
{
uchar temp;
RST=0;
SCLK=0;
RST=1;
write_byte(add);
temp=read_byte();
SCLK=1;
RST=0;
return(temp);
}
uchar BCD_Decimal(uchar bcd)//BCD码转十进制函数,输入BCD,返回十进制
{
uchar Decimal;
Decimal=bcd>>4;
return(Decimal=Decimal*10+(bcd&=0x0F));
}
void ds1302_init() //1302芯片初始化子函数(2012-06-18,12:00:00,week7)
{
RST=0;
SCLK=0;
write_1302(0x8e,0x00); //允许写,禁止写保护
write_1302(0x80,0x00); //向DS1302内写秒寄存器80H写入初始秒数据00
write_1302(0x82,0x00);//向DS1302内写分寄存器82H写入初始分数据00
write_1302(0x84,0x12);//向DS1302内写小时寄存器84H写入初始小时数据12
write_1302(0x8a,0x07);//向DS1302内写周寄存器8aH写入初始周数据4
write_1302(0x86,0x17);//向DS1302内写日期寄存器86H写入初始日期数据18
write_1302(0x88,0x06);//向DS1302内写月份寄存器88H写入初始月份数据06
write_1302(0x8c,0x12);//向DS1302内写年份寄存器8cH写入初始年份数据12
write_1302(0x8e,0x80); //打开写保护
}
3.4 LCD显示程序流程图
显示流程图如图3.4所示,流程图分析如下:首先对1602显示屏进行初始化(初始化大约持续10ms),然后检查忙信号,若BF=0,则获得显示RAM地址,写入相应的数据显示。若BF=1,则代表模块正在进行内部操作,不接受任何外部指令和数据,直到BF=0为止。
图3.4 LCD显示程序流程图
4 万年历的仿真与调试结果
上电后的显示
秒调节
功能按键,自上而下功能依次为调节按键、加按键、减按键
5 心得体会
本次设计是我们遇到过的较大的设计,所以遇到的问题也比较的多,尤其是以前没有接触过如此复杂的硬件电路以及软件编程,在软、硬件设计和调试中遇到了不少的困难,在同学的帮助才逐一克服了难题,学习到了不少的专业知识。
在整个设计过程之前,我已经在网上找了相关方面的资料,万事开始难,一开始不知道从哪里下手。后来慢慢学会分析系统,将系统模块化,各个模块可以在软件或者硬件上实现。在确保各个模块的硬件电路和与之相搭配的程序能够正常工作后在把它们组成一个系统。在今后的日子里,我会进一步加强自己的动手能力,丰富自己的知识面。
参考文献
[1]李朝青.单片机原理及接口技术[M],北京:北京航天航空大学出版社,2005
[2]李广弟.单片机基础[M],北京:北京航空航天大学出版社,2000
[3]万光毅.单片机实验与实践教程[M],北京:北京航空航天大学出版社,2003
[4]唐亚平、李移伦.单片机原理实训与学习指导[M],长沙:中南大学出版社,2006
[5]刘军.单片机原理与接口技术[M],华东理工大学出版社,2006
[6]谢自美.电子线路设计、实验、测试[M],武汉:华中理工大学出版社,2000
附件
万年历源程序
#include #include"DS18B20_3.H" #define uint unsigned int #define uchar unsigned char uchar a,miao,shi,fen,ri,yue,nian,week,flag,key1n,temp; //flag用于读取头文件中的温度值,和显示温度值 #define yh 0x80 //LCD第一行的初始位置,因为LCD1602字符地址首位D7恒定为1(100000000=80) #define er 0x80+0x40 //LCD第二行初始位置(因为第二行第一个字符位置地址是0x40) //液晶屏的与C51之间的引脚连接定义(显示数据线接C51的P0口) sbit rs=P2^6; //寄存器选择 sbit en=P2^7; //下降沿使能 sbit rw=P2^5; //读写信号线 //DS1302时钟芯片与C51之间的引脚连接定义 sbit IO=P3^4;//数据线 sbit SCLK=P3^6; sbit RST=P3^5; sbit ACC0=ACC^0; sbit ACC7=ACC^7; ACC累加器=A ACC.0=E0H //校时按键与C51的引脚连接定义 sbit key1=P2^0; //设置键 sbit key2=P2^1; //加键 sbit key3=P2^2; //减键 sbit buzzer=P1^5;//蜂鸣器,端口低电平响 u年显示的固定字符 uchar code tab2[]={" : : "};//时间显示的固定字符 //延时函数,后面经常调用 void delay(uint xms)//延时函数,有参函数 { uint x,y; for(x=xms;x>0;x--) for(y=120;y>0;y--); } void write_1602com(uchar com)//****液晶写入指令函数**** { rs=0;//数据/指令选择置为指令 rw=0; //读写选择置为写 P0=com;//送入数据 delay(1); en=1;//拉高使能端,为制造有效的下降沿做准备 delay(1); en=0;//en由高变低,产生下降沿,液晶执行命令 } void write_1602dat(uchar dat)//***液晶写入数据函数**** { rs=1;//数据/指令选择置为数据 rw=0; //读写选择置为写 P0=dat;//送入数据 delay(1); en=1; //en置高电平,为制造下降沿做准备 delay(1); en=0; //en由高变低,产生下降沿,液晶执行命令 } void lcd_init()//***液晶初始化函数**** { write_1602com(0x38);//设置液晶工作模式,意思:16*2行显示,5*7点阵,8位数据 write_1602com(0x0c);//开显示不显示光标 write_1602com(0x06);//整屏不移动,光标自动右移 write_1602com(0x01);//清显示 write_1602com(yh+1);//日历显示固定符号从第一行第1个位置之后开始显示 for(a=0;a<14;a++) { write_1602dat(tab1[a]);//向液晶屏写日历显示的固定符号部分 //delay(3); } write_1602com(er+2);//时间显示固定符号写入位置,从第2个位置后开始显示 for(a=0;a<8;a++) { write_1602dat(tab2[a]);//写显示时间固定符号,两个冒号 //delay(3); } } void write_byte(uchar dat)//写一个字节 { ACC=dat; RST=1; for(a=8;a>0;a--) { IO=ACC0; SCLK=0; //产生上升沿写入数据,从低位写入 SCLK=1; ACC=ACC>>1; } } uchar read_byte()//读一个字节 { RST=1; for(a=8;a>0;a--) { ACC7=IO; SCLK=1; //产生下降沿输出数据,先输出低位,保存到ACC中 SCLK=0; ACC=ACC>>1; } return (ACC); } void write_1302(uchar add,uchar dat)//向1302芯片写函数,指定写入地址,数据 { RST=0; SCLK=0; RST=1; write_byte(add); write_byte(dat); SCLK=1; RST=0; } uchar read_1302(uchar add)//从1302读数据函数,指定读取数据来源地址 { uchar temp; RST=0; SCLK=0; RST=1; write_byte(add); temp=read_byte(); SCLK=1; RST=0; return(temp); } uchar BCD_Decimal(uchar bcd)//BCD码转十进制函数,输入BCD,返回十进制 { uchar Decimal; Decimal=bcd>>4; return(Decimal=Decimal*10+(bcd&=0x0F)); } void ds1302_init() //1302芯片初始化子函数(2012-06-18,12:00:00,week7) { RST=0; SCLK=0; write_1302(0x8e,0x00); //允许写,禁止写保护 write_1302(0x80,0x00); //向DS1302内写秒寄存器80H写入初始秒数据00 write_1302(0x82,0x00);//向DS1302内写分寄存器82H写入初始分数据00 write_1302(0x84,0x12);//向DS1302内写小时寄存器84H写入初始小时数据12 write_1302(0x8a,0x07);//向DS1302内写周寄存器8aH写入初始周数据4 write_1302(0x86,0x17);//向DS1302内写日期寄存器86H写入初始日期数据18 write_1302(0x88,0x06);//向DS1302内写月份寄存器88H写入初始月份数据06 write_1302(0x8c,0x12);//向DS1302内写年份寄存器8cH写入初始年份数据12 write_1302(0x8e,0x80); //打开写保护 } //温度显示子函数 void write_temp(uchar add,uchar dat)//向LCD写温度数据,并指定显示位置 { uchar gw,sw; gw=dat%10;//取得个位数字 sw=dat/10;//取得十位数字 write_1602com(er+add);//er是头文件规定的值0x80+0x40 write_1602dat(0x30+sw);//数字+30得到该数字的LCD1602显示码 write_1602dat(0x30+gw);//数字+30得到该数字的LCD1602显示码 显示温度的小圆圈符号,0xdf是液晶屏字符库的该符号地址码 显示"C"符号,0x43是液晶屏字符库里大写C的地址码 } //时分秒显示子函数 void write_sfm(uchar add,uchar dat)//向LCD写时分秒,有显示位置加、现示数据,两个参数 { uchar gw,sw; gw=dat%10;//取得个位数字 sw=dat/10;//取得十位数字 write_1602com(er+add);//er是头文件规定的值0x80+0x40 write_1602dat(0x30+sw);//数字+30得到该数字的LCD1602显示码 write_1602dat(0x30+gw);//数字+30得到该数字的LCD1602显示码 } //年月日显示子函数 void write_nyr(uchar add,uchar dat)//向LCD写年月日,有显示位置加数、显示数据,两个参数 { uchar gw,sw; gw=dat%10;//取得个位数字 sw=dat/10;//取得十位数字 write_1602com(yh+add);//设定显示位置为第一个位置+add write_1602dat(0x30+sw);//数字+30得到该数字的LCD1602显示码 write_1602dat(0x30+gw);//数字+30得到该数字的LCD1602显示码 } void write_week(uchar week)//写星期函数 { write_1602com(yh+0x0c);//星期字符的显示位置 switch(week) { case 1:write_1602dat('M');//星期数为1时,显示 case 2:write_1602dat('T');//星期数据为2时显示 case 3:write_1602dat('W');//星期数据为3时显示 case 4:write_1602dat('T');//星期数据为4是显示 case 5:write_1602dat('F');//星期数据为5时显示 case 6:write_1602dat('S');//星期数据为6时显示 case 7:write_1602dat('S');//星期数据为7时显示 } } //****************键盘扫描有关函数********************** void keyscan() { if(key1==0)//---------------key1为功能键(设置键)-------------------- { delay(9);//延时,用于消抖动 if(key1==0)//延时后再次确认按键按下 { 蜂鸣器短响一次 while(!key1); key1n++; if(key1n==9) key1n=1;//设置按键共有秒、分、时、星期、日、月、年、返回,8个功能循环 switch(key1n) { case 1: TR0=0;//关闭定时器 设置按键按动一次,秒位置显示光标 设置光标为闪烁 秒数据写入DS1302 case 2: write_1602com(er+6);//按2次fen位置显示光标 case 3: write_1602com(er+3);//按动3次,shi case 4: write_1602com(yh+0x0e);//按动4次,week case 5: write_1602com(yh+0x0a);//按动5次,ri case 6: write_1602com(yh+0x07);//按动6次,yue case 7: write_1602com(yh+0x04);//按动7次,nian case 8: 按动到第8次,设置光标不闪烁 打开定时器 数据写入DS1302 break; } } } //------------------------------加键key2---------------------------- if(key1n!=0)//当key1按下以下。再按以下键才有效(按键次数不等于零) { if(key2==0) //上调键 { delay(10); if(key2==0) { 蜂鸣器短响一次 while(!key2); switch(key1n) { case 1:miao++;//设置键按动1次,调秒 秒超过59,再加1,就归零 令LCD在正确位置显示"加"设定好的秒数 十进制转换成DS1302要求的BCD码 允许写,禁止写保护 向DS1302内写秒寄存器80H写入调整后的秒数据BCD码 打开写保护 因为设置液晶的模式是写入数据后,光标自动右移,所以要指定返回 case 2:fen++; 令LCD在正确位置显示"加"设定好的分数据 十进制转换成DS1302要求的DCB码 允许写,禁止写保护 向DS1302内写分寄存器82H写入调整后的分数据BCD码 打开写保护 因为设置液晶的模式是写入数据后,指针自动加一,在这里是写回原来的位置 case 3:shi++; 令LCD在正确的位置显示"加"设定好的小时数据 十进制转换成DS1302要求的DCB码 允许写,禁止写保护 向DS1302内写小时寄存器84H写入调整后的小时数据BCD码 打开写保护 因为设置液晶的模式是写入数据后,指针自动加一,所以需要光标回位 case 4:week++; 指定'加'后的周数据显示位置 指定周数据显示内容 十进制转换成DS1302要求的DCB码 允许写,禁止写保护 向DS1302内写周寄存器8aH写入调整后的周数据BCD码 打开写保护 因为设置液晶的模式是写入数据后,指针自动加一,所以需要光标回位 case 5:ri++; 令LCD在正确的位置显示"加"设定好的日期数据 十进制转换成DS1302要求的DCB码 允许写,禁止写保护 向DS1302内写日期寄存器86H写入调整后的日期数据BCD码 打开写保护 因为设置液晶的模式是写入数据后,指针自动加一,所以需要光标回位 case 6:yue++; 令LCD在正确的位置显示"加"设定好的月份数据 十进制转换成DS1302要求的DCB码 允许写,禁止写保护 向DS1302内写月份寄存器88H写入调整后的月份数据BCD码 打开写保护 因为设置液晶的模式是写入数据后,指针自动加一,所以需要光标回位 case 7:nian++; 令LCD在正确的位置显示"加"设定好的年份数据 十进制转换成DS1302要求的DCB码 允许写,禁止写保护 向DS1302内写年份寄存器8cH写入调整后的年份数据BCD码 打开写保护 因为设置液晶的模式是写入数据后,指针自动加一,所以需要光标回位 } } } //------------------减键key3,各句功能参照'加键'注释--------------- if(key3==0) { delay(10);//调延时,消抖动 if(key3==0) { 蜂鸣器短响一次 while(!key3); switch(key1n) { case 1:miao--; 秒数据减到-1时自动变成59 在LCD的正确位置显示改变后新的秒数 十进制转换成DS1302要求的DCB码 允许写,禁止写保护 向DS1302内写秒寄存器80H写入调整后的秒数据BCD码 打开写保护 因为设置液晶的模式是写入数据后,指针自动加一,在这里是写回原来的位置 case 2:fen--; 十进制转换成DS1302要求的DCB码 允许写,禁止写保护 向DS1302内写分寄存器82H写入调整后的分数据BCD码 打开写保护 因为设置液晶的模式是写入数据后,指针自动加一,在这里是写回原来的位置 case 3:shi--; 十进制转换成DS1302要求的DCB码 允许写,禁止写保护 向DS1302内写小时寄存器84H写入调整后的小时数据BCD码 打开写保护 因为设置液晶的模式是写入数据后,指针自动加一,所以需要光标回位 case 4:week--; 指定'加'后的周数据显示位置 指定周数据显示内容 十进制转换成DS1302要求的DCB码 允许写,禁止写保护 向DS1302内写周寄存器8aH写入调整后的周数据BCD码 打开写保护 因为设置液晶的模式是写入数据后,指针自动加一,所以需要光标回位 case 5:ri--; 十进制转换成DS1302要求的DCB码 允许写,禁止写保护 向DS1302内写日期寄存器86H写入调整后的日期数据BCD码 打开写保护 因为设置液晶的模式是写入数据后,指针自动加一,所以需要光标回位 case 6:yue--; 十进制转换成DS1302要求的DCB码 允许写,禁止写保护 向DS1302内写月份寄存器88H写入调整后的月份数据BCD码 打开写保护 因为设置液晶的模式是写入数据后,指针自动加一,所以需要光标回位 case 7:nian--; 十进制转换成DS1302要求的DCB码 允许写,禁止写保护 向DS1302内写年份寄存器8cH写入调整后的年份数据BCD码 打开写保护 因为设置液晶的模式是写入数据后,指针自动加一,所以需要光标回位 break; } } } } } void init() //定时器、计数器设置函数 { TMOD=0x11; //指定定时/计数器的工作方式为3 TH0=0; //定时器T0的高四位=0 TL0=0; //定时器T0的低四位=0 EA=1; //系统允许有开放的中断 ET0=1; //允许T0中断 TR0=1; //开启中断,启动定时器 } //*******************主函数************************** void main() { l调用液晶屏初始化子函数 d调用DS1302时钟的初始化子函数 i调用定时计数器的设置子函数 /打开LCD的背光电源 蜂鸣器长响一次 while(1) //无限循环下面的语句: { keyscan(); //调用键盘扫描子函数 } void timer0() interrupt 1 //取得并显示日历和时间 { 温度传感器DS18b2初始化子函数,在头文件中 将18b20头文件运行返回的函数结果送到变量FLAG中,用于显示 //读取秒时分周日月年七个数据(DS1302的读寄存器与写寄存器不一样): fen = BCD_Decimal(read_1302(0x83)); shi = BCD_Decimal(read_1302(0x85)); ri = BCD_Decimal(read_1302(0x87)); yue = BCD_Decimal(read_1302(0x)); nian=BCD_Decimal(read_1302(0x8d)); week=BCD_Decimal(read_1302(0x8b)); //显示温度、秒、时、分数据: 显示温度,从第二行第12个字符后开始显示 秒,从第二行第8个字后开始显示(调用时分秒显示子函数) write_sfm(5,fen);//分,从第二行第5个字符后开始显示 write_sfm(2,shi);//小时,从第二行第2个字符后开始显示 //显示日、月、年数据: write_nyr(9,ri);//日期,从第二行第9个字符后开始显示 月份,从第二行第6个字符后开始显示 write_nyr(3,nian);//年,从第二行第3个字符后开始显示 write_week(week); }