概述
在許多系統當中都需要精確的時鐘功能,因此時鐘芯片孕育而生。其中美國達拉斯 DALLAS 公司設計的 DS1302 是一款非常流行的數字時鐘芯片。DS1302 是一款具有涓細電流充電能力的低功耗實時時鐘芯片。它可以對年、月、日、星期、時、分、秒進行計時,并且具有閏年功能。年計數可達到 2100 年。
15.1 DS1302 功能簡介
DS1302 內部包含 31 字節的通用 RAM,實現設置備用電池功能。采用 3 線制的串行數據通信接口,并且適用于大多數的微處理器。工作電壓范圍達到 2~5V,與 5VTTL 電平完全兼容。支持單字節或多字節時鐘、RAM 數據讀、寫操作。當工作電壓為 2V 時,工作電流低至 320nA。工業級 DS1302 正常工作溫度范圍為:-40℃ ~85℃,芯片包括直插和貼片兩種封裝模式,封裝示意圖如下所示:
DS1302 典型通信電路如下圖所示:
如上圖所示,只需 3 根線 CE、I/O、SCLK 便可實現處理器與 DS1302 之間通信,上圖中 X1,X2 之間為外接時鐘晶振,VCC2 為電源供電端,VCC1 為備用電池端。各管腳定義及功能如下所示:
15.2 單字節操作模式
與 DS1302 進行數據通信時,首先得向 DS1302 傳輸一個字節的控制指令,控制指令位定義如下所示。
字節的最高位 bit7 必須為 1,否則無法向 DS1302 寫入數據。Bit6 為 1 時,表示后續對 31 字節的 RAM 進行讀寫操作,為 0 時,表示后續將對時鐘寄存器進行讀寫操作。Bit5~bit1 為后續操作的時鐘寄存器或 RAM 的地址。最低位 bit0 為 1 時,表示讀取 DS1302 的數據,為 0 時,表示向 DS1302 寫入數據。對 DS1302 進行單字節模式的讀、寫操作時序如下圖所示。
單字節寫操作(Single-Byte Write)時序為首先向 DS1302 寫入控制指令,緊接著寫入一個字節的數據。寫入的順序為低位在前,高位在后的傳輸方式,要求在時鐘上升沿準備好數據。單字節讀操作(Single-Byte Read)時序為首先向 DS1302 寫入控制指令,緊接著 SCLK 的的下降沿 DS1302 有數據 D0 輸出,因此在接下來的上升沿前讀取穩定的 D0 值,依次類推至 D7。根據上圖時序要求,往 DS1302 寫入一個字節和讀取一個字節的函數如下所示:
//寫字節
void WrByte_1302(uchar dat)
{
uchar j;
bit flag;
for(j=1;j<=8;j++)
{ //從低到高依次將1Byte數據寫入DS1302
flag = dat&0x01;
IO_1302 = flag;//將要寫的位放到總線
SCLK_1302 = 0;
SCLK_1302 = 1;//產生一個上升沿,完成1位數據寫入
dat=dat >>1;//將數據移到下一位
}
}
//讀字節
uchar RdByte_1302(void)
{
uchar dat,flag,j;
for(j=1;j<=8;j++)
{
SCLK_1302 = 1;//產生一個下降沿
SCLK_1302 = 0;
flag = IO_1302;//讀取DS1302發出的一位數據
dat=(dat >>1)|(flag< 7);//讀出的值最低位在前面
}
return dat;
}
如上所示,寫字節函數 WrByte_1302()中,要求單片機在時鐘 SCLK_1302 上升沿前將數據放到數據總線 IO_1302 上,然后產生一個 SCLK_1302 上升,完成一 bit 數據的寫入,同時要求 1Byte 的數據低位在前,高位在后依次發送。讀字節函數 RdByte_1302()中,要求在時鐘 SCLK_1302 下降沿之后,將總線 IO_1302 數據讀出,同時要求 1Byte 的數據低位在前,高位在后依次讀取。
在上述函數的基礎上,單字節操作模式的讀、寫函數如下所示:
//單字節寫模式
void WrSingle_1302(uchar addr,uchar dat)
{
CE_1302 = 1;//拉高片選
WrByte_1302(addr);//寫入地址及控制指令
WrByte_1302(dat);//寫入數據
CE_1302 = 0;//拉低片選
SCLK_1302 = 0;//釋放始終總線,滿足下次操作時序要求(非常重要)
}
//單字節讀模式
uchar RdSingle_1302(uchar addr)
{
uchar dat;
CE_1302 = 1;//拉高片選
WrByte_1302(addr);//寫入地址及控制指令
dat = RdByte_1302();//讀取一個字節數據
CE_1302 = 0;//拉低片選
return dat;
}
我們這里重點講述 DS1302 的時鐘功能,因此與涓流充電有關的 31 字節 RAM 操作這里不做詳細的介紹。與實時時鐘有關的寄存器如圖所示。
與時鐘有關的寄存器總共有 9 個如上圖所示,前 7 個分別為:秒、分、時、日、月、星期、年,均為 8 位寄存器。以秒寄存器為例介紹時間的表示法,其中 bit6-bit4 為秒的十位,bit3-bit0 為秒的個位。59 秒時,bit6-bit4=“101”,bit3-bit0=“1001”,其它依此類推。另外,設置“時”寄存器的 bit7 可以設置為 12 小時或 24 小時制。上述寄存器的讀寫控制指令字節分別如圖左側兩列所示。
秒寄存器的 CH(bit7)定義為時鐘運行標志位,當該位被設置成 1 時,時鐘計時停止,并且 DS1302 進入低功耗模式。當設置為 0 時,啟動計時。上電初始狀態時,該位狀態不定,因此在時鐘初始化時確保該位被清 0,保證后續時鐘芯片正常運行。
第 8 個寄存器為控制寄存器,WP(bit7)為寫保護位,當設置為 1 時,無法向 DS1302 寫入數據,上電時該位狀態不定,因此,需要對 DS1302 其它寄存器進行寫操作之前,務必先將 WP 設置為 0。第 9 個寄存器不影響實現時鐘功能,暫不做介紹。
因此,在 DS 1302 應用時要對它進行初始化,首先解除寫保護,然后將與時間有關的 7 個寄存器賦初值,將初始化的內容放到函數 Init_1302(Uchar *SetTime)中。另外,我們將從 DS1302 讀取 7 個時間值的操作放到函數 GetTime(*CurrentTime)中,如下代碼所示。
//1302初始化
void Init_1302(uchar *SetTime)
{
uchar j;
CE_1302 = 0;//初始化通信引腳
SCLK_1302 = 0;
WrSingle_1302(0x8E,0x00);//解除寫保護(WP=0)
for(j=0;j<=6;j++)
{
WrSingle_1302(0x80+2*j,SetTime[j]);//寫入7個時鐘數據
}
//WrBurst_1302(SetTime);//當采用Burst模式時,使用此語句替代上面for循環語句
}
//獲取當前時間值
void GetTime(uchar *CurrentTime)
{
uchar j;
CE_1302 = 0;//初始化通信引腳
SCLK_1302 = 0;
for(j=0;j<=6;j++)
{
*CurrentTime = RdSingle_1302(0x81+2*j);//讀取7個時鐘數據
CurrentTime++;
}
//RdBurst_1302(CurrentTime); //當采用Burst模式時,使用此語句替代上面for循環語句
}
到目前為止,我們已經學習了時鐘芯片 DS1302 的功能介紹,以及初始化和時鐘獲取函數的編寫,RY-51 單片機開發板上 DS1302 電路原理圖如下所示,三根通信線分別接 4.7K 上拉電阻,分別與單片機的 I/O 口相連接。
按照慣例我們將和 DS1302 有關的函數打包放入"Drive_DS1302.h","Drive_DS1302.c"中,方便后續調用及移植。"Drive_DS1302.h"代碼如下:
#ifndef __DS1302_H__
#define __DS1302_H__
//1302初始化
extern void Init_1302(unsigned char *SetTime);
//獲取時間
extern void GetTime(unsigned char *CurrentTime);
//單字節模式寫
void WrSingle_1302(unsigned char addr,unsigned char dat);
//單字節模式讀
unsigned char RdSingle_1302(unsigned char addr);
//突發模式寫
void WrBurst_1302(unsigned char *SetTime);
//突發模式讀
void RdBurst_1302(unsigned char *CurrentTime);
#endif
"Drive_DS1302.c"代碼如下:
#include< reg52.h >
#include"Drive_DS1302.h"
#define uchar unsigned char
#define uint unsigned int
sbit CE_1302 = P1^2; //DS1302通信引腳CE,I/O,SCLK定義
sbit IO_1302 = P1^1;
sbit SCLK_1302 = P1^0;
//寫字節
void WrByte_1302(uchar dat)
{
uchar j;
bit flag;
for(j=1;j<=8;j++)
{ //從低到高依次將1Byte數據寫入DS1302
flag = dat&0x01;
IO_1302 = flag;//將要寫的位放到總線
SCLK_1302 = 0;
SCLK_1302 = 1;//產生一個上升沿,完成1位數據寫入
dat=dat >>1;//將數據移到下一位
}
}
//讀字節
uchar RdByte_1302(void)
{
uchar dat,flag,j;
for(j=1;j<=8;j++)
{
SCLK_1302 = 1;//產生一個下降沿
SCLK_1302 = 0;
flag = IO_1302;//讀取DS1302發出的一位數據
dat=(dat >>1)|(flag< 7);//讀出的值最低位在前面
}
return dat;
}
//單字節寫模式
void WrSingle_1302(uchar addr,uchar dat)
{
CE_1302 = 1;//拉高片選
WrByte_1302(addr);//寫入地址及控制指令
WrByte_1302(dat);//寫入數據
CE_1302 = 0;//拉低片選
SCLK_1302 = 0;//釋放始終總線,滿足下次操作時序要求(非常重要)
}
//單字節讀模式
uchar RdSingle_1302(uchar addr)
{
uchar dat;
CE_1302 = 1;//拉高片選
WrByte_1302(addr);//寫入地址及控制指令
dat = RdByte_1302();//讀取一個字節數據
CE_1302 = 0;//拉低片選
return dat;
}
//突發寫模式
void WrBurst_1302(uchar *SetTime)
{
uchar j;
CE_1302 = 1;//拉高片選
WrByte_1302(0xBE);//Burst模式寫專用指令
for(j=0;j<=6;j++)
{
WrByte_1302(SetTime[j]);//寫入7位時鐘數據
}
CE_1302 = 0;//拉低片選
}
//突發讀模式
void RdBurst_1302(uchar *CurrentTime)
{
uchar j;
CE_1302 = 1;//拉高片選
WrByte_1302(0xBF);//Burst模式讀專用指令
for(j=0;j<=6;j++)
{
*CurrentTime = RdByte_1302();//讀取一個字節數據;
CurrentTime++;
}
CE_1302 = 0;//拉低片選
}
//1302初始化
void Init_1302(uchar *SetTime)
{
uchar j;
CE_1302 = 0;//初始化通信引腳
SCLK_1302 = 0;
WrSingle_1302(0x8E,0x00);//解除寫保護(WP=0)
for(j=0;j<=6;j++)
{
WrSingle_1302(0x80+2*j,SetTime[j]);//寫入7個時鐘數據
}
//WrBurst_1302(SetTime);//當采用Burst模式時,使用此語句替代上面for循環語句
}
//獲取當前時間值
void GetTime(uchar *CurrentTime)
{
uchar j;
CE_1302 = 0;//初始化通信引腳
SCLK_1302 = 0;
for(j=0;j<=6;j++)
{
*CurrentTime = RdSingle_1302(0x81+2*j);//讀取7個時鐘數據
CurrentTime++;
}
//RdBurst_1302(CurrentTime); //當采用Burst模式時,使用此語句替代上面for循環語句
}
下面我們利用上面編寫的函數以及學習的單片機的知識,開始編寫一個小的時鐘顯示綜合應用程序。程序的功能為:上電時由單片機對 DS1302 進行初始化,設置時間為 2017 年、星期日、12 月 31 日、23 時 58 分 56 秒,初始化完成后,每隔 500ms 獲取 DS1302 的時間,并將時間顯示到 1602 液晶顯示器上,主函數程序如下所示:
#include< reg52.h >
#include"Drive_1602.h"
#include"Drive_DS1302.h"
#define uchar unsigned char
#define uint unsigned int
#define FOSC 11059200 //單片機晶振頻率
#define T_1ms (65536 - FOSC/12/1000) //定時器初始值計算
sbit DU = P2^7;//數碼管段選、位選引腳定義
sbit WE = P2^6;
sbit DU_L = P2^3;
uchar T_flag = 0;//定時500ms標志位
uchar str[23]=0; //字符臨時存儲變量
unsigned char code SetTime[7]={//2017年,星期日,12月31日,23時58分56秒,時間初始值
0x56,0x58,0x23,0x31,0x12,0x07,0x17};
uchar CurrentTime[7]={0};//存儲時間變量
void main()
{
Init_1602();//1602初始
P0 = 0xff;//關閉所有數碼管
WE = 1;
WE = 0;
DU_L = 0;
TMOD = 0x01; //定時器工作模式配置
TL0 = T_1ms; //裝載初始值
TH0 = T_1ms >>8;
TR0 = 1; //啟動定時器
ET0 = 1; //允許定時器中斷
EA = 1; //開總中斷
Init_1302(SetTime);//1302初始化
while(1)
{
if(T_flag)//500ms定時
{
T_flag = 0;
GetTime(CurrentTime);//獲取時間
str[0] = '2';
str[1] = '0';
str[2] = (CurrentTime[6] >>4)+'0'; //年
str[3] = (CurrentTime[6]& 0x0F)+'0';
str[4] = '-';
str[5] = (CurrentTime[4] >>4)+'0'; //月
str[6] = (CurrentTime[4]& 0x0F)+'0';
str[7] = '-';
str[8] = (CurrentTime[3] >>4)+'0'; //日
str[9] = (CurrentTime[3]& 0x0F)+'0';
str[10] = '?';
str[11] = (CurrentTime[2] >>4)+'0'; //時
str[12] = (CurrentTime[2]& 0x0F)+'0';
str[13] = ':';
str[14] = (CurrentTime[1] >>4)+'0'; //分
str[15] = (CurrentTime[1]& 0x0F)+'0';
str[16] = ':';
str[17] = (CurrentTime[0] >>4)+'0'; //秒
str[18] = (CurrentTime[0]& 0x0F)+'0';
str[19] = ' ';
str[20] = (CurrentTime[5] >>4)+'0'; //星期
str[21] = (CurrentTime[5]& 0x0F)+'0';
str[22] = '?';
Disp_1602_str(1,4,str); //將獲得的時間分別顯示到1602的第一二行
Disp_1602_str(2,3,str+11);
}
}
}
//定時器0中斷子程序,定時1ms
void timer0() interrupt 1
{
static uint T_500ms = 0;
TL0 = T_1ms;//重裝初始值
TH0 = T_1ms >>8;
T_500ms++;
if(T_500ms >=500)//500ms,置位T_flag
{
T_500ms = 0;
T_flag = 1;
}
}
將程序下查看結果是否與預想的一致吧。
15.3 突發操作模式
上面我們講解的是以單字節的模式,從 1302 中連續讀取時間數據。仔細的同學可能會發現一個問題,就是我們連續讀 7 個時間寄存器是有先后順序的,會有讀錯數據的風險。例如我們要讀的時間為 23 時 59 分 59 秒,最開始時我們把 59 秒讀出來了,如果剛好在你讀完的時候 59 秒變成了 00 秒,59 分變成了 00 分,23 時變成了 00 時,接下來把分、時依次讀出來,因此我們讀出來的時間為 00 時 00 分 59 秒,很顯然這個時間是不對的。下面我們講解的突發操作模式有效的解決了這個問題。
在突發操作讀模式下,當 DS1302 收到突發讀數據指令,DS1302 首先會把 8 個時間寄存器的數據同時讀出存放在 8 個二級時間寄存器中,然后依次把 8 個二級時間寄存器的數據輸出給單片機,突發讀專用指令為 0xBF。同樣,當我們需要寫 DS1302 時,當收到突發寫指令后,DS1302 將接收到的 8 個連續數據存儲到 8 個二級時間寄存器中,然后同時將 8 個數據寫到時間寄存器中,突發寫專用指令為 0xBE。根據上述原理,編寫突發寫模式和突發讀模式函數如下代碼所示。
//突發寫模式
void WrBurst_1302(uchar *SetTime)
{
uchar j;
CE_1302 = 1;//拉高片選
WrByte_1302(0xBE);//Burst模式寫專用指令
for(j=0;j<=6;j++)
{
WrByte_1302(SetTime[j]);//寫入7位時鐘數據
}
CE_1302 = 0;//拉低片選
}
//突發讀模式
void RdBurst_1302(uchar *CurrentTime)
{
uchar j;
CE_1302 = 1;//拉高片選
WrByte_1302(0xBF);//Burst模式讀專用指令
for(j=0;j<=6;j++)
{
*CurrentTime = RdByte_1302();//讀取一個字節數據;
CurrentTime++;
}
CE_1302 = 0;//拉低片選
}
如上圖所示,首先為向 DS1302 寫入突發讀或者寫指令,然后緊接著是讀取或寫入 7 個時間數據。前面講解的都是 8 個連續的數據,我們這里寫 7 個的原因是,時間顯示這 7 個就足夠了。上述完整代碼詳見完整代碼"Drive_DS1302.h"、"Drive_DS1302.c"中。
突發模式的應用與單字節模式類似,只需將如下代碼中的for循環語句替換成“RdBurst_1302(CurrentTime)”即可。
//獲取當前時間值
void GetTime(uchar *CurrentTime)
{
uchar j;
CE_1302 = 0;//初始化通信引腳
SCLK_1302 = 0;
for(j=0;j<=6;j++)
{
*CurrentTime = RdSingle_1302(0x81+2*j);//讀取7個時鐘數據
CurrentTime++;
}
//RdBurst_1302(CurrentTime); //當采用Burst模式時,使用此語句替代上面for循環語句
}
15.4 本章小結
本章詳細介紹了實時時鐘芯片DS1302的工作原理,驅動程序的編寫,以及DS1302的簡單應用。
-
單片機
+關注
關注
6032文章
44514瀏覽量
632946 -
微處理器
+關注
關注
11文章
2247瀏覽量
82310 -
時鐘芯片
+關注
關注
2文章
243瀏覽量
39846 -
DS1302
+關注
關注
8文章
449瀏覽量
50614
發布評論請先 登錄
相關推薦
評論