本例直接忽略了星期這項內容,通過上、下、左、右、回車、ESC 這 6 個按鍵可以調整時間。這也是一個具有綜合練習性質的實例,雖然在功能實現上沒有多少難度,但要進行的操作卻比較多而且煩瑣,同學們可以從中體會到把繁雜的功能實現分解為一步步函數操作的必要性以及方便靈活性。簡單說一下這個程序的幾個要點,方便大家閱讀理解程序。
把 DS1302 的底層操作封裝為一個 DS1302.c 文件,對上層應用提供基本的實時時間的操作接口,這個文件也是我們的又一個功能模塊了,我們的積累也越來越多了。
定義一個結構體類型 sTime 用來封裝日期時間的各個元素,又用該結構體定義了一個時間緩沖區變量 bufTime 來暫存從 DS1302 讀出的時間和設置時間時的設定值。需要注意的是在其它文件中要使用這個結構體變量時,必須首先再聲明一次 sTime 類型;
定義一個變量 setIndex 來控制當前是否處于設置時間的狀態,以及設置時間的哪一位,該值為 0 就表示正常運行,1~12 分別代表可以修改日期時間的 12 個位;
由于這節課的程序功能要進行時間調整,用到了 1602 液晶的光標功能,添加了設置光標的函數,我們要改變哪一位的數字,就在 1602 對應位置上進行光標閃爍,所以 Lcd1602.c在之前文件的基礎上添加了兩個控制光標的函數;
時間的顯示、增減、設置移位等上層功能函數都放在 main.c 中來實現,當按鍵需要這些函數時則在按鍵文件中做外部聲明,這樣做是為了避免一組功能函數分散在不同的文件內而使程序顯得凌亂。
?
/***************************DS1302.c 文件程序源代碼*****************************/
#include
sbit DS1302_CE = P1^7;
sbit DS1302_CK = P3^5;
sbit DS1302_IO = P3^4;
struct sTime { //日期時間結構體定義
unsigned int year; //年
unsigned char mon; //月
unsigned char day; //日
unsigned char hour; //時
unsigned char min; //分
unsigned char sec; //秒
unsigned char week; //星期
};
/* 發送一個字節到 DS1302 通信總線上 */
void DS1302ByteWrite(unsigned char dat){
unsigned char mask;
for (mask=0x01; mask!=0; mask《《=1){ //低位在前,逐位移出
if ((mask&dat) != 0){ //首先輸出該位數據
DS1302_IO = 1;
}else{
DS1302_IO = 0;
}
DS1302_CK = 1; //然后拉高時鐘
DS1302_CK = 0; //再拉低時鐘,完成一個位的操作
}
DS1302_IO = 1; //最后確保釋放 IO 引腳
}
/* 由 DS1302 通信總線上讀取一個字節 */
unsigned char DS1302ByteRead(){
unsigned char mask;
unsigned char dat = 0;
for (mask=0x01; mask!=0; mask《《=1){ //低位在前,逐位讀取
if (DS1302_IO != 0){ //首先讀取此時的 IO 引腳,并設置 dat 中的對應位
dat |= mask;
}
DS1302_CK = 1; //然后拉高時鐘
DS1302_CK = 0; //再拉低時鐘,完成一個位的操作
}
return dat; //最后返回讀到的字節數據
}
/* 用單次寫操作向某一寄存器寫入一個字節,reg-寄存器地址,dat-待寫入字節 */
void DS1302SingleWrite(unsigned char reg, unsigned char dat){
DS1302_CE = 1; //使能片選信號
DS1302ByteWrite((reg《《1)|0x80); //發送寫寄存器指令
DS1302ByteWrite(dat); //寫入字節數據
DS1302_CE = 0; //除能片選信號
}
/* 用單次讀操作從某一寄存器讀取一個字節,reg-寄存器地址,返回值-讀到的字節 */
unsigned char DS1302SingleRead(unsigned char reg){
unsigned char dat;
DS1302_CE = 1; //使能片選信號
DS1302ByteWrite((reg《《1)|0x81); //發送讀寄存器指令
dat = DS1302ByteRead(); //讀取字節數據
DS1302_CE = 0; //除能片選信號
return dat;
}
/* 用突發模式連續寫入 8 個寄存器數據,dat-待寫入數據指針 */
void DS1302BurstWrite(unsigned char *dat){
unsigned char i;
DS1302_CE = 1;
DS1302ByteWrite(0xBE); //發送突發寫寄存器指令
for (i=0; i《8; i++){ //連續寫入 8 字節數據
DS1302ByteWrite(dat[i]);
}
DS1302_CE = 0;
}
/* 用突發模式連續讀取 8 個寄存器的數據,dat-讀取數據的接收指針 */
void DS1302BurstRead(unsigned char *dat){
unsigned char i;
DS1302_CE = 1;
DS1302ByteWrite(0xBF); //發送突發讀寄存器指令
for (i=0; i《8; i++){ //連續讀取 8 個字節
dat[i] = DS1302ByteRead();
}
DS1302_CE = 0;
}
/* 獲取實時時間,即讀取 DS1302 當前時間并轉換為時間結構體格式 */
void GetRealTime(struct sTime *time){
unsigned char buf[8];
DS1302BurstRead(buf);
time-》year = buf[6] + 0x2000;
time-》mon = buf[4];
time-》day = buf[3];
time-》hour = buf[2];
time-》min = buf[1];
time-》sec = buf[0];
time-》week = buf[5];
}
/* 設定實時時間,時間結構體格式的設定時間轉換為數組并寫入 DS1302 */
void SetRealTime(struct sTime *time){
unsigned char buf[8];
buf[7] = 0;
buf[6] = time-》year;
buf[5] = time-》week;
buf[4] = time-》mon;
buf[3] = time-》day;
buf[2] = time-》hour;
buf[1] = time-》min;
buf[0] = time-》sec;
DS1302BurstWrite(buf);
}
/* DS1302 初始化,如發生掉電則重新設置初始時間 */
void InitDS1302(){
unsigned char dat;
struct sTime code InitTime[] = { //2013 年 10 月 8 日 12:30:00 星期二
0x2013,0x10,0x08, 0x12,0x30,0x00, 0x02
};
DS1302_CE = 0; //初始化 DS1302 通信引腳
DS1302_CK = 0;
dat = DS1302SingleRead(0); //讀取秒寄存器
if ((dat & 0x80) != 0){ //由秒寄存器最高位 CH 的值判斷 DS1302 是否已停止
DS1302SingleWrite(7, 0x00); //撤銷寫保護以允許寫入數據
SetRealTime(&InitTime); //設置 DS1302 為默認的初始時間
}
}
DS1302.c 最終向外提供出與具體時鐘芯片寄存器位置無關的、由時間結構類型 sTime 作為接口的實時時間的讀取和設置函數,如此處理體現了我們前面提到過的層次化編程的思想。應用層可以不關心底層實現細節,底層實現的改變也不會對應用層造成影響,比如說日后你可能需要換一款時鐘芯片,而它與 DS1302 的操作和時間寄存器順序是不同的,那么你需要做的也僅是針對這款新的時鐘芯片設計出底層操作函數,最終提供出同樣的以 sTime 為接口的操作函數即可,應用層無需做任何的改動。
/***************************Lcd1602.c 文件程序源代碼*****************************/
#include
#define LCD1602_DB P0
sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E = P1^5;
/* 等待液晶準備好 */
void LcdWaitReady(){
unsigned char sta;
LCD1602_DB = 0xFF;
LCD1602_RS = 0;
LCD1602_RW = 1;
do {
LCD1602_E = 1;
sta = LCD1602_DB; //讀取狀態字
LCD1602_E = 0;
} while (sta & 0x80); //bit7 等于 1 表示液晶正忙,重復檢測直到其等于 0 為止
}
/* 向 LCD1602 液晶寫入一字節命令,cmd-待寫入命令值 */
void LcdWriteCmd(unsigned char cmd){
LcdWaitReady();
LCD1602_RS = 0;
LCD1602_RW = 0;
LCD1602_DB = cmd;
LCD1602_E = 1;
LCD1602_E = 0;
}
/* 向 LCD1602 液晶寫入一字節數據,dat-待寫入數據值 */
void LcdWriteDat(unsigned char dat){
LcdWaitReady();
LCD1602_RS = 1;
LCD1602_RW = 0;
LCD1602_DB = dat;
LCD1602_E = 1;
LCD1602_E = 0;
}
/* 設置顯示 RAM 起始地址,亦即光標位置,(x,y)-對應屏幕上的字符坐標 */
void LcdSetCursor(unsigned char x, unsigned char y){
unsigned char addr;
if (y == 0){ //由輸入的屏幕坐標計算顯示 RAM 的地址
addr = 0x00 + x; //第一行字符地址從 0x00 起始
}else{
評論