玩轉單片機之三--串口通信,接收數據
#define uchar unsigned char//byte
本文引用地址:http://dyxdggzs.com/article/201611/315869.htm#define uint unsigned int//word
sbit led1=P0^0;
sbit fir=P2^4; //fir=0;工作
sbit sec=P2^5; //sec=0;工作
sbit thi=P2^6; //thi=0;工作
sbit fot=P2^7; //fot=0;工作
uchar table[]={0x28,0xeb,0x32,0xa2,0xe1,0xa4,0x24,0xea,0x20,0xa0};
//P1=table[i];/* 0123456789 */
static uchar dispbuf[5];
//動(dòng)態(tài)顯示數字的函數
void scandisp(void)
{
unsigned int i;
fir=0;
P1=table[ dispbuf[0] ];
for(i=0;i<200;i++);
fir=1;
sec=0;
P1=table[ dispbuf[1] ];
for(i=0;i<200;i++);
sec=1;
thi=0;
P1=table[ dispbuf[2] ];
for(i=0;i<200;i++);
thi=1;
fot=0;
P1=table[ dispbuf[3] ];
for(i=0;i<200;i++);
fot=1;
}
//十六進(jìn)制轉十進(jìn)制存儲
void HEX_TO_BCD(unsigned int n)
{
dispbuf[3]=n/1000;
dispbuf[2]=(n/100)%10;
dispbuf[1]=(n/10)%10;
dispbuf[0]=n%10;
}
void main(void)
{
uchar a;
uint mydata;
mydata=0x00;
TMOD=0x20;
PCON=0x00;
SCON=0x50;
TL1=0xfd;
TH1=0xfd;
TR1=1;
while(1)//動(dòng)態(tài)現實(shí)是接收的數據
{//如果沒(méi)有接收到數據,RI=0,一直循環(huán)顯示原值
//如果有接收到數據,RI=1,跳出循環(huán)重新計算并再次進(jìn)入循環(huán)
HEX_TO_BCD(mydata);
while(RI==0)scandisp();
RI=0;//重新置0
a=SBUF;//從緩沖區獲取數據
mydata=a;
//HEX_TO_BCD(mydata);
//scandisp();
//SBUF=a;
//while(TI==0)
//TI=0;
}
}
1. 自定義數據通信協(xié)議
這里所說(shuō)的數據協(xié)議是建立在物理層之上的通信數據包格式。所謂通信的物理層就是指我們通常所用到的RS232、RS485、紅外、光纖、無(wú)線(xiàn)等等通信方式。在這個(gè)層面上,底層軟件提供兩個(gè)基本的操作函數:發(fā)送一個(gè)字節數據、接收一個(gè)字節數據。所有的數據協(xié)議全部建立在這兩個(gè)操作方法之上。
通信中的數據往往以數據包的形式進(jìn)行傳送的,我們把這樣的一個(gè)數據包稱(chēng)作為一幀數據。類(lèi)似于網(wǎng)絡(luò )通信中的TCPIP協(xié)議一般,比較可靠的通信協(xié)議往往包含有以下幾個(gè)組成部分:幀頭、地址信息、數據類(lèi)型、數據長(cháng)度、數據塊、校驗碼、幀尾。
幀頭和幀尾用于數據包完整性的判別,通常選擇一定長(cháng)度的固定字節組成,要求是在整個(gè)數據鏈中判別數據包的誤碼率越低越好。減小固定字節數據的匹配機會(huì ),也就是說(shuō)使幀頭和幀尾的特征字節在整個(gè)數據鏈中能夠匹配的機會(huì )最小。通常有兩種做法,一、減小特征字節的匹配幾率。二、增加特征字節的長(cháng)度。通常選取第一種方法的情況是整個(gè)數據鏈路中的數據不具有隨即性,數據可預測,可以通過(guò)人為選擇幀頭和幀尾的特征字來(lái)避開(kāi),從而減小特征字節的匹配幾率。使用第二種方法的情況更加通用,適合于數據隨即的場(chǎng)合。通過(guò)增加特征字節的長(cháng)度減小匹配幾率,雖然不能夠完全的避免匹配的情況,但可以使匹配幾率大大減小,如果碰到匹配的情況也可以由校驗碼來(lái)進(jìn)行檢測,因此這種情況在絕大多說(shuō)情況下比較可靠。
地址信息主要用于多機通信中,通過(guò)地址信息的不同來(lái)識別不同的通信終端。在一對多的通信系統中,可以只包含目的地址信息。同時(shí)包含源地址和目的地址則適用于多對多的通信系統。
數據類(lèi)型、數據長(cháng)度和數據塊是主要的數據部分。數據類(lèi)型可以標識后面緊接著(zhù)的是命令還是數據。數據長(cháng)度用于指示有效數據的個(gè)數。
校驗碼則用來(lái)檢驗數據的完整性和正確性。通常對數據類(lèi)型、數據長(cháng)度和數據塊三個(gè)部分進(jìn)行相關(guān)的運算得到。最簡(jiǎn)單的做法可是對數據段作累加和,復雜的也可以對數據進(jìn)行CRC運算等等,可以根據運算速度、容錯度等要求來(lái)選取。
2. 上位機和下位機中的數據發(fā)送
物理通信層中提供了兩個(gè)基本的操作函數,發(fā)送一個(gè)字節數據則為數據發(fā)送的基礎。數據包的發(fā)送即把數據包中的左右字節按照順序一個(gè)一個(gè)的發(fā)送數據而已。當然發(fā)送的方法也有不同。
在單片機系統中,比較常用的方法是直接調用串口發(fā)送單個(gè)字節數據的函數。這種方法的缺點(diǎn)是需要處理器在發(fā)送過(guò)程中全程參與,優(yōu)點(diǎn)是所要發(fā)送的數據能夠立即的出現在通信線(xiàn)路上,能夠立即被接收端接收到。另外一種方法是采用中斷發(fā)送的方式,所有需要發(fā)送的數據被送入一個(gè)緩沖區,利用發(fā)送中斷將緩沖區中的數據發(fā)送出去。這種方法的優(yōu)點(diǎn)是占用處理器資源小,但是可能出現需要發(fā)送的數據不能立即被發(fā)送的情況,不過(guò)這種時(shí)延相當的小。對于51系列單片機,比較傾向于采用直接發(fā)送的方式,采用中斷發(fā)送的方式比較占用RAM資源,而且對比直接發(fā)送來(lái)說(shuō)也沒(méi)有太多的優(yōu)點(diǎn)。以下是51系列單片機中發(fā)送單個(gè)字節的函數。
void SendByte(unsigned char ch)
{
SBUF = ch;
while(TI == 0);
TI = 0;
}
上位機中關(guān)于串口通信的方式也有多種,這種方式不是指數據有沒(méi)有緩沖的問(wèn)題,而是操作串口的方式不同,因為PC上數據發(fā)送基本上都會(huì )被緩沖后再發(fā)送。對于編程來(lái)說(shuō)操作串口有三種方式,一、使用windows系統中自帶的串口通信控件,這種方式使用起來(lái)比較簡(jiǎn)單,需要注意的是接收時(shí)的阻塞處理和線(xiàn)程機制。二、使用系統的API直接進(jìn)行串口數據的讀取,在windows和linux系統中,設備被虛擬為文件,只需要利用系統提供的API函數即可進(jìn)行串口數據的發(fā)送和讀取。三、使用串口類(lèi)進(jìn)行串口操作。在此只介紹windows環(huán)境下利用串口類(lèi)編程的方式。
CSerialPort是比較好用的串口類(lèi)。它提供如下的串口操作方法:
void WriteToPort(char* string, int len);
串口初始化成功后,調用此函數即可向串口發(fā)送數據。為了避免串口緩沖所帶來(lái)的延時(shí),可以開(kāi)啟串口的沖刷機制。
3. 下位機中的數據接收和協(xié)議解析
下位機接收數據也有兩種方式,一、等待接收,處理器一直查詢(xún)串口狀態(tài),來(lái)判斷是否接收到數據。二、中斷接收。兩種方法的優(yōu)缺點(diǎn)在此前的一篇關(guān)于串口通信的文章中詳細討論過(guò)。得出的結論是采用中斷接收的方法比較好。
數據包的解析過(guò)程可以設置到不同的位置。如果協(xié)議比較簡(jiǎn)單,整個(gè)系統只是處理一些簡(jiǎn)單的命令,那么可以直接把數據包的解析過(guò)程放入到中斷處理函數中,當收到正確的數據包的時(shí)候,置位相應的標志,在主程序中再對命令進(jìn)行處理。如果協(xié)議稍微復雜,比較好的方式是將接收的數據存放于緩沖區中,主程序讀取數據后進(jìn)行解析。也有兩種方式交叉使用的,比如一對多的系統中,首先在接收中斷中解析“連接”命令,連接命令接收到后主程序進(jìn)入設置狀態(tài),采用查詢(xún)的方式來(lái)解析其余的協(xié)議。
以下給出具體的實(shí)例。在這個(gè)系統中,串口的命令非常簡(jiǎn)單。所有的協(xié)議全部在串口中斷中進(jìn)行。數據包的格式如下:
0x55, 0xAA, 0x7E, 0x12, 0xF0, 0x02, 0x23, 0x45, SUM, XOR, 0x0D
其中0x55, 0xAA, 0x7E為數據幀的幀頭,0x0D為幀尾,0x12為設備的目的地址,0xF0為源地址,0x02為數據長(cháng)度,后面接著(zhù)兩個(gè)數據0x23, 0x45,從目的地址開(kāi)始結算累加、異或校驗和,到數據的最后一位結束。
協(xié)議解析的目的,首先判斷數據包的完整性,正確性,然后提取數據類(lèi)型,數據等數據,存放起來(lái)用于主程序處理。代碼如下:
if(state_machine == 0) //協(xié)議解析狀態(tài)機
{
if(rcvdat == 0x55) //接收到幀頭第一個(gè)數據
state_machine = 1;
else
state_machine = 0; //狀態(tài)機復位
}
else if(state_machine == 1)
{
if(rcvdat == 0xAA) //接收到幀頭第二個(gè)數據
state_machine = 2;
else
state_machine = 0; //狀態(tài)機復位
}
else if(state_machine == 2)
{
if(rcvdat == 0x7E) //接收到幀頭第三個(gè)數據
state_machine = 3;
else
state_machine = 0; //狀態(tài)機復位
}
else if(state_machine == 3)
{
sumchkm = rcvdat; //開(kāi)始計算累加、異或校驗和
xorchkm = rcvdat;
if(rcvdat == m_SrcAdr) //判斷目的地址是否正確
state_machine = 4;
else
state_machine = 0;
}
else if(state_machine == 4)
{
sumchkm += rcvdat;
xorchkm ^= rcvdat;
if(rcvdat == m_DstAdr) //判斷源地址是否正確
state_machine = 5;
else
state_machine = 0;
}
else if(state_machine == 5)
{
lencnt = 0; //接收數據計數器
rcvcount = rcvdat; //接收數據長(cháng)度
sumchkm += rcvdat;
xorchkm ^= rcvdat;
state_machine = 6;
}
else if(state _machine == 6 || state _machine == 7)
{
m_ucData[lencnt++] = rcvdat; //數據保存
sumchkm += rcvdat;
xorchkm ^= rcvdat;
if(lencnt == rcvcount) //判斷數據是否接收完畢
state_machine = 8;
else
state_machine = 7;
}
else if(state_machine == 8)
{
if(sumchkm == rcvdat) //判斷累加和是否相等
state_machine = 9;
else
state_machine = 0;
}
else if(state_machine == 9)
{
if(xorchkm == rcvdat) //判斷異或校驗和是否相等
state_machine = 10;
else
state_machine = 0;
}
else if(state_machine == 10)
{
if(0x0D == rcvdat) //判斷是否接收到幀尾結束符
{
retval = 0xaa; //置標志,表示一個(gè)數據包接收到
}
state_machine = 0; //復位狀態(tài)機
}
此過(guò)程中,使用了一個(gè)變量state_machine作為協(xié)議狀態(tài)機的轉換狀態(tài),用于確定當前字節處于一幀數據中的那個(gè)部位,同時(shí)在接收過(guò)程中自動(dòng)對接收數據進(jìn)行校驗和處理,在數據包接收完的同時(shí)也進(jìn)行了校驗的比較。因此當幀尾結束符接收到的時(shí)候,則表示一幀數據已經(jīng)接收完畢,并且通過(guò)了校驗,關(guān)鍵數據也保存到了緩沖去中。主程序即可通過(guò)retval的標志位來(lái)進(jìn)行協(xié)議的解析處理。
接收過(guò)程中,只要哪一步收到的數據不是預期值,則直接將狀態(tài)機復位,用于下一幀數據的判斷,因此系統出現狀態(tài)死鎖的情況非常少,系統比較穩定,如果出現丟失數據包的情況也可由上位機進(jìn)行命令的補發(fā),不過(guò)這種情況筆者還沒(méi)有碰到。
對于主程序中進(jìn)行協(xié)議處理的過(guò)程與此類(lèi)似,主程序循環(huán)中不斷的讀取串口緩沖區的數據,此數據即參與到主循環(huán)中的協(xié)議處理過(guò)程中,代碼與上面所述完全一樣。
評論