SCLK:
串行时钟,输入,控制数据的输入与输出;
I/O:
三线接口时的双向数据线;
CE:
输入信号,在读、写数据期间,必须为高。
该引脚有两个功能:
第一,CE开始控制字访问移位寄存器的控制逻辑;其次,CE提供结束单字节或多字节数据传输的方法;
DS1302有下列几组寄存器:
①DS1302有关日历、时间的寄存器共有12个,其中有7个寄存器
(读时81h~8Dh,写时80h~8Ch),存放的数据格式为BCD码形式,如图所示。
DS1302有关日历、时间的寄存器小时寄存器(85h、84h)的位7用于定义DS1302是运行于12小时模式还是24小时模式。
当为高时,选择12小时模式。
在12小时模式时,
位5是,当为1时,表示PM。
在24小时模式时,位5是第二个10小时
位。
秒寄存器(81h、80h)的位7定义为时钟暂停标志(CH)。
当该位置为1时,时钟振荡器停止,DS1302处于低功耗状态;当该位置为
0时,时钟开始运行。
控制寄存器(8Fh、8Eh)的位7是写保护位(WP),其它7位均置
为0。
在任何的对时钟和RAM的写操作之前,WP位必须为0。
当
WP位为1时,写保护位防止对任一寄存器的写操作。
②DS1302有关RAM的地址
DS1302中附加31字节静态RAM的地址如图所示。
③DS1302的工作模式寄存器
所谓突发模式是指一次传送多个字节的时钟信号和RAM数据。
突发模式寄存器如图所示。
1.1.2读写时序说明
DS1302是SPI总线驱动方式。
它不仅要向寄存器写入控制字,还需要读取相应寄存器的数据。
要想与DS1302通信,首先要先了解DS1302的控制字。
DS1302
的控制字如图
控制字的最高有效位(位7)必须是逻辑1,如果它为0,则不能把数据写入到DS1302中。
位6:
如果为0,则表示存取日历时钟数据,为1表示存取RAM数据;
位5至位1(A4~A0):
指示操作单元的地址;
位0(最低有效位):
如为0,表示要进行写操作,为1表示进行读操作。
控制字总是从最低位开始输出。
在控制字指令输入后的下一个
SCLK时钟的上升沿时,数据被写入DS1302,数据输入从最低位(0位)开始。
同样,在紧跟8位的控制字指令后的下一个SCLK脉冲的下降沿,读出DS1302的数据,读出的数据也是从最低位到最高位。
数据读写时序如图。
1.1.3电路原理图:
电路原理图如图8,DS1302与单片机的连接也仅需要3条线:
CE引脚、
SCLK串行时钟引脚、I/O串行数据引脚,Vcc2为备用电源,外接
32.768kHz晶振,为芯片提供计时脉冲。
1.2数码管显示原理
我们最常用的是七段式和八段式LED数码管,八段比七段多了一个小数点,其他的基本相同。
所谓的八段就是指数码管里有八个小LED发光二极管,通过控制不同的LED的亮灭来显示出不同的字形。
数码管又分为共阴极和共阳极两种类型,其实共阴极就是将八个LED的阴极连在一起,让其接地,这样给任何一个LED的另一端高电平,它便能点亮。
而共阳极就是将八个LED的阳极连在一起。
其原理图如下。
其中引脚图的两个COM端连在一起,是公共端,共阴数码管要将其接地,共阳数码管将其接正5伏电源。
一个八段数码管称为一位,多个数码管并列在一起可构成多位数码管,它们的段选线(即a,b,c,d,e,f,g,dp)连在一起,而各自的公共端称为位选线。
显示时,都从段选线送入字符编码,而选中哪个位选线,那个数码管便会被点亮。
数码管的8段,对应一个字节的8位,a对应最低位,dp对应最高位。
所以如果想让数码管显示数字0,那么共阴数码管的字符编码为00111111,即0x3f;共阳数码管的字符编码为11000000,即0xc0。
可以看出两个编码的各位正好相反。
如下图。
2程序设计
2.1总体设计
在设计程序之前我们已经对ds1302和c51CPU之间的通信原理很清楚了。
我们设计简易时钟时,先把主函数设计好。
那么如何设计主函数?
主函数是实现该功能的主要部分,主函数实现将ds1302的时钟信号准确无误的传给CPU,在传递过程中要明确地址和数据传送时的区别,时钟信号线I/O是分时复用的。
这里我们写了一个数据读取函数DS1302ReadCmd(),将地址中的数据传递给单片机,因为ds1302和单片机不能直接通信,所以在子程序DS1302ReadCmd()中还要嵌套一个DS1302写字节函数---DS1302WriteByte(uchardat),这个写字节函数的主要功能是把,ds1302时分秒寄存器地址告诉1302芯片,ds1302在接受到地址值后自动将该地址下的数据传给CPU,最后加一个数码管显示模块就可以完成上述功能。
具体实现的方法如下所示:
2.2分块程序设计
为了理解方便,将本课程设计软件部分分为如下模块:
Ds1302初始化模块,数码管显示模块,主函数模块。
(模块之间有交叉,分模块是为了说明方便)
2.2.1Ds1302初始化模块:
1)写字节函数:
将要写入的数据dat赋值给单片机中间变量temp,将temp数据的八位由低到高依次传递到ds1302的I/O口。
其中每传递一位,给SLK端口一个上升沿(这是由ds1302的工作时序决定的)。
//DS1302写字节函数
voidDS1302WriteByte(uchardat)
{
uchari=0,temp=0;
CE=0;//CE引脚为低,数据传送中止
SCLK=0;//清零时钟总线
CE=1;//CE引脚为高,逻辑控制有效
for(i=8;i>0;i--)//循环8次移位
{
SCLK=0;
temp=dat;
DIO=(bit)(temp&0x01);//每次传输低字节
dat>>=1;//右移一位
SCLK=1;
}
}
分析:
针对本课题的要求,写字节函数的作用是对1302进行初始化。
2)读字节函数:
将ds1302I/O口的数据传给ACC寄存器的最高位,利用循环语句移位,依次将8位传递到ACC寄存器,此函数返回值ACC。
//DS1302读字节函数
ucharDS1302ReadByte()
{
uchari,dat1,dat2;
CE=1;
for(i=8;i>0;i--)
{
ACC_7=DIO;
SCLK=1;
ACC>>=1;
SCLK=0;
}
CE=0;
dat1=ACC;
dat2=dat1/16;//数据进制转换,十六进制转换成十进制
dat1=dat1%16;
dat1=dat2*10+dat1;
returndat1;
}
分析:
读字节函数是将ds1302芯片中的时钟信号传给单片机的主要部分。
3)地址和数据发送函数:
先写地址addr,再给数据。
先将要读出的时间信号的地址告诉ds1302,单片机再将数据传到I/O口。
//地址、数据发送函数
voidDS1302WriteCmd(ucharaddr,uchardat)
{
DS1302WriteByte(addr);//发送地址
DS1302WriteByte(dat);//发送数据
}
分析:
此函数可以在本程序中实现对ds1302芯片的初始化。
4)数据读取函数:
先写地址,再返回数据。
//数据读取函数
ucharDS1302ReadCmd(ucharaddr)//数据读取子程序
{
DS1302WriteByte(addr);//发送地址
return(DS1302ReadByte());//接收数据
}
5)初始化时间:
先禁止写保护,再初始化时间,最后开启写保护。
//DS1302初始化函数
voidDS1302Init(void)//初始化DS1302
{
DS1302WriteCmd(0x8E,0x00);//禁止写保护
DS1302WriteCmd(0x80,0x00);//秒位初始化
DS1302WriteCmd(0x82,0x00);//分钟初始化
DS1302WriteCmd(0x84,0x20);//小时初始化
DS1302WriteCmd(0x86,0x01);//日初始化
DS1302WriteCmd(0x88,0x01);//月初始化
DS1302WriteCmd(0x8c,0x12);//年初始化
DS1302WriteCmd(0x8E,0x80);//允许写保护
}
分析:
此函数的初始化值可由使用者自己设定。
2.2.2数码管显示模块。
此函数作用是将单片机中ACC寄存器中时间数据值显示到七段数码管中。
其中,P3端口接受位选信号,P2端口接受段选信号。
//数码管显示函数
voidLEDDisplay()
{
uchari;
DisplayBuf[7]=TimeBuf[2]%10;
DisplayBuf[6]=TimeBuf[2]/10;
DisplayBuf[4]=TimeBuf[1]%10;
DisplayBuf[3]=TimeBuf[1]/10;
DisplayBuf[1]=TimeBuf[0]%10;
DisplayBuf[0]=TimeBuf[0]/10;
for(i=0;i<8;i++)//数码管动态显示
{
P3=Seg[i];
P2=table[DisplayBuf[i]];
delay
(1);//延时1ms让数码管正常显示出来
}
}
2.2.3主函数模块。
TimBuf数组变量存储从ds1302中读取的数据。
//主函数
voidmain()
{
DS1302Init();
while
(1)
{
TimeBuf[2]=DS1302ReadCmd(0x81);//0x81,0x83,0x85分别为秒,分,时读地址位
TimeBuf[1]=DS1302ReadCmd(0x83);
TimeBuf[0]=DS1302ReadCmd(0x85);
//TimeBuf[2]=DS1302ReadCmd(0x87);//0x87,0x89,0x8b分别为年,月,日读地址位
//TimeBuf[1]=DS1302ReadCmd(0x89);
//TimeBuf[0]=DS1302ReadCmd(0x8d);
LEDDisplay();
}
}
3Proteus仿真
3.1电路图搭建
3.1.1元件库的选择
由图上的操作后元件库如下:
3.1.2元件的布局:
ds1302与C51的连接:
注:
DIO接单片机P1.0口
SCLK接单片机P1.1口
RST接单片机P1.2口
数码管与C51的连接:
位选接P3口,段选接P2口。
注:
当proteus原理图十分复杂且连线操作困难时,可以用Lab命令标记,这样可以减少连线的数量从而使原理图更简洁直观。
具体操作如下:
第一步
“Lab”键
第二步:
点击需要标号的引脚,改成对应的编号
注意:
相连的引脚标号一定要一致,否则会导致错连进而影响电路图的正确性。
3.2仿真运行:
3.2.1keil软件的使用
本课设借助keil编译环境实现软件驱动:
先建工程
再建文件
3.2.2proteus仿真效果
4总结
本次单片机机课程设计的选题不仅仅完成了对C51编程、protues软件的学习,同时对于单片机硬件的了解掌握更加完善,在硬件电路搭配过程中更是提高了动手能力以及加深模拟电路理论知识的理解。
在完成本次课程设计的过程中,开拓了视野,获得了到以前上课所不能学到的知识和经验,会为以后的课程学习带来很大的帮助。
参考书
1.徐爱钧,徐阳编著。
《单片机原理与应用—基于Proteus虚拟仿真技术(第2版)》,机械工业出版社。
2014年7月
2.赵广元编著。
《proteus辅助的单片机原理实践—基础设计、课程设计、毕业设计》,北京航空航天大学出版社。
2013年9月
附录一:
完整的源程序代码
#include
#defineucharunsignedchar
#defineuintunsignedint
Ucharcodetable[]={0x3F,0X30,0X5b,0X4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x00,0x40};//共阴数码管"0-9","灭","-"编码
ucharcodeSeg[]={0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87};//位选编码
uchardataDisplayBuf[]={0,0,11,0,0,11,0,0};//时分秒显示缓冲区
uchardataTimeBuf[]={0,0,0};//时分秒值
sbitACC_7=ACC^7;//位寻址寄存器定义
sbitSCLK=P1^1;//DS1302时钟信号7脚
sbitDIO=P1^0;//DS1302数据信号6脚
sbitCE=P1^2;//DS1302片选5脚
//延时函数
voiddelay(uinti)
{
uintj;
for(i;i>0;i--)
for(j=110;j>0;j--);
}
//DS1302写字节函数
voidDS1302WriteByte(uchardat)
{
uchari=0,temp=0;
CE=0;//CE引脚为低,数据传送中止
SCLK=0;//清零时钟总线
CE=1;//CE引脚为高,逻辑控制有效
for(i=8;i>0;i--)//循环8次移位
{
SCLK=0;
temp=dat;
DIO=(bit)(temp&0x01);//每次传输低字节
dat>>=1;//右移一位
SCLK=1;
}
}
//DS1302读字节函数
ucharDS1302ReadByte()
{
uchari,dat1,dat2;
CE=1;
for(i=8;i>0;i--)
{
ACC_7=DIO;
SCLK=1;
ACC>>=1;
SCLK=0;
}
CE=0;
dat1=ACC;
dat2=dat1/16;//数据进制转换,十六进制转换成十进制
dat1=dat1%16;
dat1=dat2*10+dat1;
returndat1;
}
//地址、数据发送函数
voidDS1302WriteCmd(ucharaddr,uchardat)
{
DS1302WriteByte(addr);//发送地址
DS1302WriteByte(dat);//发送数据
}
//数据读取函数
ucharDS1302ReadCmd(ucharaddr)//数据读取子程序
{
DS1302WriteByte(addr);//发送地址
return(DS1302ReadByte());//接收数据
}
//DS1302初始化函数
voidDS1302Init(void)//初始化DS1302
{
DS1302WriteCmd(0x8E,0x00);//禁止写保护
DS1302WriteCmd(0x80,0x00);//秒位初始化
DS1302WriteCmd(0x82,0x00);//分钟初始化
DS1302WriteCmd(0x84,0x20);//小时初始化
DS1302WriteCmd(0x86,0x01);//日初始化
DS1302WriteCmd(0x88,0x01);//月初始化
DS1302WriteCmd(0x8c,0x12);//年初始化
DS1302WriteCmd(0x8E,0x80);//允许写保护
}
//数码管显示函数
voidLEDDisplay()
{
uchari;
DisplayBuf[7]=TimeBuf[2]%10;
DisplayBuf[6]=TimeBuf[2]/10;
DisplayBuf[4]=TimeBuf[1]%10;
DisplayBuf[3]=TimeBuf[1]/10;
DisplayBuf[1]=TimeBuf[0]%10;
DisplayBuf[0]=TimeBuf[0]/10;
for(i=0;i<8;i++)//数码管动态显示
{
P3=Seg[i];
P2=table[DisplayBuf[i]];
delay
(1);//延时1ms让数码管正常显示出来
}
}
//主函数
voidmain()
{
DS1302Init();
while
(1)
{
TimeBuf[2]=DS1302ReadCmd(0x81);//0x81,0x83,0x85分别为秒,分,时读地址位
TimeBuf[1]=DS1302ReadCmd(0x83);
TimeBu