新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > 第44節:從機的串口收發綜合程序框架

        第44節:從機的串口收發綜合程序框架

        作者: 時間:2016-11-22 來源:網絡 收藏
        開場白:
        根據上一節的預告,本來這一節內容打算講“利用AT24C02進行掉電后的數據保存”的,但是由于網友“261854681”強烈建議我講一個完整的串口收發程序實例,因此我決定再花兩節篇幅講講這方面的內容。
        實際上在大部分的項目中,串口都需要“一收一應答”的握手協議,上位機作為主機,單片機作為從機,主機先發一串數據,從機收到數據后進行校驗判斷,如果校驗正確則返回正確應答指令,如果校驗錯誤則返回錯誤應答指令,主機收到應答指令后,如果發現是正確應答指令則繼續發送其它的新數據,如果發現是錯誤應答指令,或者超時沒有接收到任何應答指令,則繼續重發,如果連續重發三次都是錯誤應答或者無應答,主機就進行報錯處理。
        這節先講從機的收發端程序實例。要教會大家三個知識點:
        第一個:為了保證串口中斷接收的數據不丟失,在初始化時必須設置IP = 0x10,相當于把串口中斷設置為最高優先級,這個時候,串口中斷可以打斷任何其他的中斷服務函數,實現中斷嵌套。
        第二個:從機端的收發端程序框架。
        第三個:從機的狀態指示程序框架??梢灾甘敬龣C,通訊中,超時出錯三種狀態。

        具體內容,請看源代碼講解。

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

        (2)實現功能:
        顯示和獨立按鍵部分根據第29節的程序來改編,用朱兆祺51單片機學習板中的S1,S5,S9,S13作為獨立按鍵。
        一共有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是切換窗口按鍵,按下此按鍵會依次循環切換不同的窗口。S13是復位按鍵,當通訊超時蜂鳴器報警時,可以按下此鍵清除報警。

        第二種:通過串口來更改參數:
        波特率是:9600.
        通訊協議:EB 00 55GG 00 02 XX XXCY
        其中第1,2,3位EB 00 55就是數據頭
        其中第4位GG就是數據類型。01代表更改參數1,02代表更改參數2,03代表更改參數3,04代表更改參數4,
        其中第5,6位00 02就是有效數據長度。高位在左,低位在右。
        其中從第7,8位XX XX是被更改的參數。高位在左,低位在右。
        第9位CY是累加和,前面所有字節的累加。
        一個完整的通訊必須接收完4串數據,每串數據之間的間隔時間不能超過10秒鐘,否則認為通訊超時出錯引發蜂鳴器報警。如果接收到得數據校驗正確,
        則返回校驗正確應答:eb 00 55 f5 00 00 35,
        否則返回校驗出錯應答::eb 00 55 fa 00 00 3a。
        系統處于待機狀態時,LED燈一直亮,
        系統處于非待機狀態時,LED燈閃爍,
        系統處于通訊超時出錯狀態時,LED燈閃爍,并且蜂鳴器間歇鳴叫報警。


        通過電腦的串口助手,依次發送以下測試數據,將會分別更改參數1,參數2,參數3,參數4。注意,每串數據之間的時間最大不能超過10秒,否則系統認為通訊超時報警。
        把參數1更改為十進制的1: eb 00 55 01 00 02 00 01 44
        把參數2更改為十進制的12:eb 00 55 02 00 02 00 0c 50
        把參數3更改為十進制的123: eb 00 55 03 00 02 00 7b c0
        把參數4更改為十進制的1234:eb 00 55 04 00 02 04 d2 1c

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


        總結陳詞:
        這節詳細講了從機收發端的程序框架,而主機端的程序則用電腦的串口助手來模擬。實際上,主機端的程序也有很多內容,它包括依次發送每一串數據,根據返回的應答來決定是否需要重發數據,重發三次如果沒反應則進行報錯,以及超時沒接收到數據等等內容。主機收發端的程序框架是什么樣的?欲知詳情,請聽下回分解-----主機的串口收發綜合程序框架


        評論


        技術專區

        關閉
        主站蜘蛛池模板: 宁化县| 济源市| 吴江市| 喀喇沁旗| 梅河口市| 辉南县| 彭山县| 临城县| 衡山县| 马山县| 永川市| 东兰县| 察雅县| 聂荣县| 崇文区| 南投市| 石城县| 东山县| 靖安县| 招远市| 鄂托克前旗| 龙门县| 鄂伦春自治旗| 泸州市| 开化县| 广平县| 崇左市| 玉屏| 清远市| 德令哈市| 安岳县| 门头沟区| 衡南县| 平凉市| 长春市| 余姚市| 开封县| 平塘县| 县级市| 喀什市| 武山县|