新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > 第46節:利用AT24C02進行掉電后的數據保存

        第46節:利用AT24C02進行掉電后的數據保存

        作者: 時間:2016-11-22 來源:網絡 收藏
        開場白:
        一個AT24C02可以存儲256個字節,地址范圍是(0至255)。利用AT24C02存儲數據時,要教會大家六個知識點:
        第一個:單片機操作AT24C02的通訊過程也就是IIC的通訊過程, IIC通訊過程是一個要求一氣呵成的通訊過程,中間不能被其它中斷影響時序出錯,因此在整個通訊過程中應該先關閉總中斷,完成之后再開中斷。
        第二個:在寫入或者讀取完一個字節之后,一定要加上一段延時時間。在11.0592M晶振的系統中,寫入數據時經驗值用delay_short(2000),讀取數據時經驗值用delay_short(800)。否則在連續寫入或者讀取一串數據時容易丟失數據。如果一旦發現丟失數據,應該適當繼續把這個時間延長,尤其是在寫入數據時。
        第三個:如何初始化EEPROM數據的方法。系統第一次上電時,我們從EEPROM讀取出來的數據有可能超出了范圍,可能是ff。這個時候我們應該給它填入一個初始化的數據,這一步千萬別漏了。
        第四個:在時序中,發送ACK確認信號時,要記得把數據線eeprom_sda_dr_s設置為輸入的狀態。對于51單片機來說,只要把eeprom_sda_dr_s=1就可以。而對于PIC或者AVR單片機來說,它們都是帶方向寄存器的,就不能直接eeprom_sda_dr_s=1,而要直接修改方向寄存器,把它設置為輸入狀態。在本驅動程序中,我沒有對ACK信號進行出錯判斷,因為我這么多年一直都是這樣用也沒出現過什么問題。
        第五個: 提醒各位讀者在硬件上應該注意的問題,單片機跟AT24C02通訊的2根IO口都要加上一個4.7K左右的上拉電阻。凡是在IIC通訊場合,都要加上拉電阻。AT24C02的WP引腳一定要接地,否則存不進數據。
        第六個:舊版的朱兆祺51學習板在硬件上有一個bug,AT24C02的第8個引腳VCC懸空了!!!,讀者記得把它飛線連接到5V電源處。新版的朱兆祺51學習板已經改過來了。
        具體內容,請看源代碼講解。

        (1)硬件平臺:
        基于朱兆祺51單片機學習板

        (2)實現功能:
        4個被更改后的參數斷電后不丟失,數據可以保存,斷電再上電后還是上一次最新被修改的數據。
        顯示和獨立按鍵部分根據第29節的程序來改編,用朱兆祺51單片機學習板中的S1,S5,S9作為獨立按鍵。
        一共有4個窗口。每個窗口顯示一個參數。
        第8,7,6,5位數碼管顯示當前窗口,P-1代表第1個窗口,P-2代表第2個窗口,P-3代表第3個窗口,P-4代表第1個窗口。
        第4,3,2,1位數碼管顯示當前窗口被設置的參數。范圍是從0到9999。S1是加按鍵,按下此按鍵會依次增加當前窗口的參數。S5是減按鍵,按下此按鍵會依次減少當前窗口的參數。S9是切換窗口按鍵,按下此按鍵會依次循環切換不同的窗口。
        (3)源代碼講解如下:
        1. #include "REG52.H"
        2. #define const_voice_short40 //蜂鳴器短叫的持續時間
        3. #define const_key_time120 //按鍵去抖動延時的時間
        4. #define const_key_time220 //按鍵去抖動延時的時間
        5. #define const_key_time320 //按鍵去抖動延時的時間
        6. void initial_myself(void);
        7. void initial_peripheral(void);
        8. void delay_short(unsigned int uiDelayShort);
        9. void delay_long(unsigned int uiDelaylong);
        10. //驅動數碼管的74HC595
        11. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
        12. void display_drive(void); //顯示數碼管字模的驅動函數
        13. void display_service(void); //顯示的窗口菜單服務程序
        14. //驅動LED的74HC595
        15. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
        16. void start24(void);//開始位
        17. void ack24(void);//確認位
        18. void stop24(void);//停止位
        19. unsigned char read24(void);//讀取一個字節的時序
        20. void write24(unsigned char dd); //發送一個字節的時序
        21. unsigned char read_eeprom(unsigned int address); //從一個地址讀取出一個字節數據
        22. void write_eeprom(unsigned int address,unsigned char dd); //往一個地址存入一個字節數據
        23. unsigned int read_eeprom_int(unsigned int address); //從一個地址讀取出一個int類型的數據
        24. void write_eeprom_int(unsigned int address,unsigned int uiWriteData); //往一個地址存入一個int類型的數據
        25. void T0_time(void);//定時中斷函數
        26. void key_service(void); //按鍵服務的應用程序
        27. void key_scan(void);//按鍵掃描函數 放在定時中斷里
        28. sbit key_sr1=P0^0; //對應朱兆祺學習板的S1鍵
        29. sbit key_sr2=P0^1; //對應朱兆祺學習板的S5鍵
        30. sbit key_sr3=P0^2; //對應朱兆祺學習板的S9鍵
        31. sbit key_gnd_dr=P0^4; //模擬獨立按鍵的地GND,因此必須一直輸出低電平
        32. sbit beep_dr=P2^7; //蜂鳴器的驅動IO口
        33. sbit eeprom_scl_dr=P3^7; //時鐘線
        34. sbit eeprom_sda_dr_sr=P3^6; //數據的輸出線和輸入線
        35. sbit dig_hc595_sh_dr=P2^0; //數碼管的74HC595程序
        36. sbit dig_hc595_st_dr=P2^1;
        37. sbit dig_hc595_ds_dr=P2^2;
        38. sbit hc595_sh_dr=P2^3; //LED燈的74HC595程序
        39. sbit hc595_st_dr=P2^4;
        40. sbit hc595_ds_dr=P2^5;
        41. unsigned char ucKeySec=0; //被觸發的按鍵編號
        42. unsigned intuiKeyTimeCnt1=0; //按鍵去抖動延時計數器
        43. unsigned char ucKeyLock1=0; //按鍵觸發后自鎖的變量標志
        44. unsigned intuiKeyTimeCnt2=0; //按鍵去抖動延時計數器
        45. unsigned char ucKeyLock2=0; //按鍵觸發后自鎖的變量標志
        46. unsigned intuiKeyTimeCnt3=0; //按鍵去抖動延時計數器
        47. unsigned char ucKeyLock3=0; //按鍵觸發后自鎖的變量標志
        48. unsigned intuiVoiceCnt=0;//蜂鳴器鳴叫的持續時間計數器
        49. unsigned charucVoiceLock=0;//蜂鳴器鳴叫的原子鎖
        50. unsigned char ucDigShow8;//第8位數碼管要顯示的內容
        51. unsigned char ucDigShow7;//第7位數碼管要顯示的內容
        52. unsigned char ucDigShow6;//第6位數碼管要顯示的內容
        53. unsigned char ucDigShow5;//第5位數碼管要顯示的內容
        54. unsigned char ucDigShow4;//第4位數碼管要顯示的內容
        55. unsigned char ucDigShow3;//第3位數碼管要顯示的內容
        56. unsigned char ucDigShow2;//第2位數碼管要顯示的內容
        57. unsigned char ucDigShow1;//第1位數碼管要顯示的內容
        58. unsigned char ucDigDot8;//數碼管8的小數點是否顯示的標志
        59. unsigned char ucDigDot7;//數碼管7的小數點是否顯示的標志
        60. unsigned char ucDigDot6;//數碼管6的小數點是否顯示的標志
        61. unsigned char ucDigDot5;//數碼管5的小數點是否顯示的標志
        62. unsigned char ucDigDot4;//數碼管4的小數點是否顯示的標志
        63. unsigned char ucDigDot3;//數碼管3的小數點是否顯示的標志
        64. unsigned char ucDigDot2;//數碼管2的小數點是否顯示的標志
        65. unsigned char ucDigDot1;//數碼管1的小數點是否顯示的標志
        66. unsigned char ucDigShowTemp=0; //臨時中間變量
        67. unsigned char ucDisplayDriveStep=1;//動態掃描數碼管的步驟變量
        68. unsigned char ucWd1Update=1; //窗口1更新顯示標志
        69. unsigned char ucWd2Update=0; //窗口2更新顯示標志
        70. unsigned char ucWd3Update=0; //窗口3更新顯示標志
        71. unsigned char ucWd4Update=0; //窗口4更新顯示標志
        72. unsigned char ucWd=1;//本程序的核心變量,窗口顯示變量。類似于一級菜單的變量。代表顯示不同的窗口。
        73. unsigned intuiSetData1=0;//本程序中需要被設置的參數1
        74. unsigned intuiSetData2=0;//本程序中需要被設置的參數2
        75. unsigned intuiSetData3=0;//本程序中需要被設置的參數3
        76. unsigned intuiSetData4=0;//本程序中需要被設置的參數4
        77. unsigned char ucTemp1=0;//中間過渡變量
        78. unsigned char ucTemp2=0;//中間過渡變量
        79. unsigned char ucTemp3=0;//中間過渡變量
        80. unsigned char ucTemp4=0;//中間過渡變量
        81. //根據原理圖得出的共陰數碼管字模表
        82. code unsigned char dig_table[]=
        83. {
        84. 0x3f,//0 序號0
        85. 0x06,//1 序號1
        86. 0x5b,//2 序號2
        87. 0x4f,//3 序號3
        88. 0x66,//4 序號4
        89. 0x6d,//5 序號5
        90. 0x7d,//6 序號6
        91. 0x07,//7 序號7
        92. 0x7f,//8 序號8
        93. 0x6f,//9 序號9
        94. 0x00,//無 序號10
        95. 0x40,//- 序號11
        96. 0x73,//P 序號12
        97. };
        98. void main()
        99. {
        100. initial_myself();
        101. delay_long(100);
        102. initial_peripheral();
        103. while(1)
        104. {
        105. key_service(); //按鍵服務的應用程序
        106. display_service(); //顯示的窗口菜單服務程序
        107. }
        108. }
        109. //AT24C02驅動程序
        110. void start24(void)//開始位
        111. {
        112. eeprom_sda_dr_sr=1;
        113. eeprom_scl_dr=1;
        114. delay_short(15);
        115. eeprom_sda_dr_sr=0;
        116. delay_short(15);
        117. eeprom_scl_dr=0;
        118. }
        119. void ack24(void)//確認位時序
        120. {
        121. eeprom_sda_dr_sr=1; //51單片機在讀取數據之前要先置一,表示數據輸入
        122. eeprom_scl_dr=1;
        123. delay_short(15);
        124. eeprom_scl_dr=0;
        125. delay_short(15);
        126. //在本驅動程序中,我沒有對ACK信號進行出錯判斷,因為我這么多年一直都是這樣用也沒出現過什么問題。
        127. //有興趣的朋友可以自己增加出錯判斷,不一定非要按我的方式去做。
        128. }
        129. void stop24(void)//停止位
        130. {
        131. eeprom_sda_dr_sr=0;
        132. eeprom_scl_dr=1;
        133. delay_short(15);
        134. eeprom_sda_dr_sr=1;
        135. }
        136. unsigned char read24(void)//讀取一個字節的時序
        137. {
        138. unsigned char outdata,tempdata;
        139. outdata=0;
        140. eeprom_sda_dr_sr=1; //51單片機的IO口在讀取數據之前要先置一,表示數據輸入
        141. delay_short(2);
        142. for(tempdata=0;tempdata<8;tempdata++)
        143. {
        144. eeprom_scl_dr=0;
        145. delay_short(2);
        146. eeprom_scl_dr=1;
        147. delay_short(2);
        148. outdata<<=1;
        149. if(eeprom_sda_dr_sr==1)outdata++;
        150. eeprom_sda_dr_sr=1; //51單片機的IO口在讀取數據之前要先置一,表示數據輸入
        151. delay_short(2);
        152. }
        153. return(outdata);
        154. }
        155. void write24(unsigned char dd) //發送一個字節的時序
        156. {
        157. unsigned char tempdata;
        158. for(tempdata=0;tempdata<8;tempdata++)
        159. {
        160. if(dd>=0x80)eeprom_sda_dr_sr=1;
        161. else eeprom_sda_dr_sr=0;
        162. dd<<=1;
        163. delay_short(2);
        164. eeprom_scl_dr=1;
        165. delay_short(4);
        166. eeprom_scl_dr=0;
        167. }
        168. }
        169. unsigned char read_eeprom(unsigned int address) //從一個地址讀取出一個字節數據
        170. {
        171. unsigned char dd,cAddress;
        172. cAddress=address; //把低字節地址傳遞給一個字節變量。
        173. /* 注釋一:
        174. * IIC通訊過程是一個要求一氣呵成的通訊過程,中間不能被其它中斷影響時序出錯,因此
        175. * 在整個通訊過程中應該先關閉總中斷,完成之后再開中斷。但是,這樣就會引起另外一個新
        176. * 問題,如果關閉總中斷的時間太長,會導致動態數碼管不能及時均勻的掃描,在操作EEPROM時,
        177. * 數碼管就會出現閃爍的現象,解決這個問題最好的辦法就是在做項目中盡量不要用動態掃描數碼管
        178. * 的方案,應該用靜態顯示的方案。那么程序上還有沒有改善的方法?有的,下一節我會講這個問題
        179. * 的改善方法。
        180. */
        181. EA=0; //禁止中斷
        182. start24(); //IIC通訊開始
        183. write24(0xA0); //此字節包含讀寫指令和芯片地址兩方面的內容。
        184. //指令為寫指令。地址為"000"的信息,此信息由A0,A1,A2的引腳決定
        185. ack24(); //發送應答信號
        186. write24(cAddress); //發送讀取的存儲地址(范圍是0至255)
        187. ack24(); //發送應答信號
        188. start24(); //開始
        189. write24(0xA1); //此字節包含讀寫指令和芯片地址兩方面的內容。
        190. //指令為讀指令。地址為"000"的信息,此信息由A0,A1,A2的引腳決定
        191. ack24(); //發送應答信號
        192. dd=read24(); //讀取一個字節
        193. ack24(); //發送應答信號
        194. stop24();//停止
        195. /* 注釋二:
        196. * 在寫入或者讀取完一個字節之后,一定要加上一段延時時間。在11.0592M晶振的系統中,
        197. * 寫入數據時經驗值用delay_short(2000),讀取數據時經驗值用delay_short(800)。
        198. * 否則在連續寫入或者讀取一串數據時容易丟失數據。如果一旦發現丟失數據,
        199. * 應該適當繼續把這個時間延長,尤其是在寫入數據時。
        200. */
        201. delay_short(800);//此處最關鍵,此處的延時時間一定要,而且要足夠長,此處也是導致動態數碼管閃爍的根本原因
        202. EA=1; //允許中斷
        203. return(dd);
        204. }
        205. void write_eeprom(unsigned int address,unsigned char dd) //往一個地址存入一個字節數據
        206. {
        207. unsigned char cAddress;
        208. cAddress=address; //把低字節地址傳遞給一個字節變量。
        209. EA=0; //禁止中斷
        210. start24(); //IIC通訊開始
        211. write24(0xA0); //此字節包含讀寫指令和芯片地址兩方面的內容。
        212. //指令為寫指令。地址為"000"的信息,此信息由A0,A1,A2的引腳決定
        213. ack24(); //發送應答信號
        214. write24(cAddress); //發送寫入的存儲地址(范圍是0至255)
        215. ack24(); //發送應答信號
        216. write24(dd);//寫入存儲的數據
        217. ack24(); //發送應答信號
        218. stop24();//停止
        219. delay_short(2000);//此處最關鍵,此處的延時時間一定要,而且要足夠長,此處也是導致動態數碼管閃爍的根本原因
        220. EA=1; //允許中斷
        221. }
        222. unsigned int read_eeprom_int(unsigned int address) //從一個地址讀取出一個int類型的數據
        223. {
        224. unsigned char ucReadDataH;
        225. unsigned char ucReadDataL;
        226. unsigned intuiReadDate;
        227. ucReadDataH=read_eeprom(address); //讀取高字節
        228. ucReadDataL=read_eeprom(address+1);//讀取低字節
        229. uiReadDate=ucReadDataH;//把兩個字節合并成一個int類型數據
        230. uiReadDate=uiReadDate<<8;
        231. uiReadDate=uiReadDate+ucReadDataL;
        232. return uiReadDate;
        233. }
        234. void write_eeprom_int(unsigned int address,unsigned int uiWriteData) //往一個地址存入一個int類型的數據
        235. {
        236. unsigned char ucWriteDataH;
        237. unsigned char ucWriteDataL;
        238. ucWriteDataH=uiWriteData>>8;
        239. ucWriteDataL=uiWriteData;
        240. write_eeprom(address,ucWriteDataH); //存入高字節
        241. write_eeprom(address+1,ucWriteDataL); //存入低字節
        242. }
        243. void display_service(void) //顯示的窗口菜單服務程序
        244. {
        245. switch(ucWd)//本程序的核心變量,窗口顯示變量。類似于一級菜單的變量。代表顯示不同的窗口。
        246. {
        247. case 1: //顯示P--1窗口的數據
        248. if(ucWd1Update==1)//窗口1要全部更新顯示
        249. {
        250. ucWd1Update=0;//及時清零標志,避免一直進來掃描
        251. ucDigShow8=12;//第8位數碼管顯示P
        252. ucDigShow7=11;//第7位數碼管顯示-
        253. ucDigShow6=1; //第6位數碼管顯示1
        254. ucDigShow5=10;//第5位數碼管顯示無
        255. //先分解數據
        256. ucTemp4=uiSetData1/1000;
        257. ucTemp3=uiSetData1%1000/100;
        258. ucTemp2=uiSetData1%100/10;
        259. ucTemp1=uiSetData1%10;
        260. //再過渡需要顯示的數據到緩沖變量里,讓過渡的時間越短越好
        261. if(uiSetData1<1000)
        262. {
        263. ucDigShow4=10;//如果小于1000,千位顯示無
        264. }
        265. else
        266. {
        267. ucDigShow4=ucTemp4;//第4位數碼管要顯示的內容
        268. }
        269. if(uiSetData1<100)
        270. {
        271. ucDigShow3=10;//如果小于100,百位顯示無
        272. }
        273. else
        274. {
        275. ucDigShow3=ucTemp3;//第3位數碼管要顯示的內容
        276. }
        277. if(uiSetData1<10)
        278. {
        279. ucDigShow2=10;//如果小于10,十位顯示無
        280. }
        281. else
        282. {
        283. ucDigShow2=ucTemp2;//第2位數碼管要顯示的內容
        284. }
        285. ucDigShow1=ucTemp1;//第1位數碼管要顯示的內容
        286. }
        287. break;
        288. case 2://顯示P--2窗口的數據
        289. if(ucWd2Update==1)//窗口2要全部更新顯示
        290. {
        291. ucWd2Update=0;//及時清零標志,避免一直進來掃描
        292. ucDigShow8=12;//第8位數碼管顯示P
        293. ucDigShow7=11;//第7位數碼管顯示-
        294. ucDigShow6=2;//第6位數碼管顯示2
        295. ucDigShow5=10; //第5位數碼管顯示無
        296. ucTemp4=uiSetData2/1000; //分解數據
        297. ucTemp3=uiSetData2%1000/100;
        298. ucTemp2=uiSetData2%100/10;
        299. ucTemp1=uiSetData2%10;
        300. if(uiSetData2<1000)
        301. {
        302. ucDigShow4=10;//如果小于1000,千位顯示無
        303. }
        304. else
        305. {
        306. ucDigShow4=ucTemp4;//第4位數碼管要顯示的內容
        307. }
        308. if(uiSetData2<100)
        309. {
        310. ucDigShow3=10;//如果小于100,百位顯示無
        311. }
        312. else
        313. {
        314. ucDigShow3=ucTemp3;//第3位數碼管要顯示的內容
        315. }
        316. if(uiSetData2<10)
        317. {
        318. ucDigShow2=10;//如果小于10,十位顯示無
        319. }
        320. else
        321. {
        322. ucDigShow2=ucTemp2;//第2位數碼管要顯示的內容
        323. }
        324. ucDigShow1=ucTemp1;//第1位數碼管要顯示的內容
        325. }
        326. break;
        327. case 3://顯示P--3窗口的數據
        328. if(ucWd3Update==1)//窗口3要全部更新顯示
        329. {
        330. ucWd3Update=0;//及時清零標志,避免一直進來掃描
        331. ucDigShow8=12;//第8位數碼管顯示P
        332. ucDigShow7=11;//第7位數碼管顯示-
        333. ucDigShow6=3;//第6位數碼管顯示3
        334. ucDigShow5=10; //第5位數碼管顯示無
        335. ucTemp4=uiSetData3/1000; //分解數據
        336. ucTemp3=uiSetData3%1000/100;
        337. ucTemp2=uiSetData3%100/10;
        338. ucTemp1=uiSetData3%10;
        339. if(uiSetData3<1000)
        340. {
        341. ucDigShow4=10;//如果小于1000,千位顯示無
        342. }
        343. else
        344. {
        345. ucDigShow4=ucTemp4;//第4位數碼管要顯示的內容
        346. }
        347. if(uiSetData3<100)
        348. {
        349. ucDigShow3=10;//如果小于100,百位顯示無
        350. }
        351. else
        352. {
        353. ucDigShow3=ucTemp3;//第3位數碼管要顯示的內容
        354. }
        355. if(uiSetData3<10)
        356. {
        357. ucDigShow2=10;//如果小于10,十位顯示無
        358. }
        359. else
        360. {
        361. ucDigShow2=ucTemp2;//第2位數碼管要顯示的內容
        362. }
        363. ucDigShow1=ucTemp1;//第1位數碼管要顯示的內容
        364. }
        365. break;
        366. case 4://顯示P--4窗口的數據
        367. if(ucWd4Update==1)//窗口4要全部更新顯示
        368. {
        369. ucWd4Update=0;//及時清零標志,避免一直進來掃描
        370. ucDigShow8=12;//第8位數碼管顯示P
        371. ucDigShow7=11;//第7位數碼管顯示-
        372. ucDigShow6=4;//第6位數碼管顯示4
        373. ucDigShow5=10; //第5位數碼管顯示無
        374. ucTemp4=uiSetData4/1000; //分解數據
        375. ucTemp3=uiSetData4%1000/100;
        376. ucTemp2=uiSetData4%100/10;
        377. ucTemp1=uiSetData4%10;
        378. if(uiSetData4<1000)
        379. {
        380. ucDigShow4=10;//如果小于1000,千位顯示無
        381. }
        382. else
        383. {
        384. ucDigShow4=ucTemp4;//第4位數碼管要顯示的內容
        385. }
        386. if(uiSetData4<100)
        387. {
        388. ucDigShow3=10;//如果小于100,百位顯示無
        389. }
        390. else
        391. {
        392. ucDigShow3=ucTemp3;//第3位數碼管要顯示的內容
        393. }
        394. if(uiSetData4<10)
        395. {
        396. ucDigShow2=10;//如果小于10,十位顯示無
        397. }
        398. else
        399. {
        400. ucDigShow2=ucTemp2;//第2位數碼管要顯示的內容
        401. }
        402. ucDigShow1=ucTemp1;//第1位數碼管要顯示的內容
        403. }
        404. break;
        405. }
        406. }
        407. void key_scan(void)//按鍵掃描函數 放在定時中斷里
        408. {
        409. if(key_sr1==1)//IO是高電平,說明按鍵沒有被按下,這時要及時清零一些標志位
        410. {
        411. ucKeyLock1=0; //按鍵自鎖標志清零
        412. uiKeyTimeCnt1=0;//按鍵去抖動延時計數器清零,此行非常巧妙,是我實戰中摸索出來的。
        413. }
        414. else if(ucKeyLock1==0)//有按鍵按下,且是第一次被按下
        415. {
        416. uiKeyTimeCnt1++; //累加定時中斷次數
        417. if(uiKeyTimeCnt1>const_key_time1)
        418. {
        419. uiKeyTimeCnt1=0;
        420. ucKeyLock1=1;//自鎖按鍵置位,避免一直觸發
        421. ucKeySec=1; //觸發1號鍵
        422. }
        423. }
        424. if(key_sr2==1)//IO是高電平,說明按鍵沒有被按下,這時要及時清零一些標志位
        425. {
        426. ucKeyLock2=0; //按鍵自鎖標志清零
        427. uiKeyTimeCnt2=0;//按鍵去抖動延時計數器清零,此行非常巧妙,是我實戰中摸索出來的。
        428. }
        429. else if(ucKeyLock2==0)//有按鍵按下,且是第一次被按下
        430. {
        431. uiKeyTimeCnt2++; //累加定時中斷次數
        432. if(uiKeyTimeCnt2>const_key_time2)
        433. {
        434. uiKeyTimeCnt2=0;
        435. ucKeyLock2=1;//自鎖按鍵置位,避免一直觸發
        436. ucKeySec=2; //觸發2號鍵
        437. }
        438. }
        439. if(key_sr3==1)//IO是高電平,說明按鍵沒有被按下,這時要及時清零一些標志位
        440. {
        441. ucKeyLock3=0; //按鍵自鎖標志清零
        442. uiKeyTimeCnt3=0;//按鍵去抖動延時計數器清零,此行非常巧妙,是我實戰中摸索出來的。
        443. }
        444. else if(ucKeyLock3==0)//有按鍵按下,且是第一次被按下
        445. {
        446. uiKeyTimeCnt3++; //累加定時中斷次數
        447. if(uiKeyTimeCnt3>const_key_time3)
        448. {
        449. uiKeyTimeCnt3=0;
        450. ucKeyLock3=1;//自鎖按鍵置位,避免一直觸發
        451. ucKeySec=3; //觸發3號鍵
        452. }
        453. }
        454. }
        455. void key_service(void) //按鍵服務的應用程序
        456. {
        457. switch(ucKeySec) //按鍵服務狀態切換
        458. {
        459. case 1:// 加按鍵 對應朱兆祺學習板的S1鍵
        460. switch(ucWd)//在不同的窗口下,設置不同的參數
        461. {
        462. case 1:
        463. uiSetData1++;
        464. if(uiSetData1>9999) //最大值是9999
        465. {
        466. uiSetData1=9999;
        467. }
        468. write_eeprom_int(0,uiSetData1); //存入EEPROM 由于內部有延時函數,所以此處會引起數碼管閃爍
        469. ucWd1Update=1;//窗口1更新顯示
        470. break;
        471. case 2:
        472. uiSetData2++;
        473. if(uiSetData2>9999) //最大值是9999
        474. {
        475. uiSetData2=9999;
        476. }
        477. write_eeprom_int(2,uiSetData2); //存入EEPROM,由于內部有延時函數,所以此處會引起數碼管閃爍
        478. ucWd2Update=1;//窗口2更新顯示
        479. break;
        480. case 3:
        481. uiSetData3++;
        482. if(uiSetData3>9999) //最大值是9999
        483. {
        484. uiSetData3=9999;
        485. }
        486. write_eeprom_int(4,uiSetData3); //存入EEPROM,由于內部有延時函數,所以此處會引起數碼管閃爍
        487. ucWd3Update=1;//窗口3更新顯示
        488. break;
        489. case 4:
        490. uiSetData4++;
        491. if(uiSetData4>9999) //最大值是9999
        492. {
        493. uiSetData4=9999;
        494. }
        495. write_eeprom_int(6,uiSetData4); //存入EEPROM,由于內部有延時函數,所以此處會引起數碼管閃爍
        496. ucWd4Update=1;//窗口4更新顯示
        497. break;
        498. }
        499. ucVoiceLock=1;//原子鎖加鎖,保護主函數與中斷函數的共享變量uiVoiceCnt
        500. uiVoiceCnt=const_voice_short; //按鍵聲音觸發,滴一聲就停。
        501. ucVoiceLock=0;//原子鎖解鎖,保護主函數與中斷函數的共享變量uiVoiceCnt
        502. ucKeySec=0;//響應按鍵服務處理程序后,按鍵編號清零,避免一致觸發
        503. break;
        504. case 2:// 減按鍵 對應朱兆祺學習板的S5鍵
        505. switch(ucWd)//在不同的窗口下,設置不同的參數
        506. {
        507. case 1:
        508. uiSetData1--;
        509. if(uiSetData1>9999)
        510. {
        511. uiSetData1=0;//最小值是0
        512. }
        513. write_eeprom_int(0,uiSetData1); //存入EEPROM,由于內部有延時函數,所以此處會引起數碼管閃爍
        514. ucWd1Update=1;//窗口1更新顯示
        515. break;
        516. case 2:
        517. uiSetData2--;
        518. if(uiSetData2>9999)
        519. {
        520. uiSetData2=0;//最小值是0
        521. }
        522. write_eeprom_int(2,uiSetData2); //存入EEPROM,由于內部有延時函數,所以此處會引起數碼管閃爍
        523. ucWd2Update=1;//窗口2更新顯示
        524. break;
        525. case 3:
        526. uiSetData3--;
        527. if(uiSetData3>9999)
        528. {
        529. uiSetData3=0;//最小值是0
        530. }
        531. write_eeprom_int(4,uiSetData3); //存入EEPROM,由于內部有延時函數,所以此處會引起數碼管閃爍
        532. ucWd3Update=1;//窗口3更新顯示
        533. break;
        534. case 4:
        535. uiSetData4--;
        536. if(uiSetData4>9999)
        537. {
        538. uiSetData4=0;//最小值是0
        539. }
        540. write_eeprom_int(6,uiSetData4); //存入EEPROM,由于內部有延時函數,所以此處會引起數碼管閃爍
        541. ucWd4Update=1;//窗口4更新顯示
        542. break;
        543. }
        544. ucVoiceLock=1;//原子鎖加鎖,保護主函數與中斷函數的共享變量uiVoiceCnt
        545. uiVoiceCnt=const_voice_short; //按鍵聲音觸發,滴一聲就停。
        546. ucVoiceLock=0;//原子鎖解鎖,保護主函數與中斷函數的共享變量uiVoiceCnt
        547. ucKeySec=0;//響應按鍵服務處理程序后,按鍵編號清零,避免一致觸發
        548. break;
        549. case 3:// 切換窗口按鍵 對應朱兆祺學習板的S9鍵
        550. ucWd++;//切換窗口
        551. if(ucWd>4)
        552. {
        553. ucWd=1;
        554. }
        555. switch(ucWd)//在不同的窗口下,在不同的窗口下,更新顯示不同的窗口
        556. {
        557. case 1:
        558. ucWd1Update=1;//窗口1更新顯示
        559. break;
        560. case 2:
        561. ucWd2Update=1;//窗口2更新顯示
        562. break;
        563. case 3:
        564. ucWd3Update=1;//窗口3更新顯示
        565. break;
        566. case 4:
        567. ucWd4Update=1;//窗口4更新顯示
        568. break;
        569. }
        570. ucVoiceLock=1;//原子鎖加鎖,保護主函數與中斷函數的共享變量uiVoiceCnt
        571. uiVoiceCnt=const_voice_short; //按鍵聲音觸發,滴一聲就停。
        572. ucVoiceLock=0;//原子鎖解鎖,保護主函數與中斷函數的共享變量uiVoiceCnt
        573. ucKeySec=0;//響應按鍵服務處理程序后,按鍵編號清零,避免一致觸發
        574. break;
        575. }
        576. }
        577. void display_drive(void)
        578. {
        579. //以下程序,如果加一些數組和移位的元素,還可以壓縮容量。但是鴻哥追求的不是容量,而是清晰的講解思路
        580. switch(ucDisplayDriveStep)
        581. {
        582. case 1://顯示第1位
        583. ucDigShowTemp=dig_table[ucDigShow1];
        584. if(ucDigDot1==1)
        585. {
        586. ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
        587. }
        588. dig_hc595_drive(ucDigShowTemp,0xfe);
        589. break;
        590. case 2://顯示第2位
        591. ucDigShowTemp=dig_table[ucDigShow2];
        592. if(ucDigDot2==1)
        593. {
        594. ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
        595. }
        596. dig_hc595_drive(ucDigShowTemp,0xfd);
        597. break;
        598. case 3://顯示第3位
        599. ucDigShowTemp=dig_table[ucDigShow3];
        600. if(ucDigDot3==1)
        601. {
        602. ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
        603. }
        604. dig_hc595_drive(ucDigShowTemp,0xfb);
        605. break;
        606. case 4://顯示第4位
        607. ucDigShowTemp=dig_table[ucDigShow4];
        608. if(ucDigDot4==1)
        609. {
        610. ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
        611. }
        612. dig_hc595_drive(ucDigShowTemp,0xf7);
        613. break;
        614. case 5://顯示第5位
        615. ucDigShowTemp=dig_table[ucDigShow5];
        616. if(ucDigDot5==1)
        617. {
        618. ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
        619. }
        620. dig_hc595_drive(ucDigShowTemp,0xef);
        621. break;
        622. case 6://顯示第6位
        623. ucDigShowTemp=dig_table[ucDigShow6];
        624. if(ucDigDot6==1)
        625. {
        626. ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
        627. }
        628. dig_hc595_drive(ucDigShowTemp,0xdf);
        629. break;
        630. case 7://顯示第7位
        631. ucDigShowTemp=dig_table[ucDigShow7];
        632. if(ucDigDot7==1)
        633. {
        634. ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
        635. }
        636. dig_hc595_drive(ucDigShowTemp,0xbf);
        637. break;
        638. case 8://顯示第8位
        639. ucDigShowTemp=dig_table[ucDigShow8];
        640. if(ucDigDot8==1)
        641. {
        642. ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
        643. }
        644. dig_hc595_drive(ucDigShowTemp,0x7f);
        645. break;
        646. }
        647. ucDisplayDriveStep++;
        648. if(ucDisplayDriveStep>8)//掃描完8個數碼管后,重新從第一個開始掃描
        649. {
        650. ucDisplayDriveStep=1;
        651. }
        652. }
        653. //數碼管的74HC595驅動函數
        654. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
        655. {
        656. unsigned char i;
        657. unsigned char ucTempData;
        658. dig_hc595_sh_dr=0;
        659. dig_hc595_st_dr=0;
        660. ucTempData=ucDigStatusTemp16_09;//先送高8位
        661. for(i=0;i<8;i++)
        662. {
        663. if(ucTempData>=0x80)dig_hc595_ds_dr=1;
        664. else dig_hc595_ds_dr=0;
        665. dig_hc595_sh_dr=0; //SH引腳的上升沿把數據送入寄存器
        666. delay_short(1);
        667. dig_hc595_sh_dr=1;
        668. delay_short(1);
        669. ucTempData=ucTempData<<1;
        670. }
        671. ucTempData=ucDigStatusTemp08_01;//再先送低8位
        672. for(i=0;i<8;i++)
        673. {
        674. if(ucTempData>=0x80)dig_hc595_ds_dr=1;
        675. else dig_hc595_ds_dr=0;
        676. dig_hc595_sh_dr=0; //SH引腳的上升沿把數據送入寄存器
        677. delay_short(1);
        678. dig_hc595_sh_dr=1;
        679. delay_short(1);
        680. ucTempData=ucTempData<<1;
        681. }
        682. dig_hc595_st_dr=0;//ST引腳把兩個寄存器的數據更新輸出到74HC595的輸出引腳上并且鎖存起來
        683. delay_short(1);
        684. dig_hc595_st_dr=1;
        685. delay_short(1);
        686. dig_hc595_sh_dr=0; //拉低,抗干擾就增強
        687. dig_hc595_st_dr=0;
        688. dig_hc595_ds_dr=0;
        689. }
        690. //LED燈的74HC595驅動函數
        691. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
        692. {
        693. unsigned char i;
        694. unsigned char ucTempData;
        695. hc595_sh_dr=0;
        696. hc595_st_dr=0;
        697. ucTempData=ucLedStatusTemp16_09;//先送高8位
        698. for(i=0;i<8;i++)
        699. {
        700. if(ucTempData>=0x80)hc595_ds_dr=1;
        701. else hc595_ds_dr=0;
        702. hc595_sh_dr=0; //SH引腳的上升沿把數據送入寄存器
        703. delay_short(1);
        704. hc595_sh_dr=1;
        705. delay_short(1);
        706. ucTempData=ucTempData<<1;
        707. }
        708. ucTempData=ucLedStatusTemp08_01;//再先送低8位
        709. for(i=0;i<8;i++)
        710. {
        711. if(ucTempData>=0x80)hc595_ds_dr=1;
        712. else hc595_ds_dr=0;
        713. hc595_sh_dr=0; //SH引腳的上升沿把數據送入寄存器
        714. delay_short(1);
        715. hc595_sh_dr=1;
        716. delay_short(1);
        717. ucTempData=ucTempData<<1;
        718. }
        719. hc595_st_dr=0;//ST引腳把兩個寄存器的數據更新輸出到74HC595的輸出引腳上并且鎖存起來
        720. delay_short(1);
        721. hc595_st_dr=1;
        722. delay_short(1);
        723. hc595_sh_dr=0; //拉低,抗干擾就增強
        724. hc595_st_dr=0;
        725. hc595_ds_dr=0;
        726. }
        727. void T0_time(void) interrupt 1 //定時中斷
        728. {
        729. TF0=0;//清除中斷標志
        730. TR0=0; //關中斷
        731. /* 注釋三:
        732. * 此處多增加一個原子鎖,作為中斷與主函數共享數據的保護,實際上是借鑒了"紅金龍吸味"關于原子鎖的建議.
        733. */
        734. if(ucVoiceLock==0) //原子鎖判斷
        735. {
        736. if(uiVoiceCnt!=0)
        737. {
        738. uiVoiceCnt--; //每次進入定時中斷都自減1,直到等于零為止。才停止鳴叫
        739. beep_dr=0;//蜂鳴器是PNP三極管控制,低電平就開始鳴叫。
        740. }
        741. else
        742. {
        743. ; //此處多加一個空指令,想維持跟if括號語句的數量對稱,都是兩條指令。不加也可以。
        744. beep_dr=1;//蜂鳴器是PNP三極管控制,高電平就停止鳴叫。
        745. }
        746. }
        747. key_scan(); //按鍵掃描函數
        748. display_drive();//數碼管字模的驅動函數
        749. TH0=0xfe; //重裝初始值(65535-500)=65035=0xfe0b
        750. TL0=0x0b;
        751. TR0=1;//開中斷
        752. }
        753. void delay_short(unsigned int uiDelayShort)
        754. {
        755. unsigned int i;
        756. for(i=0;i
        757. {
        758. ; //一個分號相當于執行一條空語句
        759. }
        760. }
        761. void delay_long(unsigned int uiDelayLong)
        762. {
        763. unsigned int i;
        764. unsigned int j;
        765. for(i=0;i
        766. {
        767. for(j=0;j<500;j++)//內嵌循環的空指令數量
        768. {
        769. ; //一個分號相當于執行一條空語句
        770. }
        771. }
        772. }
        773. void initial_myself(void)//第一區 初始化單片機
        774. {
        775. /* 注釋四:
        776. * 矩陣鍵盤也可以做獨立按鍵,前提是把某一根公共輸出線輸出低電平,
        777. * 模擬獨立按鍵的觸發地,本程序中,把key_gnd_dr輸出低電平。
        778. * 朱兆祺51學習板的S1就是本程序中用到的一個獨立按鍵。
        779. */
        780. key_gnd_dr=0; //模擬獨立按鍵的地GND,因此必須一直輸出低電平
        781. beep_dr=1; //用PNP三極管控制蜂鳴器,輸出高電平時不叫。
        782. hc595_drive(0x00,0x00);//關閉所有經過另外兩個74HC595驅動的LED燈
        783. TMOD=0x01;//設置定時器0為工作方式1
        784. TH0=0xfe; //重裝初始值(65535-500)=65035=0xfe0b
        785. TL0=0x0b;
        786. }
        787. void initial_peripheral(void) //第二區 初始化外圍
        788. {
        789. ucDigDot8=0; //小數點全部不顯示
        790. ucDigDot7=0;
        791. ucDigDot6=0;
        792. ucDigDot5=0;
        793. ucDigDot4=0;
        794. ucDigDot3=0;
        795. ucDigDot2=0;
        796. ucDigDot1=0;
        797. EA=1; //開總中斷
        798. ET0=1; //允許定時中斷
        799. TR0=1; //啟動定時中斷
        800. /* 注釋五:
        801. * 如何初始化EEPROM數據的方法。在使用EEPROM時,這一步初始化很關鍵!
        802. * 第一次上電時,我們從EEPROM讀取出來的數據有可能超出了范圍,可能是ff。
        803. * 這個時候我們應該給它填入一個初始化的數據,這一步千萬別漏了。另外,
        804. * 由于int類型數據占用2個字節,所以以下4個數據挨著的地址是0,2,4,6.
        805. */
        806. uiSetData1=read_eeprom_int(0);//讀取uiSetData1,內部占用2個字節地址
        807. if(uiSetData1>9999) //不在范圍內
        808. {
        809. uiSetData1=0; //填入一個初始化數據
        810. write_eeprom_int(0,uiSetData1); //存入uiSetData1,內部占用2個字節地址
        811. }
        812. uiSetData2=read_eeprom_int(2);//讀取uiSetData2,內部占用2個字節地址
        813. if(uiSetData2>9999)//不在范圍內
        814. {
        815. uiSetData2=0;//填入一個初始化數據
        816. write_eeprom_int(2,uiSetData2); //存入uiSetData2,內部占用2個字節地址
        817. }
        818. uiSetData3=read_eeprom_int(4);//讀取uiSetData3,內部占用2個字節地址
        819. if(uiSetData3>9999)//不在范圍內
        820. {
        821. uiSetData3=0;//填入一個初始化數據
        822. write_eeprom_int(4,uiSetData3); //存入uiSetData3,內部占用2個字節地址
        823. }
        824. uiSetData4=read_eeprom_int(6);//讀取uiSetData4,內部占用2個字節地址
        825. if(uiSetData4>9999)//不在范圍內
        826. {
        827. uiSetData4=0;//填入一個初始化數據
        828. write_eeprom_int(6,uiSetData4); //存入uiSetData4,內部占用2個字節地址
        829. }
        830. }
        總結陳詞:
        IIC通訊過程是一個要求一氣呵成的通訊過程,中間不能被其它中斷影響時序出錯,因此,在整個通訊過程中應該先關閉總中斷,完成之后再開中斷。但是,這樣就會引起另外一個新問題,如果關閉總中斷的時間太長,會導致動態數碼管不能及時均勻的掃描,在按鍵更改參數,內部操作EEPROM時,數碼管就會出現短暫明顯的閃爍現象,解決這個問題最好的辦法就是在做項目中盡量不要用動態掃描數碼管的方案,應該用靜態顯示的方案。那么在程序上還有沒有改善這種現象的方法?當然有。欲知詳情,請聽下回分解-----操作AT24C02時,利用“一氣呵成的定時器方式”改善數碼管的閃爍現象。


        評論


        技術專區

        關閉
        主站蜘蛛池模板: 盱眙县| 延川县| 河曲县| 澄江县| 新丰县| 常山县| 且末县| 贵州省| 玉龙| 焉耆| 鹤壁市| 凯里市| 绿春县| 景宁| 星座| 大同县| 偏关县| 黄骅市| 隆尧县| 称多县| 普兰县| 镇江市| 大同市| 芦山县| 松原市| 蓝田县| 南丰县| 宜兰县| 铜梁县| 育儿| 洪泽县| 綦江县| 彭阳县| 建湖县| 尉氏县| 石屏县| 芦山县| 铜川市| 乌什县| 泸水县| 麟游县|