DS1302 我們前邊也有提起過,,是三根線,分別是 CE,、I/O 和 SCLK,,其中 CE 是使能線,SCLK 是時鐘線,,I/O 是數(shù)據(jù)線,。前邊我們介紹過了 SPI 通信,同學(xué)們發(fā)現(xiàn)沒發(fā)現(xiàn),,這個 DS1302的通信線定義和 SPI 怎么這么像呢?
事實上,,DS1302 的通信是 SPI 的變異種類,它用了 SPI 的通信時序,,但是通信的時候沒有完全按照 SPI 的規(guī)則來,,下面我們一點點解剖 DS1302 的變異 SPI 通信方式。先看一下單字節(jié)寫入操作,,如圖 15-11 所示,。
圖 15-11 和圖 15-12 的通信時序,其中 CE 和 SSEL 的使能控制是反的,,對于通信寫數(shù)據(jù),,都是在 SCK 的上升沿,從機進行采樣,,下降沿的時候,,主機發(fā)送數(shù)據(jù),。DS1302 的時序里,單片機要預(yù)先寫一個字節(jié)指令,,指明要寫入的寄存器的地址以及后續(xù)的操作是寫操作,,然后再寫入一個字節(jié)的數(shù)據(jù)。
對于單字節(jié)讀操作,,我就不做對比了,,把 DS1302 的時序圖貼出來,大家自己看一下即可,,如圖 15-13 所示,。
讀操作有兩處需要特別注意的地方。第一,,DS1302 的時序圖上的箭頭都是針對 DS1302來說的,,因此讀操作的時候,先寫第一個字節(jié)指令,,上升沿的時候 DS1302 來鎖存數(shù)據(jù),,下降沿我們用單片機發(fā)送數(shù)據(jù)。到了第二個字?jǐn)?shù)據(jù),,由于我們這個時序過程相當(dāng)于CPOL=0/CPHA=0,,前沿發(fā)送數(shù)據(jù),后沿讀取數(shù)據(jù),,第二個字節(jié)是 DS1302 下降沿輸出數(shù)據(jù),,我們的單片機上升沿來讀取,因此箭頭從 DS1302 角度來說,,出現(xiàn)在了下降沿,。
第二個需要注意的地方就是,我們的單片機沒有標(biāo)準(zhǔn)的 SPI 接口,,和 I2C 一樣需要用 IO口來模擬通信過程,。在讀 DS1302 的時候,理論上 SPI 是上升沿讀取,,但是程序是用 IO 口模擬的,,所以數(shù)據(jù)的讀取和時鐘沿的變化不可能同時了,必然就有一個先后順序,。通過實驗發(fā)現(xiàn),,如果先讀取 IO 線上的數(shù)據(jù),再拉高 SCLK 產(chǎn)生上升沿,,那么讀到的數(shù)據(jù)一定是正確的,,而顛倒順序后數(shù)據(jù)就有可能出錯。這個問題產(chǎn)生的原因還是在于 DS1302 的通信協(xié)議與標(biāo)準(zhǔn)SPI 協(xié)議存在的差異造成的,如果是標(biāo)準(zhǔn) SPI 的數(shù)據(jù)線,,數(shù)據(jù)會一直保持到下一個周期的下降沿才會變化,,所以讀取數(shù)據(jù)和上升沿的先后順序就無所謂了;但 DS1302 的 IO 線會在時鐘上升沿后被 DS1302 釋放,也就是撤銷強推挽輸出變?yōu)槿跸吕瓲顟B(tài),,而此時在 51 單片機引腳內(nèi)部上拉的作用下,,IO 線上的實際電平會慢慢上升,從而導(dǎo)致在上升沿產(chǎn)生后再讀取 IO 數(shù)據(jù)的話就可能會出錯,。因此這里的程序我們按照先讀取 IO 數(shù)據(jù),再拉高 SCLK 產(chǎn)生上升沿的順序,。
下面我們就寫一個程序,,先將 2013 年 10 月 8 號星期二 12 點 30 分 00 秒這個時間寫到DS1302 內(nèi)部,讓 DS1302 正常運行,,然后再不停的讀取 DS1302 的當(dāng)前時間,,并顯示在我們的液晶屏上。
/***************************Lcd1602.c 文件程序源代碼*****************************/ (此處省略,,可參考之前章節(jié)的代碼)
- /*****************************main.c 文件程序源代碼******************************/
- #include
-
- sbit DS1302_CE = P1^7;
- sbit DS1302_CK = P3^5;
- sbit DS1302_IO = P3^4;
- bit flag200ms = 0; //200ms 定時標(biāo)志
- unsigned char T0RH = 0; //T0 重載值的高字節(jié)
- unsigned char T0RL = 0; //T0 重載值的低字節(jié)
-
- void ConfigTimer0(unsigned int ms);
- void InitDS1302();
- unsigned char DS1302SingleRead(unsigned char reg);
- extern void InitLcd1602();
- extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
-
- void main(){
- unsigned char i;
- unsigned char psec=0xAA; //秒備份,,初值 AA 確保首次讀取時間后會刷新顯示
- unsigned char time[8]; //當(dāng)前時間數(shù)組
- unsigned char str[12]; //字符串轉(zhuǎn)換緩沖區(qū)
- EA = 1; //開總中斷
- ConfigTimer0(1); //T0 定時 1ms
- InitDS1302(); //初始化實時時鐘
- InitLcd1602(); //初始化液晶
-
- while (1){
- if (flag200ms){ //每 200ms 讀取一次時間
- flag200ms = 0;
- for (i=0; i<>//讀取 DS1302 當(dāng)前時間
- time[i] = DS1302SingleRead(i);
- }
- if (psec != time[0]){ //檢測到時間有變化時刷新顯示
- str[0] = '2'; //添加年份的高 2 位:20
- str[1] = '0';
- str[2] = (time[6] >> 4) + '0'; //“年”高位數(shù)字轉(zhuǎn)換為 ASCII 碼
- str[3] = (time[6]&0x0F) + '0'; //“年”低位數(shù)字轉(zhuǎn)換為 ASCII 碼
- str[4] = '-'; //添加日期分隔符
- str[5] = (time[4] >> 4) + '0'; //“月”
- str[6] = (time[4]&0x0F) + '0';
- str[7] = '-';
- str[8] = (time[3] >> 4) + '0'; //“日”
- str[9] = (time[3]&0x0F) + '0';
- str[10] = '\0';
- LcdShowStr(0, 0, str); //顯示到液晶的第一行
- str[0] = (time[5]&0x0F) + '0'; //“星期”
- str[1] = '\0';
- LcdShowStr(11, 0, 'week');
- LcdShowStr(15, 0, str); //顯示到液晶的第一行
- str[0] = (time[2] >> 4) + '0'; //“時”
- str[1] = (time[2]&0x0F) + '0';
- str[2] = ':'; //添加時間分隔符
- str[3] = (time[1] >> 4) + '0'; //“分”
- str[4] = (time[1]&0x0F) + '0';
- str[5] = ':';
- str[6] = (time[0] >> 4) + '0'; //“秒”
- str[7] = (time[0]&0x0F) + '0';
- str[8] = '\0';
- LcdShowStr(4, 1, str); //顯示到液晶的第二行
- psec = time[0]; //用當(dāng)前值更新上次秒數(shù)
- }
- }
- }
- }
- /* 發(fā)送一個字節(jié)到 DS1302 通信總線上 */
- void DS1302ByteWrite(unsigned char dat){
- unsigned char mask;
-
- for (mask=0x01; mask!=0; mask<>//低位在前,逐位移出
- if ((mask&dat) != 0){ //首先輸出該位數(shù)據(jù)
- DS1302_IO = 1;
- }else{
- DS1302_IO = 0;
- }
- DS1302_CK = 1; //然后拉高時鐘
- DS1302_CK = 0; //再拉低時鐘,,完成一個位的操作
- }
- DS1302_IO = 1; //最后確保釋放 IO 引腳
- }
- /* 由 DS1302 通信總線上讀取一個字節(jié) */
- unsigned char DS1302ByteRead(){
- unsigned char mask;
- unsigned char dat = 0;
-
- for (mask=0x01; mask!=0; mask<>//低位在前,,逐位讀取
- if (DS1302_IO != 0){ //首先讀取此時的 IO 引腳,并設(shè)置 dat 中的對應(yīng)位
- dat |= mask;
- }
- DS1302_CK = 1; //然后拉高時鐘
- DS1302_CK = 0; //再拉低時鐘,,完成一個位的操作
- }
- return dat; //最后返回讀到的字節(jié)數(shù)據(jù)
- }
- /* 用單次寫操作向某一寄存器寫入一個字節(jié),,reg-寄存器地址,dat-待寫入字節(jié) */
- void DS1302SingleWrite(unsigned char reg, unsigned char dat){
- DS1302_CE = 1; //使能片選信號
- DS1302ByteWrite((reg<>//發(fā)送寫寄存器指令
- DS1302ByteWrite(dat); //寫入字節(jié)數(shù)據(jù)
- DS1302_CE = 0; //除能片選信號
- }
- /* 用單次讀操作從某一寄存器讀取一個字節(jié),,reg-寄存器地址,,返回值-讀到的字節(jié) */
- unsigned char DS1302SingleRead(unsigned char reg){
- unsigned char dat;
- DS1302_CE = 1; //使能片選信號
- DS1302ByteWrite((reg<>//發(fā)送讀寄存器指令
- dat = DS1302ByteRead()//讀取字節(jié)數(shù)據(jù)
- DS1302_CE = 0; //除能片選信號
- return dat;
- }
- /* DS1302 初始化,如發(fā)生掉電則重新設(shè)置初始時間 */
- void InitDS1302(){
- unsigned char i;
- unsigned char code InitTime[] = { //2013 年 10 月 8 日 星期二 12:30:00
- 0x00,0x30,0x12, 0x08, 0x10, 0x02, 0x13
- };
-
- DS1302_CE = 0; //初始化 DS1302 通信引腳
- DS1302_CK = 0;
- i = DS1302SingleRead(0); //讀取秒寄存器
-
- if ((i & 0x80) != 0){ //由秒寄存器最高位 CH 的值判斷 DS1302 是否已停止
- DS1302SingleWrite(7, 0x00); //撤銷寫保護以允許寫入數(shù)據(jù)
- for (i=0; i<>//設(shè)置 DS1302 為默認(rèn)的初始時間
- DS1302SingleWrite(i, InitTime[i]);
- }
- }
- }
- /* 配置并啟動 T0,,ms-T0 定時時間 */
- void ConfigTimer0(unsigned int ms){
- unsigned long tmp; //臨時變量
- tmp = 11059200 / 12; //定時器計數(shù)頻率
- tmp = (tmp * ms) / 1000; //計算所需的計數(shù)值
- tmp = 65536 - tmp; //計算定時器重載值
- tmp = tmp + 12; //補償中斷響應(yīng)延時造成的誤差
- T0RH = (unsigned char)(tmp>>8); //定時器重載值拆分為高低字節(jié)
- T0RL = (unsigned char)tmp;
- TMOD &= 0xF0; //清零 T0 的控制位
- TMOD |= 0x01; //配置 T0 為模式 1
- TH0 = T0RH; //加載 T0 重載值
- TL0 = T0RL;
- ET0 = 1; //使能 T0 中斷
- TR0 = 1; //啟動 T0
- }
- /* T0 中斷服務(wù)函數(shù),,執(zhí)行 200ms 定時 */
- void InterruptTimer0() interrupt 1{
- static unsigned char tmr200ms = 0;
- TH0 = T0RH; //重新加載重載值
- TL0 = T0RL;
- tmr200ms++;
- if (tmr200ms >= 200){ //定時 200ms
- tmr200ms = 0;
- flag200ms = 1;
- }
- }
/*****************************main.c 文件程序源代碼******************************/#include sbit DS1302_CE = P1^7;sbit DS1302_CK = P3^5;sbit DS1302_IO = P3^4;bit flag200ms = 0; //200ms 定時標(biāo)志unsigned char T0RH = 0; //T0 重載值的高字節(jié)unsigned char T0RL = 0; //T0 重載值的低字節(jié)void ConfigTimer0(unsigned int ms);void InitDS1302();unsigned char DS1302SingleRead(unsigned char reg);extern void InitLcd1602();extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);void main(){ unsigned char i; unsigned char psec=0xAA; //秒備份,初值 AA 確保首次讀取時間后會刷新顯示 unsigned char time[8]; //當(dāng)前時間數(shù)組 unsigned char str[12]; //字符串轉(zhuǎn)換緩沖區(qū) EA = 1; //開總中斷 ConfigTimer0(1); //T0 定時 1ms InitDS1302(); //初始化實時時鐘 InitLcd1602(); //初始化液晶 while (1){ if (flag200ms){ //每 200ms 讀取一次時間 flag200ms = 0; for (i=0; i<7; i++){="" 讀取="" ds1302="" 當(dāng)前時間="" time[i]="DS1302SingleRead(i);" }="" if="" (psec="" !="time[0]){" 檢測到時間有變化時刷新顯示="" str[0]='2' ;="" 添加年份的高="" 2="" 位:20="" str[1]='0' ;="" str[2]="(time[6]">> 4) + '0'; //“年”高位數(shù)字轉(zhuǎn)換為 ASCII 碼 str[3] = (time[6]&0x0F) + '0'; //“年”低位數(shù)字轉(zhuǎn)換為 ASCII 碼 str[4] = '-'; //添加日期分隔符 str[5] = (time[4] >> 4) + '0'; //“月” str[6] = (time[4]&0x0F) + '0'; str[7] = '-'; str[8] = (time[3] >> 4) + '0'; //“日” str[9] = (time[3]&0x0F) + '0'; str[10] = '\0'; LcdShowStr(0, 0, str); //顯示到液晶的第一行 str[0] = (time[5]&0x0F) + '0'; //“星期” str[1] = '\0'; LcdShowStr(11, 0, 'week'); LcdShowStr(15, 0, str); //顯示到液晶的第一行 str[0] = (time[2] >> 4) + '0'; //“時” str[1] = (time[2]&0x0F) + '0'; str[2] = ':'; //添加時間分隔符 str[3] = (time[1] >> 4) + '0'; //“分” str[4] = (time[1]&0x0F) + '0'; str[5] = ':'; str[6] = (time[0] >> 4) + '0'; //“秒” str[7] = (time[0]&0x0F) + '0'; str[8] = '\0'; LcdShowStr(4, 1, str); //顯示到液晶的第二行 psec = time[0]; //用當(dāng)前值更新上次秒數(shù) } } }}/* 發(fā)送一個字節(jié)到 DS1302 通信總線上 */void DS1302ByteWrite(unsigned char dat){ unsigned char mask; for (mask=0x01; mask!=0; mask<=1){ 低位在前,,逐位移出="" if="" ((mask&dat)="" !="0){" 首先輸出該位數(shù)據(jù)="" ds1302_io="1;" }else{="" ds1302_io="0;" }="" ds1302_ck="1;" 然后拉高時鐘="" ds1302_ck="0;" 再拉低時鐘,,完成一個位的操作="" }="" ds1302_io="1;" 最后確保釋放="" io="" 引腳}/*="" 由="" ds1302="" 通信總線上讀取一個字節(jié)="" */unsigned="" char="" ds1302byteread(){="" unsigned="" char="" mask;="" unsigned="" char="" dat="0;" for="" (mask="0x01;" mask!="0;">=1){><=1){ 低位在前,逐位讀取="" if="" (ds1302_io="" !="0){" 首先讀取此時的="" io="" 引腳,,并設(shè)置="" dat="" 中的對應(yīng)位="" dat="" |="mask;" }="" ds1302_ck="1;" 然后拉高時鐘="" ds1302_ck="0;" 再拉低時鐘,,完成一個位的操作="" }="" return="" dat;="" 最后返回讀到的字節(jié)數(shù)據(jù)}/*="" 用單次寫操作向某一寄存器寫入一個字節(jié),reg-寄存器地址,,dat-待寫入字節(jié)="" */void="" ds1302singlewrite(unsigned="" char="" reg,="" unsigned="" char="" dat){="" ds1302_ce="1;" 使能片選信號="">=1){><1)|0x80); 發(fā)送寫寄存器指令="" ds1302bytewrite(dat);="" 寫入字節(jié)數(shù)據(jù)="" ds1302_ce="0;" 除能片選信號}/*="" 用單次讀操作從某一寄存器讀取一個字節(jié),,reg-寄存器地址,返回值-讀到的字節(jié)="" */unsigned="" char="" ds1302singleread(unsigned="" char="" reg){="" unsigned="" char="" dat;="" ds1302_ce="1;" 使能片選信號="">1)|0x80);><1)|0x81); 發(fā)送讀寄存器指令="" dat="DS1302ByteRead()//讀取字節(jié)數(shù)據(jù)" ds1302_ce="0;" 除能片選信號="" return="" dat;}/*="" ds1302="" 初始化,如發(fā)生掉電則重新設(shè)置初始時間="" */void="" initds1302(){="" unsigned="" char="" i;="" unsigned="" char="" code="" inittime[]="{" 2013="" 年="" 10="" 月="" 8="" 日="" 星期二="" 12:30:00="" 0x00,0x30,0x12,="" 0x08,="" 0x10,="" 0x02,="" 0x13="" };="" ds1302_ce="0;" 初始化="" ds1302="" 通信引腳="" ds1302_ck="0;" i="DS1302SingleRead(0);" 讀取秒寄存器="" if="" ((i="" &="" 0x80)="" !="0){" 由秒寄存器最高位="" ch="" 的值判斷="" ds1302="" 是否已停止="" ds1302singlewrite(7,="" 0x00);="" 撤銷寫保護以允許寫入數(shù)據(jù)="" for="" (i="0;">1)|0x81);><7; i++){="" 設(shè)置="" ds1302="" 為默認(rèn)的初始時間="" ds1302singlewrite(i,="" inittime[i]);="" }="" }}/*="" 配置并啟動="" t0,,ms-t0="" 定時時間="" */void="" configtimer0(unsigned="" int="" ms){="" unsigned="" long="" tmp;="" 臨時變量="" tmp="11059200" 12;="" 定時器計數(shù)頻率="" tmp="(tmp" *="" ms)="" 1000;="" 計算所需的計數(shù)值="" tmp="65536" -="" tmp;="" 計算定時器重載值="" tmp="tmp" +="" 12;="" 補償中斷響應(yīng)延時造成的誤差="" t0rh="(unsigned" char)(tmp="">>8); //定時器重載值拆分為高低字節(jié) T0RL = (unsigned char)tmp; TMOD &= 0xF0; //清零 T0 的控制位 TMOD |= 0x01; //配置 T0 為模式 1 TH0 = T0RH; //加載 T0 重載值 TL0 = T0RL; ET0 = 1; //使能 T0 中斷 TR0 = 1; //啟動 T0}/* T0 中斷服務(wù)函數(shù),,執(zhí)行 200ms 定時 */void InterruptTimer0() interrupt 1{ static unsigned char tmr200ms = 0; TH0 = T0RH; //重新加載重載值 TL0 = T0RL; tmr200ms++; if (tmr200ms >= 200){ //定時 200ms tmr200ms = 0; flag200ms = 1; }}7;>7;> 前邊學(xué)習(xí)了 I2C 和 EEPROM 的底層讀寫時序,那么 DS1302 的底層讀寫時序程序的實現(xiàn)方法是與之類似的,,這里就不過多解釋了,,大家自己認(rèn)真揣摩一下。
|