目录
前言
本次编程实验以IAP15F2K61S2为单片机主控芯片,其编程使用与STC15F2K60S2完全相同,头文件为STC15F2K60S2.H
。若用于51系列单片机,以reg52.h
为头文件,则读者需将程序中可能涉及的定时器初始化程序和LED亮灭程序和数码管显示程序,根据自身所用单片机原理图和手册进行修改。
一、DS1302简介
DS1302是DALLAS公司推出的涓流充电时钟芯片,内含有两块存储器:日历时钟寄存器和静态RAM存储器(31字节),后者可用于用户自定义编程。DS1302采用SPI三线接口与单片机进行通信,可向用户提供秒分时日月年的信息,且可通过 AM/PM 指示决定采用 24 或 12 小时格式,每月的天数和闰年的天数可自动调整。
二、DS1302引脚定义和原理图
1.引脚定义
Vcc1和Vcc2:电源供电管脚。其中Vcc1作为主电源,Vcc2作为备用电源。当Vcc2>Vcc1+0.2V时,由Vcc2向DS1302供电;当Vcc2< Vcc1时,由Vcc1向DS1302供电。
X1和X2:32.768KHz 晶振管脚
SCLK:串行时钟输入。控制数据的输入和输出。
I/O: 数据输入和输出引脚
CE:使能信号,在读、写数据期间,必须为高电平。
2.原理图
由原理图可知,SCLK引脚为P1^ 7 ; I /O引脚为P2^ 3;CE引脚为P1^ 3。
三、DS1302编程原理
1.时钟日历寄存器定义
时钟日历寄存器包含在 7 个读/写寄存器内,相关数据以BCD码显示存放。BCD码是二进制十进制代码,是一种二进制的数字编码形式,利用四个位元来储存一个十进制的数码,可以快速转换。如10对应的BCD码为0x10,25对应0x25,简单来说就是在十进制前加0x(十六进制标志)即可。时钟日历寄存器从第一行至第七行,每一行分别为秒分时日月星期年寄存器。第八行为写保护寄存器。此外,第一列为读控制字节,第二列为写控制字节(后文介绍)。
7 个读/写寄存器的每一位,并不都是用于存放数据:
1.秒寄存器的 BIT7 定义为时间暂停位,当 BIT1 为 1 时,时钟振荡器停止工作,DS1302 进入低功耗模式,电源消耗小于 100 微安;当 BIT1 为 0 时,时钟振荡器启动,DS1302 正常工作。
2.小时寄存器的 BIT7 定义为 12 或 24 小时工作模式选择位,当 BIT7 为高时,为 12 小时工作模式,此时 BIT5 为 AM/PM 位,低电平标示 AM,高电平标示PM,在 24 小时模式下,BIT5 为第二个 10 小时位标示(20~23 时)。
在对上述寄存器进行数据写入时,必须先将写保护寄存器的 BIT7(即WP,写保护位)置低电平,其他位都置为0;否则不能对任何时钟日历寄存器进行写操作,但可以进行读操作。
2.控制字节定义
在单片机与DS1302进行数据通信时,需要发送一个控制字节,控制字节的意义在于:让DS1302知道在何处如何对数据进行处理(存放或读取)。
位7:必须是逻辑1,如果它为0,则不能把数据写入到DS1302中。
位6:如果为0,则表示存取日历时钟数据,为1表示存取RAM数据。
位5-位1:指示操作单元的地址。
位0(最低有效位):为0,表示要进行写操作,写入数据;为1,表示进行读操作,读出数据。
以对秒寄存器进行写操作为例,控制字节为1000 0000,即0x80,符合时钟日历寄存器中秒寄存器的写控制字节。
3.时序图
写时序:首先CE置高电平,开始的 8 个 SCLK 周期,每迎来一次上升沿,输入写控制字节的一位;数据字节在后 8 个 SCLK 周期的
上升沿输入;最后CE置低电平,结束通信。写控制字节输入和数据字节输入均从位 0 开始。
读时序:首先CE置高电平,开始的 8 个 SCLK 周期,每迎来一次上升沿,输入读控制字节的一位;数据字节在后 8 个 SCLK 周期的
下降沿输出;最后CE置低电平,结束通信。读控制字节输入和数据字节输出均从位 0 开始。需要注意的是:在读控制字节位7输入后,SCLK将迎来一次下降沿,数据字节将从此次下降沿开始发送。
4.具体过程
先向DS1302写入数据,设定起始时间:首先关闭写保护,然后写入写控制字节及数据字节,最后打开写保护。向DS1302读取数据时:直接写入读控制字节,即可读出数据。
四、编程实现
1.ds1302.h
#ifndef __DS1302_H
#define __DS1302_H
#include <STC15F2K60S2.H>
#include <intrins.h>
//对DS1302引脚进行I/O口定义
sbit SCK=P1^7; //串行时钟线
sbit SDA=P2^3; //数据线
sbit RST = P1^3; // DS1302使能线
extern unsigned char read_time[]; //声明外部变量
void DS1302_Write(unsigned char temp); //向DS1302写入一个字节
void DS1302_Write_Byte( unsigned char address,unsigned char dat ); //向指定寄存器写入数据
unsigned char DS1302_Read_Byte( unsigned char address ); //从指定寄存器读出数据
void DS1302_set(unsigned char DS1302_set_addr[],unsigned char ds1302_set_time[]); //DS1302初始时间设定
void DS1302_read(unsigned char DS1302_set_addr[]); //DS1302时间读取
#endif
2.ds1302.c
#include "ds1302.h"
unsigned char read_time[7]={0}; //DS1302时间存储数组
/*
* @brief DS1302通信初始化
* @param
* @reval
* @note:
*/
void DS1302_Start()
{
RST=0; _nop_(); //DS1302复位
SCK=0; _nop_(); //拉低串行时钟线
RST=1; _nop_(); //禁止复位
}
/*
* @brief 向DS1302写入一个字节
* @param byte:要写入的字节
* @reval
* @note:
*/
void DS1302_Write(unsigned char byte)
{
unsigned char i=0;
for (i=0;i<8;i++)
{
SCK=0; //拉低时钟线
SDA=byte&0x01; //取出写入字节的位0,放到数据线上
byte>>=1; //byte字节右移1位,从而位1变成位0,位2变成位1...从而低位先写入
SCK=1; //拉高时钟线,产生上升沿,发送一位数据
}
}
/*
* @brief 向DS1302寄存器写入数据
* @param address:要写入数据的寄存器;dat:要写入的数据
* @reval
* @note:
*/
void DS1302_Write_Byte( unsigned char address,unsigned char dat )
{
DS1302_Start(); //DS1302通信初始化
DS1302_Write(address); //写入控制字节,即指明寄存器和读/写操作
DS1302_Write(dat); //写入数据
RST=0; //通信结束
}
/*
* @brief 从DS1302寄存器读取数据
* @param address:待读取数据的寄存器
* @reval temp:读出的数据
* @note:
*/
unsigned char DS1302_Read_Byte ( unsigned char address )
{
unsigned char i=0,temp=0x00;
DS1302_Start(); //DS1302通信初始化
DS1302_Write(address); //写入控制字节,即指明寄存器和读/写操作,此行代码运行结束后,SCK=1
for (i=0;i<8;i++)
{
SCK=0; //拉低时钟线,产生下降沿,从而开始发送一位数据
temp>>=1; //temp右移一位,清零位7
if(SDA) //与下一行代码搭配,如果SDA=1,则temp位7为1;反之为0,从而读出数据。
temp|=0x80; //读出一位,并赋值给temp的位7,下次循环时,temp右移一位,使得位7变成位6,位6变成位5...从而低位先读出
SCK=1; //拉高时钟线
}
RST=0; _nop_(); //结束通信
SCK=0; _nop_(); //拉低时钟线
SCK=1; _nop_(); //拉高时钟线,实质是复位到高电平
SDA=0; _nop_();
SDA=1; _nop_(); //复位数据线到高电平
return (temp); //返回读出的数据字节
}
/*
* @brief DS1302初始时间设定
* @param DS1302_set_addr[]:时钟日历7个写寄存器地址;DS1302_set_time[]:预设的时间(BCD码)
* @reval
* @note:
*/
void DS1302_set(unsigned char DS1302_set_addr[],unsigned char DS1302_set_time[])
{
unsigned char i=0;
DS1302_Write_Byte(0x8e,0x00); //关闭写保护
for(i=0;i<7;i++)
{
DS1302_Write_Byte(DS1302_set_addr[i],DS1302_set_time[i]);//向时钟日历寄存器写入相应数据
}
DS1302_Write_Byte(0x8e,0x80); //打开写保护
}
/*
* @brief DS1302时间读取
* @param DS1302_set_addr[]:时钟日历7个写寄存器地址
* @reval
* @note:
*/
void DS1302_read(unsigned char DS1302_set_addr[])
{
unsigned char i=0;
for(i=0;i<7;i++)
{
read_time[i]=DS1302_Read_Byte(DS1302_set_addr[i]+0x01);//注意到每个时钟日历读寄存器地址比写寄存器地址大1,所以读寄存器地址在写寄存器地址基础上加1
}
}
3.main.c
#include <STC15F2K60S2.H>
#include "ds1302.h"
typedef unsigned char u8;
#define outputp0(y,x) P0=x,P2&=0x1f,P2|=y,P2&=0x1f;//P2高三位用于选择P0输出的通道,P0用于数据输出
u8 code DS1302_write_addr[]={0x80,0x82,0x84,0x86,0x88,0x8a,0x8c}; //时钟日历7个写寄存器地址
u8 code DS1302_write_time[]={0x45,0x59,0x23,0x31,0x12,0x07,0x99}; //预设的时间,依次为秒分时日月星期年
unsigned char code Seg_Table[]={ //共阳数码管
0xc0,
0xf9,
0xa4,
0xb0,
0x99,
0x92,
0x82,
0xf8,
0x80,
0x90,
0xff,
0xbf //'-'
};
void Delay2ms(void) //@11.0592MHz
{
unsigned char data i, j;
_nop_();
_nop_();
i = 22;
j = 128;
do
{
while (--j);
} while (--i);
}
/*
* @brief 1位数码管显示函数
* @param pos:位选;dat:显示数字;dot:小数点选择位,1有0无
* @reval
* @note:
*/
void showbit(unsigned char pos,dat,dot)
{
outputp0(0xc0,0x01<<pos); //位选
outputp0(0xe0,Seg_Table[dat]+0x80*dot); //段选
Delay2ms(); //延时
outputp0(0xc0,0x01<<pos); //位选
outputp0(0xe0,Seg_Table[10]); //段选,这两行用于消影
}
void test() //测试程序
{
DS1302_read(DS1302_write_addr); //读出DS1302时间
//时间显示,可通过更改read_time[]索引值显示秒分时日月星期年
showbit(0,read_time[2]/16,0); //由于BCD码相当于将十进制十位乘以16加上个位获得,因此对其除以16获得十位
showbit(1,read_time[2]%16,0); //对其取余获得个位
showbit(2,11,0);
showbit(3,read_time[1]/16,0);
showbit(4,read_time[1]%16,0);
showbit(5,11,0);
showbit(6,read_time[0]/16,0);
showbit(7,read_time[0]%16,0);
}
void main()
{
outputp0(0x80,0xff); //关闭8位LED
outputp0(0xa0,0x00); //关闭蜂鸣器、继电器等
DS1302_set(DS1302_write_addr,DS1302_write_time); //预设DS1302时间
while(1)
{
test();
}
}
五、测试现象
上电显示“23-59-45”,15秒后变成“00-00-00”。
总结
DS1302通过SPI与单片机进行通信,其通信时序相对复杂,期间不能被打断,一般来说无法使用定时器扫描数码管的方式来显示时间。当然,通过某些方式也可以实现二者并存,请读者自行思考。
有任何问题和补充,欢迎私信或评论区交流。