串口數(shù)據(jù)包解析代碼分析
2019/1/10??????點(diǎn)擊:
這里以串口作為傳輸媒介,介紹下怎樣來(lái)發(fā)送接收一個(gè)完整的數(shù)據(jù)包。過(guò)程涉及到封包與解包。設(shè)計(jì)一個(gè)良好的包傳輸機(jī)制很有利于數(shù)據(jù)傳輸?shù)姆€(wěn)定性以及正確性。串口只是一種傳輸媒介,這種包機(jī)制同時(shí)也可以用于SPI,I2C的總線下的數(shù)據(jù)傳輸。在單片機(jī)通信系統(tǒng)(多機(jī)通信以及PC與單片機(jī)通信)中,是很常見的問(wèn)題。
一、根據(jù)幀頭幀尾或者幀長(zhǎng)檢測(cè)一個(gè)數(shù)據(jù)幀
1、幀頭+數(shù)據(jù)+校驗(yàn)+幀尾
這是一個(gè)典型的方案,但是對(duì)幀頭與幀尾在設(shè)計(jì)的時(shí)候都要注意,也就是說(shuō)幀頭、幀尾不能在所傳輸?shù)臄?shù)據(jù)域中出現(xiàn),一旦出現(xiàn)可能就被誤判。如果用中斷來(lái)接收的話,程序基本可以這么實(shí)現(xiàn):
unsigned char recstatu;//表示是否處于一個(gè)正在接收數(shù)據(jù)包的狀態(tài)
unsigned char ccnt; //計(jì)數(shù)
unsigned char packerflag;//是否接收到一個(gè)完整的數(shù)據(jù)包標(biāo)志
unsigned char rxbuf[100];//接收數(shù)據(jù)的緩沖區(qū)
void UartHandler()
{
unsigned char tmpch;
tmpch = UARTRBR;
if(tmpch 是包頭) //檢測(cè)是否是包頭
{
recstatu = 1;
ccnt = 0 ;
packerflag = 0;
return ;
}
if(tmpch是包尾) //檢測(cè)是否是包尾
{
recstatu = 0;
packerflag = 1; //用于告知系統(tǒng)已經(jīng)接收到一個(gè)完整的數(shù)據(jù)包
return ;
}
if(recstatu ==1) //是否處于接收數(shù)據(jù)包狀態(tài)
{
rxbuf[ccnt++] = tmpch;
}
}
上面也就是接收一個(gè)數(shù)據(jù)包,但是再次提醒,包頭和包尾不能在數(shù)據(jù)域中出現(xiàn),一旦出現(xiàn)將會(huì)出現(xiàn)誤判。另外一個(gè)。數(shù)據(jù)的校驗(yàn)算法是很必要的,在數(shù)據(jù)傳輸中,由于受到干擾,很難免有時(shí)出現(xiàn)數(shù)據(jù)錯(cuò)誤,加上校驗(yàn)碼可在發(fā)現(xiàn)數(shù)據(jù)傳輸錯(cuò)誤時(shí),可以要求數(shù)據(jù)的另一方重新發(fā)送,或是進(jìn)行簡(jiǎn)單的丟棄處理。校驗(yàn)算法不一定要很復(fù)雜,普通的加和,異或,以及循環(huán)冗余都是可以的。我上面的接收程序在接收數(shù)據(jù)時(shí),已經(jīng)將包頭和包尾去掉,這些可以根據(jù)自己的需求加上,關(guān)鍵是要理解原理。
上述包協(xié)議出現(xiàn)了以下的幾種變種:
1.1 幀頭+數(shù)據(jù)長(zhǎng)度+數(shù)據(jù)+校驗(yàn)值
1.2包長(zhǎng)+校驗(yàn)值
上面兩種其實(shí)都是知道了數(shù)據(jù)包的長(zhǎng)度,然后根據(jù)接收字節(jié)的長(zhǎng)度來(lái)判斷一個(gè)完整的數(shù)據(jù)包。例如,定義一個(gè)數(shù)據(jù)包的長(zhǎng)度為256字節(jié),那我們就可以一直接收,直到接收到256個(gè)字節(jié),就認(rèn)為是一個(gè)數(shù)據(jù)包。但是,會(huì)不會(huì)存在問(wèn)題呢?比如說(shuō)從機(jī)向主機(jī)發(fā)送數(shù)據(jù),發(fā)送了一半,掉電,重啟,開機(jī)后繼續(xù)發(fā)送,這很明顯接收到的數(shù)據(jù)就不對(duì)了,所以此時(shí)很有必要定義一個(gè)超限時(shí)間,比如我們可以維護(hù)下面這樣的一個(gè)結(jié)構(gòu)體。
struct uartrd{
char rd[ 256];
unsigned int timeout;
}
成員變量rd用來(lái)存放接收到的數(shù)據(jù)字節(jié);成員變量timeout用來(lái)維護(hù)超時(shí)值,這里主要討論這個(gè)。這個(gè)數(shù)值怎么維護(hù)呢,可以用一個(gè)定時(shí)器來(lái)維護(hù),以可以放在普通的滴答中斷里面來(lái)維護(hù),也可以根據(jù)系統(tǒng)運(yùn)行一條指令的周期,在自己的循環(huán)中來(lái)維護(hù),給其設(shè)置個(gè)初值,比如說(shuō)100,當(dāng)有第一個(gè)數(shù)據(jù)到來(lái)以后,timeout在指定的時(shí)間就會(huì)減少1,減少到0時(shí),就認(rèn)為超時(shí),不論是否接收到足夠的數(shù)據(jù),都應(yīng)該拋棄。
二、根據(jù)接收超時(shí)來(lái)判斷一個(gè)數(shù)據(jù)包
2.1 數(shù)據(jù)+校驗(yàn)
核心思想是如果在達(dá)到一定的時(shí)間沒有接受到數(shù)據(jù),就認(rèn)為數(shù)據(jù)包接收完成。modbus協(xié)議里就有通過(guò)時(shí)間間隔來(lái)判斷幀結(jié)束的。具體實(shí)現(xiàn)是要使用一個(gè)定時(shí)器,在接收到第一個(gè)數(shù)據(jù)時(shí)候,開啟定時(shí)器,在接收到一個(gè)數(shù)據(jù)時(shí)候,就將定時(shí)器清零,讓定時(shí)器重新開始計(jì)時(shí),如果設(shè)定的超時(shí)時(shí)間到(超時(shí)時(shí)間長(zhǎng)度可以設(shè)置為5個(gè)正常接收的周期),則認(rèn)為在這一段時(shí)間內(nèi)沒有接受到新的數(shù)據(jù),就認(rèn)為接收到一個(gè)完整的數(shù)據(jù)包了。
一、根據(jù)幀頭幀尾或者幀長(zhǎng)檢測(cè)一個(gè)數(shù)據(jù)幀
1、幀頭+數(shù)據(jù)+校驗(yàn)+幀尾
這是一個(gè)典型的方案,但是對(duì)幀頭與幀尾在設(shè)計(jì)的時(shí)候都要注意,也就是說(shuō)幀頭、幀尾不能在所傳輸?shù)臄?shù)據(jù)域中出現(xiàn),一旦出現(xiàn)可能就被誤判。如果用中斷來(lái)接收的話,程序基本可以這么實(shí)現(xiàn):
unsigned char recstatu;//表示是否處于一個(gè)正在接收數(shù)據(jù)包的狀態(tài)
unsigned char ccnt; //計(jì)數(shù)
unsigned char packerflag;//是否接收到一個(gè)完整的數(shù)據(jù)包標(biāo)志
unsigned char rxbuf[100];//接收數(shù)據(jù)的緩沖區(qū)
void UartHandler()
{
unsigned char tmpch;
tmpch = UARTRBR;
if(tmpch 是包頭) //檢測(cè)是否是包頭
{
recstatu = 1;
ccnt = 0 ;
packerflag = 0;
return ;
}
if(tmpch是包尾) //檢測(cè)是否是包尾
{
recstatu = 0;
packerflag = 1; //用于告知系統(tǒng)已經(jīng)接收到一個(gè)完整的數(shù)據(jù)包
return ;
}
if(recstatu ==1) //是否處于接收數(shù)據(jù)包狀態(tài)
{
rxbuf[ccnt++] = tmpch;
}
}
上面也就是接收一個(gè)數(shù)據(jù)包,但是再次提醒,包頭和包尾不能在數(shù)據(jù)域中出現(xiàn),一旦出現(xiàn)將會(huì)出現(xiàn)誤判。另外一個(gè)。數(shù)據(jù)的校驗(yàn)算法是很必要的,在數(shù)據(jù)傳輸中,由于受到干擾,很難免有時(shí)出現(xiàn)數(shù)據(jù)錯(cuò)誤,加上校驗(yàn)碼可在發(fā)現(xiàn)數(shù)據(jù)傳輸錯(cuò)誤時(shí),可以要求數(shù)據(jù)的另一方重新發(fā)送,或是進(jìn)行簡(jiǎn)單的丟棄處理。校驗(yàn)算法不一定要很復(fù)雜,普通的加和,異或,以及循環(huán)冗余都是可以的。我上面的接收程序在接收數(shù)據(jù)時(shí),已經(jīng)將包頭和包尾去掉,這些可以根據(jù)自己的需求加上,關(guān)鍵是要理解原理。
上述包協(xié)議出現(xiàn)了以下的幾種變種:
1.1 幀頭+數(shù)據(jù)長(zhǎng)度+數(shù)據(jù)+校驗(yàn)值
1.2包長(zhǎng)+校驗(yàn)值
上面兩種其實(shí)都是知道了數(shù)據(jù)包的長(zhǎng)度,然后根據(jù)接收字節(jié)的長(zhǎng)度來(lái)判斷一個(gè)完整的數(shù)據(jù)包。例如,定義一個(gè)數(shù)據(jù)包的長(zhǎng)度為256字節(jié),那我們就可以一直接收,直到接收到256個(gè)字節(jié),就認(rèn)為是一個(gè)數(shù)據(jù)包。但是,會(huì)不會(huì)存在問(wèn)題呢?比如說(shuō)從機(jī)向主機(jī)發(fā)送數(shù)據(jù),發(fā)送了一半,掉電,重啟,開機(jī)后繼續(xù)發(fā)送,這很明顯接收到的數(shù)據(jù)就不對(duì)了,所以此時(shí)很有必要定義一個(gè)超限時(shí)間,比如我們可以維護(hù)下面這樣的一個(gè)結(jié)構(gòu)體。
struct uartrd{
char rd[ 256];
unsigned int timeout;
}
成員變量rd用來(lái)存放接收到的數(shù)據(jù)字節(jié);成員變量timeout用來(lái)維護(hù)超時(shí)值,這里主要討論這個(gè)。這個(gè)數(shù)值怎么維護(hù)呢,可以用一個(gè)定時(shí)器來(lái)維護(hù),以可以放在普通的滴答中斷里面來(lái)維護(hù),也可以根據(jù)系統(tǒng)運(yùn)行一條指令的周期,在自己的循環(huán)中來(lái)維護(hù),給其設(shè)置個(gè)初值,比如說(shuō)100,當(dāng)有第一個(gè)數(shù)據(jù)到來(lái)以后,timeout在指定的時(shí)間就會(huì)減少1,減少到0時(shí),就認(rèn)為超時(shí),不論是否接收到足夠的數(shù)據(jù),都應(yīng)該拋棄。
二、根據(jù)接收超時(shí)來(lái)判斷一個(gè)數(shù)據(jù)包
2.1 數(shù)據(jù)+校驗(yàn)
核心思想是如果在達(dá)到一定的時(shí)間沒有接受到數(shù)據(jù),就認(rèn)為數(shù)據(jù)包接收完成。modbus協(xié)議里就有通過(guò)時(shí)間間隔來(lái)判斷幀結(jié)束的。具體實(shí)現(xiàn)是要使用一個(gè)定時(shí)器,在接收到第一個(gè)數(shù)據(jù)時(shí)候,開啟定時(shí)器,在接收到一個(gè)數(shù)據(jù)時(shí)候,就將定時(shí)器清零,讓定時(shí)器重新開始計(jì)時(shí),如果設(shè)定的超時(shí)時(shí)間到(超時(shí)時(shí)間長(zhǎng)度可以設(shè)置為5個(gè)正常接收的周期),則認(rèn)為在這一段時(shí)間內(nèi)沒有接受到新的數(shù)據(jù),就認(rèn)為接收到一個(gè)完整的數(shù)據(jù)包了。
簡(jiǎn)單的小的總結(jié),上述幾種方法都還是較為常用的,在具體的實(shí)現(xiàn)上,可以根據(jù)具體的實(shí)際情況,設(shè)計(jì)出具體的通訊協(xié)議。數(shù)據(jù)校驗(yàn)位,有時(shí)候感覺不出來(lái)其重要性,但是一定要加上,對(duì)數(shù)據(jù)進(jìn)行一個(gè)相關(guān)的驗(yàn)證還是必要的?,F(xiàn)在很在MCU都帶有FIFO,DMA等功能,所以有時(shí)候利用上這些特性,可以設(shè)計(jì)出更好的通訊方式。有的人問(wèn)在接受串口數(shù)據(jù)時(shí)候是應(yīng)該中斷一次接收一個(gè),還是進(jìn)入中斷后接收一段數(shù)據(jù)呢,我認(rèn)為應(yīng)該中斷接收一個(gè),因?yàn)镃PU是很快的,至少對(duì)于串口是這樣,在接受每個(gè)數(shù)據(jù)的間隔期間,處理器還是可以做些其他工作的。這是在裸機(jī)下的模型。在多線程中,那就可以直接建立一個(gè)數(shù)據(jù)接收線程。
- 上一篇:Unity3d 動(dòng)態(tài)加載模型文件的方法 2019/1/22
- 下一篇:unity3d中協(xié)程Coroutine的的原理及使用 2019/1/9