一、原理簡介
51 單片機內部有一個全雙工串行接口。什麼叫全雙工串口呢?一般來說,只能接受或只能發送的稱爲單工串行;既可接收又可發送,但不能同時進行的稱爲半雙工;能同時接收和發送的串行口稱爲全雙工串行口。串行通信是指數據一位一位地按順序傳送的通信方式,其突出優點是隻需一根傳輸線,可大大降低硬件成本,適合遠距離通信。其缺點是傳輸速度較低。
與之前一樣,首先我們來了解單片機串口相關的寄存器。
SBUF 寄存器:它是兩個在物理上獨立的接收、發送緩衝器,可同時發送、接收數據,可通過指令對SBUF 的讀寫來區別是對接收緩衝器的操作還是對發送緩衝器的操作。從而控制外部兩條獨立的收發信號線RXD(P3.0)、TXD(P3.1),同時發送、接收數據,實現全雙工。
串行口控制寄存器SCON(見表1) 。
表1 SCON寄存器
表中各位(從左至右爲從高位到低位)含義如下。
SM0 和SM1 :串行口工作方式控制位,其定義如表2 所示。
表2 串行口工作方式控制位
其中,fOSC 爲單片機的時鐘頻率;波特率指串行口每秒鐘發送(或接收)的位數。
SM2 :多機通信控制位。 該僅用於方式2 和方式3 的多機通信。其中發送機SM2 = 1(需要程序控制設置)。接收機的串行口工作於方式2 或3,SM2=1 時,只有當接收到第9 位數據(RB8)爲1 時,才把接收到的前8 位數據送入SBUF,且置位RI 發出中斷申請引發串行接收中斷,否則會將接受到的數據放棄。當SM2=0 時,就不管第位數據是0 還是1,都將數據送入SBUF,並置位RI 發出中斷申請。工作於方式0 時,SM2 必須爲0。
REN :串行接收允許位:REN =0 時,禁止接收;REN =1 時,允許接收。
TB8 :在方式2、3 中,TB8 是發送機要發送的第9 位數據。在多機通信中它代表傳輸的地址或數據,TB8=0 爲數據,TB8=1 時爲地址。
RB8 :在方式2、3 中,RB8 是接收機接收到的第9 位數據,該數據正好來自發送機的TB8,從而識別接收到的數據特徵。
TI :串行口發送中斷請求標誌。當CPU 發送完一串行數據後,此時SBUF 寄存器爲空,硬件使TI 置1,請求中斷。CPU 響應中斷後,由軟件對TI 清零。
RI :串行口接收中斷請求標誌。當串行口接收完一幀串行數據時,此時SBUF 寄存器爲滿,硬件使RI 置1,請求中斷。CPU 響應中斷後,用軟件對RI 清零。
電源控制寄存器PCON(見表3) 。
表3 PCON寄存器
表中各位(從左至右爲從高位到低位)含義如下。
SMOD :波特率加倍位。SMOD=1,當串行口工作於方式1、2、3 時,波特率加倍。SMOD=0,波特率不變。
GF1、GF0 :通用標誌位。
PD(PCON.1) :掉電方式位。當PD=1 時,進入掉電方式。
IDL(PCON.0) :待機方式位。當IDL=1 時,進入待機方式。
另外與串行口相關的寄存器有前面文章敘述的定時器相關寄存器和中斷寄存器。定時器寄存器用來設定波特率。中斷允許寄存器IE 中的ES 位也用來作爲串行I/O 中斷允許位。當ES = 1,允許 串行I/O 中斷;當ES = 0,禁止串行I/O 中斷。中斷優先級寄存器IP的PS 位則用作串行I/O 中斷優先級控制位。當PS=1,設定爲高優先級;當PS =0,設定爲低優先級。
波特率計算:在瞭解了串行口相關的寄存器之後,我們可得出其通信波特率的一些結論:
① 方式0 和方式2 的波特率是固定的。
在方式0 中, 波特率爲時鐘頻率的1/12, 即fOSC/12,固定不變。
在方式2 中,波特率取決於PCON 中的SMOD 值,即波特率爲:
當SMOD=0 時,波特率爲fosc/64 ;當SMOD=1 時,波特率爲fosc/32。
② 方式1 和方式3 的波特率可變,由定時器1 的溢出率決定。
當定時器T1 用作波特率發生器時,通常選用定時初值自動重裝的工作方式2( 注意:不要把定時器的工作方式與串行口的工作方式搞混淆了)。其計數結構爲8 位,假定計數初值爲Count,單片機的機器週期爲T,則定時時間爲(256 ?Count)×T 。從而在1s內發生溢出的次數(即溢出率)可由公式(1)所示:
從而波特率的計算公式由公式(2)所示:
在實際應用時,通常是先確定波特率,後根據波特率求T1 定時初值,因此式(2)又可寫爲:
二、電路詳解
下面就對圖1 所示電路進行詳細說明。
圖1 串行通信實驗電路圖
最小系統部分(時鐘電路、復位電路等)第一講已經講過,在此不再敘述。我們重點來了解下與計算機通信的RS-232 接口電路。可以看到,在電路圖中,有TXD 和RXD 兩個接收和發送指示狀態燈,此外用了一個叫MAX3232 的芯片,那它是用來實現什麼的呢?首先我們要知道計算機上的串口是具有RS-232 標準的串行接口,而RS-232 的標準中定義了其電氣特性:高電平“1”信號電壓的範圍爲-15V~-3V,低電平“0”
信號電壓的範圍爲+3V~+15V。可能有些讀者會問,它爲什麼要以這樣的電氣特性呢?這是因爲高低電平用相反的電壓表示,至少有6V 的壓差,非常好的提高了數據傳輸的可靠性。由於單片機的管腳電平爲TTL,單片機與RS-232 標準的串行口進行通信時,首先要解決的便是電平轉換的問題。一般來說,可以選擇一些專業的集成電路芯片,如圖中的MAX3232。MAX3232 芯片內部集成了電壓倍增電路,單電源供電即可完成電平轉換,而且工作電壓寬,3V~5.5V 間均能正常工作。其典型應用如圖中所示,其外圍所接的電容對傳輸速率有影響,在試驗套件中採用的是0.1μF。
值得一提的是MAX3232 芯片擁有兩對電平轉換線路,圖中只用了一路,因此浪費了另一路,在一些場合可以將兩路並聯以獲得較強的驅動抗干擾能力。此外,我們有必要了解圖中與計算機相連的DB-9 型RS-232的引腳結構(見圖2)。
圖2 DB-9連接器接口圖
其各管腳定義如下(見表4)。
表4 DB-9型接口管腳定義
三、程序設計
本講設計實例程序如下:
#i nclude "AT89X52.h" (1)
void Init_Com(void) ( 2)
{
TMOD = 0x20; ( 3)
PCON = 0x00; ( 4)
SCON = 0x50; ( 5)
TH1 = 0xE8; ( 6)
TL1 = 0xE8; ( 7)
TR1 = 1; ( 8)
}
void main(void) ( 9)
{
unsigned char dat; ( 10)
Init_Com(); ( 11)
while(1) ( 12)
程序詳細說明:
(1)頭文件包含。
(2)聲明串口初始化程序。
(3)設置定時器1 工作在模式2,自動裝載初值(詳見第二講)。
(4)SMOD 位清0,波特率不加倍。
(5)串行口工作在方式1,並允許接收。
(6)定時器1 高8 位賦初值。波特率爲1200b/s(7)定時器1 低8 位賦初值。
(8)啓動定時器。
(9)主函數。
(10)定義一個字符型變量。
(11)初始化串口。
(12)死循環。
(13)如果接收到數據。
(14)將接收到的數據賦給之前定義的變量。
(15)將接收到的值輸出到P0 口。
(16)對接收標誌位清0,準備再次接收。
(17)將接收到的數據又發送出去。
(18)查詢是否發送完畢。
(19)對發送標誌位清0。
四、調試要點與實驗現象
接好硬件,通過冷啓動方式將程序所生成的。hex文件下載到單片機運行後,打開串口調試助手軟件,設置好波特率1200,復位單片機,然後在通過串口調試助手往單片機發送數據(見圖3),可以觀察到在接收窗口有發送的數據顯示,此外電路板上的串行通信指示燈也會閃爍,P0 口所接到LED 燈會閃爍所接收到的數據。
圖3 串口軟件調試界面
另外串口調試助手軟件使用時應注意的是,如果單片機開發板採用串口下載而且和串口調試助手是使用同一串口,則在打開串口軟件的同時不能給單片機下載程序,如需要下載,請首先點擊“關閉串口”,做發送實驗的時候,注意如果選中16 進制發送的就是數字或者字母的16 進制數值,比如發送“0”,實際接收的就應該是0x00,如果不選中,默認發送的是ASCII 碼值,此時發送“0”,實際接收的就應該是0x30,這點可以通過觀察板子P0 口上的對應的LED 指示出來。
五、總結
本講介紹了單片機串口通信的原理並給出了實例,通過該講,讀者可以瞭解和掌握51 單片機串口通信的原理與應用流程,利用串口通信,單片機可以與計算機相連,也可以單片機互聯或者多個單片機相互通信組網等,在實際的工程應用中非常廣泛。從學習的角度來說,熟練的利用串口將單片機系統中的相關信息顯示在計算機上可以很直觀方便的進行調試和開發。因此希望讀者能夠自己靈活應用串口通信到自己的實際開發當中,至此,51 單片機內部資源講述得差不多了,從下講開始,將要介紹單片機外圍電路。下講將講述單片機外接按鍵的原理與實例,敬請期待
六:分析
51單片機的串口,是個全雙工的串口,發送數據的同時,還可以接收數據。
當串行發送完畢後,將在標誌位 TI 置 1,同樣,當收到了數據後,也會在 RI 置 1。
無論 RI 或 TI 出現了 1,只要串口中斷處於開放狀態,單片機都會進入串口中斷處理程序。
在中斷程序中,要區分出來究竟是發送引起的中斷,還是接收引起的中斷,然後分別進行處理。
看到過一些書籍和文章,在串口收、發數據的處理方法上,很多人都有不妥之處。
接收數據時,基本上都是使用“中斷方式”,這是正確合理的。
即:每當收到一個新數據,就在中斷函數中,把 RI 清零,並用一個變量,通知主函數,收到了新數據。
發送數據時,很多的程序都是使用的“查詢方式”,就是執行 while(TI ==0); 這樣的語句來等待發送完畢。
這時,處理不好的話,就可能帶來問題。
看了一些網友編寫的程序,發現有如下幾條容易出錯:
1.有人在發送數據之前,先關閉了串口中斷!等待發送完畢後,再打開串口中斷。
這樣,在發送數據的等待期間內,如果收到了數據,將不能進入中斷函數,也就不會保存的這個新收到的數據。
這種處理方法,就會遺漏收到的數據。
2.有人在發送數據之前,並沒有關閉串口中斷,當 TI = 1 時,是可以進入中斷程序的。
但是,卻在中斷函數中,將 TI 清零!
這樣,在主函數中的while(TI ==0);,將永遠等不到發送結束的標誌。
3.還有人在中斷程序中,並沒有區分中斷的來源,反而讓發送引起的中斷,執行了接收中斷的程序。
對此,做而論道發表自己常用的方法:
接收數據時,使用“中斷方式”,清除 RI 後,用一個變量通知主函數,收到新數據。
發送數據時,也用“中斷方式”,清除 TI 後,用另一個變量通知主函數,數據發送完畢。
這樣一來,收、發兩者基本一致,編寫程序也很規範、易懂。
更重要的是,主函數中,不用在那兒死等發送完畢,可以有更多的時間查看其它的標誌。
實例:
求一個PC與單片機串口通信的程序,要求如下:
1、如果在電腦上發送以$開始的字符串,則將整個字符串原樣返回(字符串長度不是固定的)。
2、如果接收到1,則將P10置高電平,接收到0,P10置低電平。(用來控制一個LED)
單片機是STC89C52RC/晶振11.0592/波特率要求是9600或4800。謝謝!
問題補充:可能會將【$ABCD,123456,987654ccc,aasdasd,aaaa,sssd,4D】這樣的字符串(字符串長度約爲50-150個字符)傳送給單片機,只能能原樣返回。
- 最佳答案:
- 下列程序,已經調試成功。
- #include <REG52.H>
- sbit LED = P1^0;
- unsigned char UART_buff;
- bit New_rec = 0, Send_ed = 1, Money = 0;
- //----------------------------------------------
- void main (void)
- {
- SCON = 0x50; //串口方式1, 8-n-1, 允許接收.
- TMOD = 0x20; //T1方式2
- TH1 = 0xFD; [url=]//[email protected][/url]
- TL1 = 0xFD;
- TR1 = 1;
- ES = 1; //開中斷.
- EA = 1;
- while(Money == 0); //等着交費,呵呵,等着接收$.
- while(1) {
- if ((New_rec == 1) && (Send_ed == 1)) { //如果收到新數據及發送完畢
- SBUF = UART_buff; //那就發送.
- New_rec = 0;
- Send_ed = 0;
- } }
- }
- //----------------------------------------------
- void ser_int (void) interrupt 4
- {
- if(RI == 1) { //如果收到.
- RI = 0; //清除標誌.
- New_rec = 1;
- UART_buff = SBUF; //接收.
- if(UART_buff == '1') LED = 1;
- if(UART_buff == '0') LED = 0;
- if(UART_buff == '$') Money = 1;
- }
- else { //如果送畢.
- TI = 0; //清除標誌.
- Send_ed = 1;
- }
- }
- //----------------------------------------------
最佳答案:
下列程序,已經調試成功。
#include <REG52.H>
sbit LED = P1^0;
unsigned char UART_buff;
bit New_rec = 0, Send_ed = 1, Money = 0;
//----------------------------------------------
void main (void)
{
SCON = 0x50; //串口方式1, 8-n-1, 允許接收.
TMOD = 0x20; //T1方式2
TH1 = 0xFD; [url=]//[email protected][/url]
TL1 = 0xFD;
TR1 = 1;
ES = 1; //開中斷.
EA = 1;
while(Money == 0); //等着交費,呵呵,等着接收$.
while(1) {
if ((New_rec == 1) && (Send_ed == 1)) { //如果收到新數據及發送完畢
SBUF = UART_buff; //那就發送.
New_rec = 0;
Send_ed = 0;
} }
}
//----------------------------------------------
void ser_int (void) interrupt 4
{
if(RI == 1) { //如果收到.
RI = 0; //清除標誌.
New_rec = 1;
UART_buff = SBUF; //接收.
if(UART_buff == '1') LED = 1;
if(UART_buff == '0') LED = 0;
if(UART_buff == '$') Money = 1;
}
else { //如果送畢.
TI = 0; //清除標誌.
Send_ed = 1;
}
}
//----------------------------------------------
http://bbs.ednchina.com/BLOG_ARTICLE_3007162.HTM
串口接收程序是基於串口中斷的,單片機的串口每次接收到一字節數據產生一次中斷,然後再讀取某個寄存器就可以得到串口接收的數據了。然而在實際應用當中,基本上不會有單字節接收的情況。一般都是基於一定串口通信協議的多字節通信。在422或者485通信中,還可能是一個主機(一般是計算機)帶多個從機(相應的有單片機的板卡)。這就要求我們的單片機能夠在連續接收到的串口數據序列中識別出符合自己板卡對應的通信協議,來進行控制操作,不符合則不進行任何操作。簡而言之就是,單片機要在一串數據中找到符合一定規律的幾個字節的數據。
先來說下怎樣定串口協議吧。這個協議指的不是串口底層的協議,而是前面提到的數據幀協議。一般都是有幀頭(2~3個字節吧),數據(長度根據需要),結束位(1位,有時候設計成校驗字節,最簡單的校驗也就是前面所有數據求和)。
比如0xaa 0x55 +(數據部分省略)+校驗和(除了aa 55 之外數據的和),如果要是多板卡的話有時候還要在幀頭後面加一個板選字節(相當於3字節幀頭了)。
第一次寫串口接收程序的時候,我首先想到的就是定義一個全局變量(實際上最好是定義局部靜態變量),初始值設置爲0,然後每進一次中斷+1,然後加到串口通信協議的長度的時候再清零。然後判斷幀頭、校驗。寫完了之後我自己都覺得不對,一旦數據錯開了一位,後面就永遠都接收不到數了。無奈看了一下前輩們的代碼,跟我的思路差不多,只不過那個計數值跟接收到的數據時同時判斷的,而且每次中斷都要判斷,一旦不對計數的那個變量就清零。
廢話少說,直接上一段代碼讓大家看看就明白了。(通信協議姑且按照簡單的aa 55 一個字節數據 一個字節校驗,代碼是基於51單片機的)。接收成功則在中斷程序中把串口接收成功標誌位置1。
- 然後串口中斷部分
- void ser()interrupt 4
- {
- static unsigned char count;//串口接收計數的變量
- RI=0;//手動清某個寄存器,大家都懂的
- receive[count]=SBUF;
- if(count==0&&receive[count]==0xaa)//同時判斷count跟收到的數據
- {
- count=1;
- }
- else if(count==1&&receive[count]==0x55)
- {
- count=2;
- }
- else if(count==2)
- {
- count++;
- }
- else if(count==3&&receive[count]== receive [2])//判斷校驗和,數據多的話是求//和,或者其他的校驗方法,也可能是固定的幀尾
- {
- count=0;
- uart_flag =1;//串口接收成功標誌,爲1時在主程序中回覆,然後清零
- ES=0; //關中斷,回覆完了再ES=1;
- }
- else
- {
- count=0;//判斷不滿足條件就將計數值清零
- }
- }
然後串口中斷部分
void ser()interrupt 4
{
static unsigned char count;//串口接收計數的變量
RI=0;//手動清某個寄存器,大家都懂的
receive[count]=SBUF;
if(count==0&&receive[count]==0xaa)//同時判斷count跟收到的數據
{
count=1;
}
else if(count==1&&receive[count]==0x55)
{
count=2;
}
else if(count==2)
{
count++;
}
else if(count==3&&receive[count]== receive [2])//判斷校驗和,數據多的話是求//和,或者其他的校驗方法,也可能是固定的幀尾
{
count=0;
uart_flag =1;//串口接收成功標誌,爲1時在主程序中回覆,然後清零
ES=0; //關中斷,回覆完了再ES=1;
}
else
{
count=0;//判斷不滿足條件就將計數值清零
}
}
第一次做的串口大概就按照這個方法寫完了(我後來看過其他的代碼,有人用switch語句寫的,邏輯跟這個也差不多,不過我還是感覺用if else來寫清晰一些),
不過在測試的時候發現了bug,如果數據幀發送一半,然後突然停止,再來重新發,就會丟失一幀的數據。比如先接受到aa 55,然後斷了,再進來aa 55 01 01,就不受控制了。後來我也想到一個bug,如果在多設備通信中,屬於其他設備的的幀數據最後一位是aa(或者最後兩位爲aa 55 ,或者最後3位爲aa 55 板選),下一次通信的數據就接收不到了。
當時對於數據突然中斷的bug,沒有想到很好的解決辦法,不過這種情況機率極小,所以一直用這個方法寫也沒有問題。多設備通信最後一位恰好是aa的機率也很小,出問題的可能也很小。當時項目裏面的控制數據跟校驗恰好不可能出現aa,於是我把if(count==0&&receive[count]==0xaa)改成了if(receive[count]==0xaa)其他都沒變,解決了,沒有bug了。
後來我又寫了幾次單片機程序,纔想到了一些解決問題的方法——不過改天再接着寫吧,太累了,明天還要上班呢。
在後來的項目中,真的遇到了數據位跟校驗位都可能出現aa的情況。我考慮到每次數據都是連續發送的(至少我們用labwindows做的上位機程序是這樣的),成功接收到了一幀數據是要有一定時間回覆的,也就是說如果接收到一半,但是很長時間沒接收到數據,把計數值count清零就ok啦。涉及時間的問題自然要用定時器來實現啦。
這次的通信協議如下,串口波特率19200,2個幀頭aa 55 ,一個板選,6字節數據,一個校驗字節(除幀頭外其他數據的和)。
- 全局變量定義
- unsigned char boardAddr;//板選地址,通過檢測幾個io引腳,具體怎麼得到的就不寫了,很簡單的
- unsigned char g_DatRev [10]={0};//接收緩存
- bit retFlag=0;//爲1代表串口接收到了一幀數據
- 串口初始化函數,晶振22.1184
- void init_uart()
- {
- SCON = 0x50; //串口方式1允許接收
- TMOD = 0x21; //定時器1,方式2,8位自動重載,同時配置定時器0,工作方式1
- PCON = 0x80; // 波特率加倍
- TH1 = 0xfa;
- TL1 = 0xfa; //寫入串口定時器初值
- TH0=(65536-2000)/256; //寫入定時器0初值,串口傳輸一個字節時間爲(1/19200)*10,計算得0.52ms
- TL0=(65536-2000)%256; //定時器0定時大約1ms多
- EA=1;
- ET0=1; //波特率:19200 22.1184M 初值:250(0xfa)
- IE |= 0x90;
- TR1 = 1;
- }
- 串口中斷函數
- void UART_INT(void) interrupt 4
- {
- static unsigned char count;//串口接收計數的變量
- RI = 0;
- g_DatRev[count] = SBUF;
- if(g_DatRev[count]==0xaa&&count==0) //幀頭
- {
- count=1;
- }
- else if(count==1&&g_DatRev[count]==0x55)
- {
- count=2;
- }
- else if (count==2&&g_DatRev[2] == boardAddr)
- {
- CK = g_DatRev[count];
- count=3;
- }
- else if(count>=3&&count<9)
- {
- CK += g_DatRev[count];
- count ++;
- }
- else if(count == 9&&CK==g_DatRev[9])
- {
- ES = 0;
- retFlag = 1;
- count=0;
- }
- else
- {
- count=0;
- }
- resettimer();
- }
- //判斷count不爲0的話就啓動定時器
- void resettimer()
- {
- TR0=0;
- TH0=(65536-2000)/256;
- TL0=(65536-2000)%256;
- if(count!=0)
- {
- TR0=1;
- }
- }
- 定時器中斷函數
- void T0_time()interrupt 1
- {
- TR0=0;
- TH0=(65536-2000)/256;
- TL0=(65536-2000)%256;
- count=0;
- }
全局變量定義
unsigned char boardAddr;//板選地址,通過檢測幾個io引腳,具體怎麼得到的就不寫了,很簡單的
unsigned char g_DatRev [10]={0};//接收緩存
bit retFlag=0;//爲1代表串口接收到了一幀數據
串口初始化函數,晶振22.1184
void init_uart()
{
SCON = 0x50; //串口方式1允許接收
TMOD = 0x21; //定時器1,方式2,8位自動重載,同時配置定時器0,工作方式1
PCON = 0x80; // 波特率加倍
TH1 = 0xfa;
TL1 = 0xfa; //寫入串口定時器初值
TH0=(65536-2000)/256; //寫入定時器0初值,串口傳輸一個字節時間爲(1/19200)*10,計算得0.52ms
TL0=(65536-2000)%256; //定時器0定時大約1ms多
EA=1;
ET0=1; //波特率:19200 22.1184M 初值:250(0xfa)
IE |= 0x90;
TR1 = 1;
}
串口中斷函數
void UART_INT(void) interrupt 4
{
static unsigned char count;//串口接收計數的變量
RI = 0;
g_DatRev[count] = SBUF;
if(g_DatRev[count]==0xaa&&count==0) //幀頭
{
count=1;
}
else if(count==1&&g_DatRev[count]==0x55)
{
count=2;
}
else if (count==2&&g_DatRev[2] == boardAddr)
{
CK = g_DatRev[count];
count=3;
}
else if(count>=3&&count<9)
{
CK += g_DatRev[count];
count ++;
}
else if(count == 9&&CK==g_DatRev[9])
{
ES = 0;
retFlag = 1;
count=0;
}
else
{
count=0;
}
resettimer();
}
//判斷count不爲0的話就啓動定時器
void resettimer()
{
TR0=0;
TH0=(65536-2000)/256;
TL0=(65536-2000)%256;
if(count!=0)
{
TR0=1;
}
}
定時器中斷函數
void T0_time()interrupt 1
{
TR0=0;
TH0=(65536-2000)/256;
TL0=(65536-2000)%256;
count=0;
}
這種方法的確是本人自己想出來的,別人可能也這樣做過,但我這個絕對不是抄襲或者模仿來的。這樣寫的確可以避免前面提到過的bug,不過代價是多用了一個定時器的資源,而且中斷函數裏的內容更多了,佔用了更多的時間。
要是能把第一種方法改進一下就好了,主要是那個校驗不能爲aa的那個bug,因爲畢竟傳輸到一半突然斷了的可能性是非常小的。後來我想第一個判斷if(count==0&&receive[count]==0xaa)好像有點太嚴格了,考慮到第二字節的幀頭,跟板選地址不可能爲aa,於是把這個改寫爲if(count>=0&&count<=2&& receive[count]==0xaa),這樣就把bug出現的機率降到了非常小,也只是在前一幀結尾數據恰好爲 aa 55 板選 的時候纔出現,機率是多少大家自己算一下吧,呵呵。這樣我自己覺得,昨天寫的那種方法改進到這個程度,應該算可以啦,反正我是很滿意了。
實際上我還想過其他的方法,比如緩存的數組採用移位寄存的方式。拿前面的4個字節的協議爲例。
- void ser()interrupt 4
- {
- unsigned char i;
- RI=0;
- for(i=0;i<3;i++)
- {
- receive[i]=receive[i+1];
- }
- receive[3]=SBUF;
- if(reveive[0]==0xaa&&receive[1]==0x55&&receive[2]==receive[3])
- {
- ret_flag=1;
- ES = 0;
- }
- }
void ser()interrupt 4
{
unsigned char i;
RI=0;
for(i=0;i<3;i++)
{
receive[i]=receive[i+1];
}
receive[3]=SBUF;
if(reveive[0]==0xaa&&receive[1]==0x55&&receive[2]==receive[3])
{
ret_flag=1;
ES = 0;
}
}
這段代碼看上去可是簡單明瞭,這樣判斷可是不錯啊,同時判斷幀頭跟校驗不會產生前面提到的bug。說實話當時我剛想出這種方法並寫出來的時候,馬上就被我給否了。那個for循環可真是很佔時間的啊,延時函數都是這樣寫的。每次都循環一下,這延時太長,通信速度太快的話就不能接收到下一字節數據了。最要命的是這個時間的長度是隨着通信協議幀的字節數增加而增加的,如果一次要接收幾十個字節,肯定就玩完了。這種方法我一次都沒用過。
不過我居然又想出來了這種方法的改良措施,是前兩天剛想出來的,呵呵,還沒有實踐過呢。
下面代碼的協議就按第二段程序(定時器清零的那個協議,一共10字節)
全局變量
- bit ret_flag;
- unsigned char receive[256]={0};
- unsigned char boardaddress;
- 中斷函數
- void ser()interrupt 4
- {
- static unsigned char i=0;
- static unsigned char total=0;
- RI=0;
- receive[i]=SBUF;
- total=total-receive[i-7]+receive[i-1];
- if(receive[i-9]==0xaa&&receive[i-8]==0x55
- &&receive[i-7]==boardaddress&&receive[i]==total
- )
- {
- ret_flag=1;
- ES = 0;
- }
- i++;
- }
bit ret_flag;
unsigned char receive[256]={0};
unsigned char boardaddress;
中斷函數
void ser()interrupt 4
{
static unsigned char i=0;
static unsigned char total=0;
RI=0;
receive[i]=SBUF;
total=total-receive[i-7]+receive[i-1];
if(receive[i-9]==0xaa&&receive[i-8]==0x55
&&receive[i-7]==boardaddress&&receive[i]==total
)
{
ret_flag=1;
ES = 0;
}
i++;
}
之所以要定義256個長度的數組,就是爲了能夠讓數組“首尾相接”。因爲0 -1 = 255 , 255+1 = 0。而且我在計算校驗的時候也改進了算法,不會因爲數據長度的增加而增加計算校驗值的時間。這種方法也是我不久前才想出來的,所以還沒有經過實際的驗證。上面的代碼可能會有邏輯上的錯誤,如果真有錯誤,有網友看出來的話,請在下面留言告訴我。這個方法也是我原創的哦,別人也肯能會想到,不過我這個絕對不是抄襲別人的。
上面的代碼最大的缺點就是變量定義的太多了,太佔ram資源了,編譯的時候可能會出現錯誤,畢竟51單片機才128字節的ram(有的資源也很豐富的,比如c8051系列的),這一下子就是256字節的變量。不過對於資源多一些的單片機,這樣寫還是可以的。要是能有4bit在一起的數據類型就好了,呵呵,verilog代碼裏面是可以的,C語言裏貌似不行啊。
要想能在例如51單片機上運行,只能按照下面的折中方式了,也就是把i相關的量都與一個0x0f
- 全局變量
- bit ret_flag;
- unsigned char receive[16]={0};// 可以考慮在定義時加上idata,畢竟還可能是32
- //或者64長度的數組呢unsigned char idata receive[16]={0};
- unsigned char boardaddress;
- 中斷函數
- void ser()interrupt 4
- {
- static unsigned char i=0;
- static unsigned char total=0;
- RI=0;
- receive[i&0x0f]=SBUF;
- total=total-receive[(i-7)&0x0f]+receive[(i-1)&0x0f];
- if(receive[(i-9)&0x0f]==0xaa&&receive[(i-8)&0x0f]==0x55
- &&receive[(i-7)&0x0f]==boardaddress&&receive[i&0x0f]==total
- )
- {
- ret_flag=1;
- ES = 0;
- }
- i++;
- }