第88節:單片機靠關鍵字快速截取有效數據串
開場白:
我前面串口程序大部分都是通過靠時間來識別每一串數據是否接收完畢,有一些串口項目的協議是固定不變的,而且也不需要從機反饋任何應答信號,這類項目只需根據特定關鍵字來快速識別數據串是否接收完畢即可。比如現在有一種電子稱,它的測量范圍是0.00克到500.00克,他是靠串口不斷對外發送當前重量數據的,每串數據固定長度26個字節,最后兩個字節是回車換行符0x0d 0x0a,倒數第9,10,11,12,13,14為有效的ASCII碼數字,其中倒數第11位為固定的小數點,其它的數據可以忽略不計。這類串口框架的思路是:根據數據尾是否有0x0d 0x0a來判斷數據串是否有效的,一旦發現有此關鍵字,再判斷總的數據長度是否等于或者大于一串數據的固定長度,如果滿足,則把相關標志位置位,通知主函數中的串口服務程序進行處理。同時也及時關閉串口中斷,避免在處理串口數據期間受到串口數據的中斷干擾,等串口服務程序處理完畢再打開。
具體內容,請看源代碼講解。
(1) 硬件平臺:
基于朱兆祺51單片機學習板。
(2) 實現功能:
波特率是:9600。把當前電子稱的重量數據顯示在數碼管上,在電腦上用串口助手軟件來模擬電子稱發送以下格式協議的3串數據,它的協議很簡單,每串數據固定長度26個字節,最后兩個字節是回車換行符0x0d 0x0a,倒數第9,10,11,12,13,14為有效的ASCII碼數字,其中倒數第11位為固定的小數點,其它的數據可以忽略不計。
(a)字符是:
ST,GS,+ 0.77 g
轉換成16進制是:
20 53 54 2C 47 53 2C 2B 20 20 20 20 20 20 30 2E 37 37 20 2020 20 20 67 0D 0A
數碼管顯示:0.77
(b)
字符是:
ST,GS,+ 136.39 g
轉換成16進制是:
20 53 54 2C 47 53 2C 2B 20 20 20 20 31 33 36 2E 33 39 20 2020 20 20 67 0D 0A
數碼管顯示:136.39
(c)
字符是:
ST,GS,+ 0.00 g
轉換成16進制是:
20 53 54 2C 47 53 2C 2B 20 20 20 20 20 20 30 2E 30 30 20 2020 20 20 67 0D 0A
數碼管顯示:0.00
(3)源代碼講解如下:
- #include "REG52.H"
- #define const_rc_size36//接收串口中斷數據的緩沖區數組大小
- #define const_least_size 26 //一串標準數據的大小
- void initial_myself();
- void initial_peripheral();
- void delay_short(unsigned int uiDelayShort);
- void delay_long(unsigned int uiDelaylong);
- //驅動數碼管的74HC595
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
- void display_drive(); //顯示數碼管字模的驅動函數
- void display_service(); //顯示的窗口菜單服務程序
- //驅動LED的74HC595
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
- void usart_service(void);//串口接收服務程序,在main函數里
- void usart_receive(void); //串口接收中斷函數
- void T0_time();//定時中斷函數
- sbit dig_hc595_sh_dr=P2^0; //數碼管的74HC595程序
- sbit dig_hc595_st_dr=P2^1;
- sbit dig_hc595_ds_dr=P2^2;
- sbit hc595_sh_dr=P2^3; //LED燈的74HC595程序
- sbit hc595_st_dr=P2^4;
- sbit hc595_ds_dr=P2^5;
- sbit beep_dr=P2^7; //蜂鳴器的驅動IO口
- sbit led_dr=P3^5;//獨立LED燈
- //根據原理圖得出的共陰數碼管字模表
- code unsigned char dig_table[]=
- {
- 0x3f,//0 序號0
- 0x06,//1 序號1
- 0x5b,//2 序號2
- 0x4f,//3 序號3
- 0x66,//4 序號4
- 0x6d,//5 序號5
- 0x7d,//6 序號6
- 0x07,//7 序號7
- 0x7f,//8 序號8
- 0x6f,//9 序號9
- 0x00,//無 序號10
- 0x40,//- 序號11
- 0x73,//P 序號12
- };
- unsigned intuiRcregTotal=0;//代表當前緩沖區已經接收了多少個數據
- unsigned intuiRcregTotalTemp=0;//代表當前緩沖區已經接收了多少個數據的中間變量
- unsigned char ucRcregBuf[const_rc_size]; //接收串口中斷數據的緩沖區數組
- unsigned char ucReceiveFlag=0; //接收成功標志
- unsigned char ucDigShow8;//第8位數碼管要顯示的內容
- unsigned char ucDigShow7;//第7位數碼管要顯示的內容
- unsigned char ucDigShow6;//第6位數碼管要顯示的內容
- unsigned char ucDigShow5;//第5位數碼管要顯示的內容
- unsigned char ucDigShow4;//第4位數碼管要顯示的內容
- unsigned char ucDigShow3;//第3位數碼管要顯示的內容
- unsigned char ucDigShow2;//第2位數碼管要顯示的內容
- unsigned char ucDigShow1;//第1位數碼管要顯示的內容
- unsigned char ucDigDot8;//數碼管8的小數點是否顯示的標志
- unsigned char ucDigDot7;//數碼管7的小數點是否顯示的標志
- unsigned char ucDigDot6;//數碼管6的小數點是否顯示的標志
- unsigned char ucDigDot5;//數碼管5的小數點是否顯示的標志
- unsigned char ucDigDot4;//數碼管4的小數點是否顯示的標志
- unsigned char ucDigDot3;//數碼管3的小數點是否顯示的標志
- unsigned char ucDigDot2;//數碼管2的小數點是否顯示的標志
- unsigned char ucDigDot1;//數碼管1的小數點是否顯示的標志
- unsigned char ucDigShowTemp=0; //臨時中間變量
- unsigned char ucDisplayDriveStep=1;//動態掃描數碼管的步驟變量
- unsigned char ucWd1Part1Update=1; //8位數碼管更新顯示標志
- unsigned long ulWeightCurrent=12345; //顯示當前實際的重量
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- usart_service();//串口接收服務程序
- display_service(); //顯示的窗口菜單服務程序
- }
- }
- /* 注釋一:
- * 本節內容處理串口數據是根據數據尾是否有0x0d 0x0a來判斷數據串是否有效的,一旦發現有此關鍵字,
- * 再判斷總的數據長度是否等于或者大于一串數據的固定長度,如果滿足,則把相關標志位置位,通知主函數中
- * 的串口服務程序進行處理。同時也及時關閉串口中斷,避免在處理串口數據期間受到串口數據的中斷干擾,
- * 等串口服務程序處理完畢再打開。
- */
- void usart_receive(void) interrupt 4 //串口接收數據中斷函數
- {
- if(RI==1)
- {
- RI = 0;
- ++uiRcregTotal;
- ucRcregBuf[uiRcregTotal-1]=SBUF; //將串口接收到的數據緩存到接收緩沖區里
- if(uiRcregTotal>=2&&ucRcregBuf[uiRcregTotal-2]==0x0d&&ucRcregBuf[uiRcregTotal-1]==0x0a)//一旦發現后綴是0x0d 0x0a關鍵字的就進去處理判斷
- {
- if(uiRcregTotal
- {
- uiRcregTotal=0;
- }
- else
- {
- uiRcregTotalTemp=uiRcregTotal; //把接收到的總數據傳遞給一個中間變量,在主函數那邊處理這個中間變量
- ucReceiveFlag=1; //通知主程序接收成功
- ES=0; // 禁止接收中斷,等主函數處理完接收的數據后再打開串口中斷,避免在處理串口數據期間受到串口數據的中斷干擾。
- }
- }
- else if(uiRcregTotal>=const_rc_size)//超過緩沖區
- {
- uiRcregTotal=0;
- }
- }
- else //如果不是串口接收中斷,那么必然是串口發送中斷,及時清除發送中斷的標志,否則一直發送中斷
- {
- TI = 0;
- }
- }
- void usart_service(void)//串口接收服務程序,在main函數里
- {
- //加了static關鍵字后,此局部變量不會每次進來函數都初始化一次,這樣有可能減少了一點指令消耗的時間。
- static unsigned long ulReceiveData10000; //定義成long類型,是為了方便后面換算的乘法運算,讓它不會溢出而出錯。
- static unsigned long ulReceiveData1000;
- static unsigned long ulReceiveData100;
- static unsigned long ulReceiveData10;
- static unsigned long ulReceiveData1;
- if(ucReceiveFlag==1)//說明有數據接收成功,進入數據處理分析
- {
- ulReceiveData10000=0;
- ulReceiveData1000=0;
- ulReceiveData100=0;
- ulReceiveData10=0;
- ulReceiveData1=0;
- /* 注釋二:
- * 根據協議,倒數第9,10,11,12,13,14為有效的ASCII碼數字,其中倒數第11位為固定的小數點,因此省略不寫。
- */
- if(ucRcregBuf[uiRcregTotalTemp-9]>=0x30)
- {
- ulReceiveData1=ucRcregBuf[uiRcregTotalTemp-9]-0x30; //接收到的ASCII碼數字減去0x30變成實際數值.
- }
- if(ucRcregBuf[uiRcregTotalTemp-10]>=0x30)
- {
- ulReceiveData10=ucRcregBuf[uiRcregTotalTemp-10]-0x30;
- ulReceiveData10=ulReceiveData10*10;
- }
- if(ucRcregBuf[uiRcregTotalTemp-12]>=0x30)
- {
- ulReceiveData100=ucRcregBuf[uiRcregTotalTemp-12]-0x30;
- ulReceiveData100=ulReceiveData100*100;
- }
- if(ucRcregBuf[uiRcregTotalTemp-13]>=0x30)
- {
- ulReceiveData1000=ucRcregBuf[uiRcregTotalTemp-13]-0x30;
- ulReceiveData1000=ulReceiveData1000*1000;
- }
- if(ucRcregBuf[uiRcregTotalTemp-14]>=0x30)
- {
- ulReceiveData10000=ucRcregBuf[uiRcregTotalTemp-14]-0x30;
- ulReceiveData10000=ulReceiveData10000*10000;
- }
- ulWeightCurrent=ulReceiveData10000+ulReceiveData1000+ulReceiveData100+ulReceiveData10+ulReceiveData1;
- ucWd1Part1Update=1; //更新顯示
- uiRcregTotalTemp=0;//清零實際接收到的字節數的中間變量
- uiRcregTotal=0;//清零實際接收到的字節數
- ucReceiveFlag=0;//清零完成標志
- ES = 1; // 允許接收中斷
- }
- }
- void display_service() //顯示的窗口菜單服務程序
- {
- //加了static關鍵字后,此局部變量不會每次進來函數都初始化一次,這樣有可能減少了一點指令消耗的時間。
- static unsigned char ucTemp5; //中間過渡變量
- static unsigned char ucTemp4; //中間過渡變量
- static unsigned char ucTemp3; //中間過渡變量
- static unsigned char ucTemp2; //中間過渡變量
- static unsigned char ucTemp1; //中間過渡變量
- if(ucWd1Part1Update==1)//更新顯示
- {
- ucWd1Part1Update=0;//及時清零標志,避免一直進來掃描
- //先分解數據用來顯示每一位
- ucTemp5=ulWeightCurrent%100000/10000;
- ucTemp4=ulWeightCurrent%10000/1000;
- ucTemp3=ulWeightCurrent%1000/100;
- ucTemp2=ulWeightCurrent%100/10;
- ucTemp1=ulWeightCurrent%10;
- ucDigDot3=1;//顯示第3位數碼管的小數點,實際數據帶2位小數點。
- ucDigShow8=10;//沒有用到第8位數碼管,因此顯示無。10代表顯示空。
- ucDigShow7=10;//沒有用到第7位數碼管,因此顯示無。10代表顯示空。
- ucDigShow6=10;//沒有用到第6位數碼管,因此顯示無。10代表顯示空。
- if(ulWeightCurrent<10000)
- {
- ucDigShow5=10;//如果小于1000,千位顯示無
- }
- else
- {
- ucDigShow5=ucTemp5;//第5位數碼管要顯示的內容
- }
- if(ulWeightCurrent<1000)
- {
- ucDigShow4=10;//如果小于1000,千位顯示無
- }
- else
- {
- ucDigShow4=ucTemp4;//第4位數碼管要顯示的內容
- }
- //因為帶2位小數點,因此最前面3位數據都是有效數,必然要顯示,不要判斷去0的空顯示處理。
- ucDigShow3=ucTemp3;//第3位數碼管要顯示的內容
- ucDigShow2=ucTemp2;//第2位數碼管要顯示的內容
- ucDigShow1=ucTemp1;//第1位數碼管要顯示的內容
- }
- }
- void display_drive()
- {
- //以下程序,如果加一些數組和移位的元素,還可以壓縮容量。但是鴻哥追求的不是容量,而是清晰的講解思路
- switch(ucDisplayDriveStep)
- {
- case 1://顯示第1位
- ucDigShowTemp=dig_table[ucDigShow1];
- if(ucDigDot1==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
- }
- dig_hc595_drive(ucDigShowTemp,0xfe);
- break;
- case 2://顯示第2位
- ucDigShowTemp=dig_table[ucDigShow2];
- if(ucDigDot2==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
- }
- dig_hc595_drive(ucDigShowTemp,0xfd);
- break;
- case 3://顯示第3位
- ucDigShowTemp=dig_table[ucDigShow3];
- if(ucDigDot3==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
- }
- dig_hc595_drive(ucDigShowTemp,0xfb);
- break;
- case 4://顯示第4位
- ucDigShowTemp=dig_table[ucDigShow4];
- if(ucDigDot4==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
- }
- dig_hc595_drive(ucDigShowTemp,0xf7);
- break;
- case 5://顯示第5位
- ucDigShowTemp=dig_table[ucDigShow5];
- if(ucDigDot5==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
- }
- dig_hc595_drive(ucDigShowTemp,0xef);
- break;
- case 6://顯示第6位
- ucDigShowTemp=dig_table[ucDigShow6];
- if(ucDigDot6==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
- }
- dig_hc595_drive(ucDigShowTemp,0xdf);
- break;
- case 7://顯示第7位
- ucDigShowTemp=dig_table[ucDigShow7];
- if(ucDigDot7==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
- }
- dig_hc595_drive(ucDigShowTemp,0xbf);
- break;
- case 8://顯示第8位
- ucDigShowTemp=dig_table[ucDigShow8];
- if(ucDigDot8==1)
- {
- ucDigShowTemp=ucDigShowTemp|0x80;//顯示小數點
- }
- dig_hc595_drive(ucDigShowTemp,0x7f);
- break;
- }
- ucDisplayDriveStep++;
- if(ucDisplayDriveStep>8)//掃描完8個數碼管后,重新從第一個開始掃描
- {
- ucDisplayDriveStep=1;
- }
- }
- //數碼管的74HC595驅動函數
- void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- dig_hc595_sh_dr=0;
- dig_hc595_st_dr=0;
- ucTempData=ucDigStatusTemp16_09;//先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引腳的上升沿把數據送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucDigStatusTemp08_01;//再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)dig_hc595_ds_dr=1;
- else dig_hc595_ds_dr=0;
- dig_hc595_sh_dr=0; //SH引腳的上升沿把數據送入寄存器
- delay_short(1);
- dig_hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- dig_hc595_st_dr=0;//ST引腳把兩個寄存器的數據更新輸出到74HC595的輸出引腳上并且鎖存起來
- delay_short(1);
- dig_hc595_st_dr=1;
- delay_short(1);
- dig_hc595_sh_dr=0; //拉低,抗干擾就增強
- dig_hc595_st_dr=0;
- dig_hc595_ds_dr=0;
- }
- //LED燈的74HC595驅動函數
- void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
- {
- unsigned char i;
- unsigned char ucTempData;
- hc595_sh_dr=0;
- hc595_st_dr=0;
- ucTempData=ucLedStatusTemp16_09;//先送高8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引腳的上升沿把數據送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- ucTempData=ucLedStatusTemp08_01;//再先送低8位
- for(i=0;i<8;i++)
- {
- if(ucTempData>=0x80)hc595_ds_dr=1;
- else hc595_ds_dr=0;
- hc595_sh_dr=0; //SH引腳的上升沿把數據送入寄存器
- delay_short(1);
- hc595_sh_dr=1;
- delay_short(1);
- ucTempData=ucTempData<<1;
- }
- hc595_st_dr=0;//ST引腳把兩個寄存器的數據更新輸出到74HC595的輸出引腳上并且鎖存起來
- delay_short(1);
- hc595_st_dr=1;
- delay_short(1);
- hc595_sh_dr=0; //拉低,抗干擾就增強
- hc595_st_dr=0;
- hc595_ds_dr=0;
- }
- void T0_time() interrupt 1
- {
- TF0=0;//清除中斷標志
- TR0=0; //關中斷
- display_drive();//數碼管字模的驅動函數
- TH0=0xfe; //重裝初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1;//開中斷
- }
- void delay_short(unsigned int uiDelayShort)
- {
- unsigned int i;
- for(i=0;i
- {
- ; //一個分號相當于執行一條空語句
- }
- }
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i
- {
- for(j=0;j<500;j++)//內嵌循環的空指令數量
- {
- ; //一個分號相當于執行一條空語句
- }
- }
- }
- void initial_myself()//第一區 初始化單片機
- {
- beep_dr=1; //用PNP三極管控制蜂鳴器,輸出高電平時不叫。
- led_dr=0;//關閉獨立LED燈
- hc595_drive(0x00,0x00);//關閉所有經過另外兩個74HC595驅動的LED燈
- TMOD=0x01;//設置定時器0為工作方式1
- TH0=0xfe; //重裝初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- //配置串口
- SCON=0x50;
- TMOD=0X21;
- /* 注釋三:
- * 為了保證串口中斷接收的數據不丟失,必須設置IP = 0x10,相當于把串口中斷設置為最高優先級,
- * 這個時候,串口中斷可以打斷任何其他的中斷服務函數實現嵌套,
- */
- IP =0x10;//把串口中斷設置為最高優先級,必須的。
- TH1=TL1=-(11059200L/12/32/9600);//串口波特率為9600。
- TR1=1;
- }
- void initial_peripheral() //第二區 初始化外圍
- {
- ucDigDot8=0; //初始化小數點全部不顯示
- ucDigDot7=0;
- ucDigDot6=0;
- ucDigDot5=0;
- ucDigDot4=0;
- ucDigDot3=0;
- ucDigDot2=0;
- ucDigDot1=0;
- EA=1; //開總中斷
- ES=1; //允許串口中斷
- ET0=1; //允許定時中斷
- TR0=1; //啟動定時中斷
- }
總結陳詞:
前面我在第48節里講過用ds1302做的時鐘程序,但是后來很多網友建議,為了方便初學者學習編程思路,我應該用單片機定時器做一個時鐘程序。因此,我決定下一節講這方面的內容。欲知詳情,請聽下回分解----用單片機內部定時器做一個時鐘。
評論