本例直接忽略了星期這項內(nèi)容,通過上、下、左、右、回車、ESC 這 6 個按鍵可以調(diào)整時間。這也是一個具有綜合練習(xí)性質(zhì)的實例,雖然在功能實現(xiàn)上沒有多少難度,但要進行的操作卻比較多而且煩瑣,同學(xué)們可以從中體會到把繁雜的功能實現(xiàn)分解為一步步函數(shù)操作的必要性以及方便靈活性。簡單說一下這個程序的幾個要點,方便大家閱讀理解程序。
- 把 DS1302 的底層操作封裝為一個 DS1302.c 文件,對上層應(yīng)用提供基本的實時時間的操作接口,這個文件也是我們的又一個功能模塊了,我們的積累也越來越多了。
- 定義一個結(jié)構(gòu)體類型 sTime 用來封裝日期時間的各個元素,又用該結(jié)構(gòu)體定義了一個時間緩沖區(qū)變量 bufTime 來暫存從 DS1302 讀出的時間和設(shè)置時間時的設(shè)定值。需要注意的是在其它文件中要使用這個結(jié)構(gòu)體變量時,必須首先再聲明一次 sTime 類型;
- 定義一個變量 setIndex 來控制當前是否處于設(shè)置時間的狀態(tài),以及設(shè)置時間的哪一位,該值為 0 就表示正常運行,1~12 分別代表可以修改日期時間的 12 個位;
- 由于這節(jié)課的程序功能要進行時間調(diào)整,用到了 1602 液晶的光標功能,添加了設(shè)置光標的函數(shù),我們要改變哪一位的數(shù)字,就在 1602 對應(yīng)位置上進行光標閃爍,所以 Lcd1602.c在之前文件的基礎(chǔ)上添加了兩個控制光標的函數(shù);
- 時間的顯示、增減、設(shè)置移位等上層功能函數(shù)都放在 main.c 中來實現(xiàn),當按鍵需要這些函數(shù)時則在按鍵文件中做外部聲明,這樣做是為了避免一組功能函數(shù)分散在不同的文件內(nèi)而使程序顯得凌亂。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | /***************************DS1302.c 文件程序源代碼*****************************/ #include <reg52.h> sbit DS1302_CE = P1^7; sbit DS1302_CK = P3^5; sbit DS1302_IO = P3^4; struct sTime { //日期時間結(jié)構(gòu)體定義 unsigned int year; //年 unsigned char mon; //月 unsigned char day; //日 unsigned char hour; //時 unsigned char min; //分 unsigned char sec; //秒 unsigned char week; //星期 }; /* 發(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; mask<<=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; //使能片選信號 DS1302ByteWrite((reg<<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; //使能片選信號 DS1302ByteWrite((reg<<1)|0x81); //發(fā)送讀寄存器指令 dat = DS1302ByteRead(); //讀取字節(jié)數(shù)據(jù) DS1302_CE = 0; //除能片選信號 return dat; } /* 用突發(fā)模式連續(xù)寫入 8 個寄存器數(shù)據(jù),dat-待寫入數(shù)據(jù)指針 */ void DS1302BurstWrite(unsigned char *dat){ unsigned char i; DS1302_CE = 1; DS1302ByteWrite(0xBE); //發(fā)送突發(fā)寫寄存器指令 for (i=0; i<8; i++){ //連續(xù)寫入 8 字節(jié)數(shù)據(jù) DS1302ByteWrite(dat[i]); } DS1302_CE = 0; } /* 用突發(fā)模式連續(xù)讀取 8 個寄存器的數(shù)據(jù),dat-讀取數(shù)據(jù)的接收指針 */ void DS1302BurstRead(unsigned char *dat){ unsigned char i; DS1302_CE = 1; DS1302ByteWrite(0xBF); //發(fā)送突發(fā)讀寄存器指令 for (i=0; i<8; i++){ //連續(xù)讀取 8 個字節(jié) dat[i] = DS1302ByteRead(); } DS1302_CE = 0; } /* 獲取實時時間,即讀取 DS1302 當前時間并轉(zhuǎn)換為時間結(jié)構(gòu)體格式 */ 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]; } /* 設(shè)定實時時間,時間結(jié)構(gòu)體格式的設(shè)定時間轉(zhuǎn)換為數(shù)組并寫入 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 初始化,如發(fā)生掉電則重新設(shè)置初始時間 */ 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); //撤銷寫保護以允許寫入數(shù)據(jù) SetRealTime(&InitTime); //設(shè)置 DS1302 為默認的初始時間 } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | /***************************Lcd1602.c 文件程序源代碼*****************************/ #include <reg52.h> #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; //讀取狀態(tài)字 LCD1602_E = 0; } while (sta & 0x80); //bit7 等于 1 表示液晶正忙,重復(fù)檢測直到其等于 0 為止 } /* 向 LCD1602 液晶寫入一字節(jié)命令,cmd-待寫入命令值 */ void LcdWriteCmd(unsigned char cmd){ LcdWaitReady(); LCD1602_RS = 0; LCD1602_RW = 0; LCD1602_DB = cmd; LCD1602_E = 1; LCD1602_E = 0; } /* 向 LCD1602 液晶寫入一字節(jié)數(shù)據(jù),dat-待寫入數(shù)據(jù)值 */ void LcdWriteDat(unsigned char dat){ LcdWaitReady(); LCD1602_RS = 1; LCD1602_RW = 0; LCD1602_DB = dat; LCD1602_E = 1; LCD1602_E = 0; } /* 設(shè)置顯示 RAM 起始地址,亦即光標位置,(x,y)-對應(yīng)屏幕上的字符坐標 */ void LcdSetCursor(unsigned char x, unsigned char y){ unsigned char addr; if (y == 0){ //由輸入的屏幕坐標計算顯示 RAM 的地址 addr = 0x00 + x; //第一行字符地址從 0x00 起始 } else { addr = 0x40 + x; //第二行字符地址從 0x40 起始 } LcdWriteCmd(addr | 0x80); //設(shè)置 RAM 地址 } /* 在液晶上顯示字符串,(x,y)-對應(yīng)屏幕上的起始坐標,str-字符串指針 */ void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str){ LcdSetCursor(x, y); //設(shè)置起始地址 while (*str != '\0' ){ //連續(xù)寫入字符串數(shù)據(jù),直到檢測到結(jié)束符 LcdWriteDat(*str++); } } /* 打開光標的閃爍效果 */ void LcdOpenCursor(){ LcdWriteCmd(0x0F); } /* 關(guān)閉光標顯示 */ void LcdCloseCursor(){ LcdWriteCmd(0x0C); } /* 初始化 1602 液晶 */ void InitLcd1602(){ LcdWriteCmd(0x38); //16*2 顯示,5*7 點陣,8 位數(shù)據(jù)接口 LcdWriteCmd(0x0C); //顯示器開,光標關(guān)閉 LcdWriteCmd(0x06); //文字不動,地址自動+1 LcdWriteCmd(0x01); //清屏 } |
/***************************keyboard.c 文件程序源代碼****************************/
(此處省略,可參考之前章節(jié)的代碼)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | /*****************************main.c 文件程序源代碼******************************/ #include <reg52.h> struct sTime { //日期時間結(jié)構(gòu)體定義 unsigned int year; unsigned char mon; unsigned char day; unsigned char hour; unsigned char min; unsigned char sec; unsigned char week; }; bit flag200ms = 1; //200ms 定時標志 struct sTime bufTime; //日期時間緩沖區(qū) unsigned char setIndex = 0; //時間設(shè)置索引 unsigned char T0RH = 0; //T0 重載值的高字節(jié) unsigned char T0RL = 0; //T0 重載值的低字節(jié) void ConfigTimer0(unsigned int ms); void RefreshTimeShow(); extern void InitDS1302(); extern void GetRealTime( struct sTime * time ); extern void SetRealTime( struct sTime * time ); extern void KeyScan(); extern void KeyDriver(); extern void InitLcd1602(); extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str); extern void LcdSetCursor(unsigned char x, unsigned char y); extern void LcdOpenCursor(); extern void LcdCloseCursor(); void main(){ unsigned char psec=0xAA; //秒備份,初值 AA 確保首次讀取時間后會刷新顯示 EA = 1; //開總中斷 ConfigTimer0(1); //T0 定時 1ms InitDS1302(); //初始化實時時鐘 InitLcd1602(); //初始化液晶 //初始化屏幕上固定不變的內(nèi)容 LcdShowStr(3, 0, "20 - - " ); LcdShowStr(4, 1, " : : " ); while (1){ KeyDriver(); //調(diào)用按鍵驅(qū)動 if (flag200ms && (setIndex == 0)){ //每隔 200ms 且未處于設(shè)置狀態(tài)時, flag200ms = 0; GetRealTime(&bufTime); //獲取當前時間 if (psec != bufTime.sec){ //檢測到時間有變化時刷新顯示 RefreshTimeShow(); psec = bufTime.sec; //用當前值更新上次秒數(shù) } } } } /* 將一個 BCD 碼字節(jié)顯示到屏幕上,(x,y)-屏幕起始坐標,bcd-待顯示 BCD 碼 */ void ShowBcdByte(unsigned char x, unsigned char y, unsigned char bcd){ unsigned char str[4]; str[0] = (bcd >> 4) + '0' ; str[1] = (bcd&0x0F) + '0' ; str[2] = '\0' ; LcdShowStr(x, y, str); } /* 刷新日期時間的顯示 */ void RefreshTimeShow(){ ShowBcdByte(5, 0, bufTime.year); ShowBcdByte(8, 0, bufTime.mon); ShowBcdByte(11, 0, bufTime.day); ShowBcdByte(4, 1, bufTime.hour); ShowBcdByte(7, 1, bufTime.min); ShowBcdByte(10, 1, bufTime.sec); } /* 刷新當前設(shè)置位的光標指示 */ void RefreshSetShow(){ switch (setIndex){ case 1: LcdSetCursor(5, 0); break ; case 2: LcdSetCursor(6, 0); break ; case 3: LcdSetCursor(8, 0); break ; case 4: LcdSetCursor(9, 0); break ; case 5: LcdSetCursor(11, 0); break ; case 6: LcdSetCursor(12, 0); break ; case 7: LcdSetCursor(4, 1); break ; case 8: LcdSetCursor(5, 1); break ; case 9: LcdSetCursor(7, 1); break ; case 10: LcdSetCursor(8, 1); break ; case 11: LcdSetCursor(10, 1); break ; case 12: LcdSetCursor(11, 1); break ; default : break ; } } /* 遞增一個 BCD 碼的高位 */ unsigned char IncBcdHigh(unsigned char bcd){ if ((bcd&0xF0) < 0x90){ bcd += 0x10; } else { bcd &= 0x0F; } return bcd; } /* 遞增一個 BCD 碼的低位 */ unsigned char IncBcdLow(unsigned char bcd){ if ((bcd&0x0F) < 0x09){ bcd += 0x01; } else { bcd &= 0xF0; } return bcd; } /* 遞減一個 BCD 碼的高位 */ unsigned char DecBcdHigh(unsigned char bcd){ if ((bcd&0xF0) > 0x00){ bcd -= 0x10; } else { bcd |= 0x90; } return bcd; } /* 遞減一個 BCD 碼的低位 */ unsigned char DecBcdLow(unsigned char bcd){ if ((bcd&0x0F) > 0x00){ bcd -= 0x01; } else { bcd |= 0x09; } return bcd; } /* 遞增時間當前設(shè)置位的值 */ void IncSetTime(){ switch (setIndex){ case 1: bufTime.year = IncBcdHigh(bufTime.year); break ; case 2: bufTime.year = IncBcdLow(bufTime.year); break ; case 3: bufTime.mon = IncBcdHigh(bufTime.mon); break ; case 4: bufTime.mon = IncBcdLow(bufTime.mon); break ; case 5: bufTime.day = IncBcdHigh(bufTime.day); break ; case 6: bufTime.day = IncBcdLow(bufTime.day); break ; case 7: bufTime.hour = IncBcdHigh(bufTime.hour); break ; case 8: bufTime.hour = IncBcdLow(bufTime.hour); break ; case 9: bufTime.min = IncBcdHigh(bufTime.min); break ; case 10: bufTime.min = IncBcdLow(bufTime.min); break ; case 11: bufTime.sec = IncBcdHigh(bufTime.sec); break ; case 12: bufTime.sec = IncBcdLow(bufTime.sec); break ; default : break ; } RefreshTimeShow(); RefreshSetShow(); } /* 遞減時間當前設(shè)置位的值 */ void DecSetTime(){ switch (setIndex){ case 1: bufTime.year = DecBcdHigh(bufTime.year); break ; case 2: bufTime.year = DecBcdLow(bufTime.year); break ; case 3: bufTime.mon = DecBcdHigh(bufTime.mon); break ; case 4: bufTime.mon = DecBcdLow(bufTime.mon); break ; case 5: bufTime.day = DecBcdHigh(bufTime.day); break ; case 6: bufTime.day = DecBcdLow(bufTime.day); break ; case 7: bufTime.hour = DecBcdHigh(bufTime.hour); break ; case 8: bufTime.hour = DecBcdLow(bufTime.hour); break ; case 9: bufTime.min = DecBcdHigh(bufTime.min); break ; case 10: bufTime.min = DecBcdLow(bufTime.min); break ; case 11: bufTime.sec = DecBcdHigh(bufTime.sec); break ; case 12: bufTime.sec = DecBcdLow(bufTime.sec); default : break ; } RefreshTimeShow(); RefreshSetShow(); } /* 右移時間設(shè)置位 */ void RightShiftTimeSet(){ if (setIndex != 0){ if (setIndex < 12){ setIndex++; } else { setIndex = 1; } RefreshSetShow(); } } /* 左移時間設(shè)置位 */ void LeftShiftTimeSet(){ if (setIndex != 0){ if (setIndex > 1){ setIndex--; } else { setIndex = 12; } RefreshSetShow(); } } /* 進入時間設(shè)置狀態(tài) */ void EnterTimeSet(){ setIndex = 2; //把設(shè)置索引設(shè)置為 2,即可進入設(shè)置狀態(tài) LeftShiftTimeSet(); //再利用現(xiàn)成的左移操作移到位置 1 并完成顯示刷新 LcdOpenCursor(); //打開光標閃爍效果 } /* 退出時間設(shè)置狀態(tài),save-是否保存當前設(shè)置的時間值 */ void ExitTimeSet(bit save){ setIndex = 0; //把設(shè)置索引設(shè)置為 0,即可退出設(shè)置狀態(tài) if (save){ //需保存時即把當前設(shè)置時間寫入 DS1302 SetRealTime(&bufTime); } LcdCloseCursor(); //關(guān)閉光標顯示 } /* 按鍵動作函數(shù),根據(jù)鍵碼執(zhí)行相應(yīng)的操作,keycode-按鍵鍵碼 */ void KeyAction(unsigned char keycode){ if ((keycode>= '0' ) && (keycode<= '9' )){ //本例中不響應(yīng)字符鍵 } else if (keycode == 0x26){ //向上鍵,遞增當前設(shè)置位的值 IncSetTime(); } else if (keycode == 0x28){ //向下鍵,遞減當前設(shè)置位的值 DecSetTime(); } else if (keycode == 0x25){ //向左鍵,向左切換設(shè)置位 LeftShiftTimeSet(); } else if (keycode == 0x27){ //向右鍵,向右切換設(shè)置位 RightShiftTimeSet(); } else if (keycode == 0x0D){ //回車鍵,進入設(shè)置模式/啟用當前設(shè)置值 if (setIndex == 0){ //不處于設(shè)置狀態(tài)時,進入設(shè)置狀態(tài) EnterTimeSet(); } else { //已處于設(shè)置狀態(tài)時,保存時間并退出設(shè)置狀態(tài) ExitTimeSet(1); } } else if (keycode == 0x1B){ //Esc 鍵,取消當前設(shè)置 ExitTimeSet(0); } } /* 配置并啟動 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 + 28; //補償中斷響應(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; KeyScan(); //按鍵掃描 tmr200ms++; if (tmr200ms >= 200){ //定時 200ms tmr200ms = 0; flag200ms = 1; } } |
