李逸楠,孫麗君,王 欣,所玉君
(1.天津津航計算技術(shù)研究所,天津 300308;2.天津市航空電子綜合顯示控制重點實驗室,天津 300308;3.天津航海技術(shù)研究所,天津 300131)
在嵌入式系統(tǒng)中,分系統(tǒng)間最常用的數(shù)據(jù)傳輸方式是采用各種串口進(jìn)行通訊,比如RS232/RS422/RS485等[1-3]。拋開底層硬件設(shè)計的不同,通常在鏈路層通過一些協(xié)議來保證發(fā)送方與接收方數(shù)據(jù)的一致性。
SLIP協(xié)議是一種常用的鏈路層通訊協(xié)議,規(guī)定了幀頭、幀尾、在幀頭幀尾之間如果出現(xiàn)幀頭或幀尾字符,則按照一定的規(guī)則進(jìn)行替換,保證發(fā)送方在幀頭幀尾間不會出現(xiàn)幀頭幀尾對應(yīng)的字符[4];接收方收到數(shù)據(jù)后,也需要按照幀頭幀尾把數(shù)據(jù)解析出來,再按照替換規(guī)則把有效數(shù)據(jù)還原。這時得到的數(shù)據(jù)就可以按照上層協(xié)議進(jìn)一步解析,得到有意義的數(shù)據(jù)。不同的項目有具體的上層協(xié)議,解析的方法不盡相同,但基本思路是一致的。本文涉及到的協(xié)議及硬件可返回的數(shù)據(jù)如下。
以0xc0為幀頭,0xc0為幀尾,幀頭幀尾間如果出現(xiàn)0xc0,則以連續(xù)兩個字符0xdb和0xdc取代;如果幀頭幀尾間出現(xiàn)0xdb,那么就用連續(xù)兩個字符0xdb和0xdd替代;接收方也按照這個規(guī)則還原接收到的數(shù)據(jù)。
經(jīng)SLIP協(xié)議解析后的數(shù)據(jù)為一個完整的數(shù)據(jù)幀,格式如表1所示。
表1 消息幀格式
UINT16 ReciveCοunt:自硬件上電以來收取到的所有字符數(shù),數(shù)據(jù)類型為UINT16,無符號的16位整型。
UINT8 DataRev[2048]:數(shù)據(jù)接收緩存,硬件接收到數(shù)據(jù)之后會按順序記錄在緩存中,最多可存放2 048個字節(jié),當(dāng)超過2 048個字節(jié),新數(shù)據(jù)從頭開始存放。
根據(jù)背景說明,可以分析出數(shù)據(jù)解析具體需求有以下幾點:①需要根據(jù)硬件提供的信息計算出每周期接收到的新數(shù)據(jù)個數(shù),并從數(shù)據(jù)緩存中取出新數(shù)據(jù);②需要按照SLIP協(xié)議解析出原始數(shù)據(jù),并將接收方回復(fù)的數(shù)據(jù)按照SLIP協(xié)議處理后發(fā)送;③如果一個周期內(nèi)未接收到一個完整的數(shù)據(jù)幀,需要將不完整的數(shù)據(jù)幀存儲起來,待下一個周期接收到數(shù)據(jù)之后,進(jìn)行數(shù)據(jù)拼接,保證數(shù)據(jù)的完整性;④需要將解析出的原始數(shù)據(jù)整理為上述數(shù)據(jù)幀格式。一個周期內(nèi)如果有多條數(shù)據(jù)幀,則都需要保存起來。
按照上述需求分析,采用模塊化的設(shè)計思想設(shè)計出以下幾個軟件模塊。數(shù)據(jù)解析過程的總流程如圖1所示。
該模塊用來判斷一個周期內(nèi)收取到的數(shù)據(jù)中是否存在不完整的數(shù)據(jù)幀,如果存在,則給出需要保存的數(shù)據(jù)個數(shù),并將需保存的數(shù)據(jù)存儲在全局?jǐn)?shù)組中。算法的關(guān)鍵是如何判斷是否存在不完整的數(shù)據(jù)幀。本文采用的方法如下:判斷接收到的數(shù)據(jù)中最后一個c0是否就是本次接收到的最后一個數(shù)據(jù),如果是,說明本次接收的是一幀完整的數(shù)據(jù);如果最后一個c0不是最后一個數(shù)據(jù),說明本次接收到了不完整的數(shù)據(jù),需要將最后一個c0之后的數(shù)據(jù)保存起來。
需要特別注意的是,主要有以下兩種情況需要單獨處理:①本周期內(nèi)就只收到了一個數(shù)據(jù),而這個數(shù)據(jù)恰好就是c0。如果按照之前的條件判斷,不會把這單獨的c0認(rèn)為是一個不完整的數(shù)據(jù)幀,就不會保存這個c0,導(dǎo)致下一周期收取的數(shù)據(jù)沒有幀頭或幀尾,解析數(shù)據(jù)就會出錯。正確的做法是遇到僅有一個c0的情況,要把這個c0保存起來,在下一周期拼接在收到數(shù)據(jù)之前。②收到數(shù)據(jù)不止一個,且最后一個c0確實也是本周期收到數(shù)據(jù)的最后一個,但這個c0之前還是個c0。如果按照最開始的條件判斷,會誤認(rèn)為這是一幀完整的數(shù)據(jù),而事實上,連著的兩個c0前者是上一幀數(shù)據(jù)的結(jié)尾,后者是下一幀數(shù)據(jù)的開頭。正確的做法是當(dāng)遇到最后一個c0就是最后一個數(shù)據(jù)的情況,還需要額外判斷這個c0之前是否是c0。如果不是,則說明確實是完整的數(shù)據(jù),無需保存數(shù)據(jù);如果是,則說明恰好收到了下一幀數(shù)據(jù)的開頭,需要把這個c0保存起來,在下一周期拼接在收到數(shù)據(jù)之前。經(jīng)過以上分情況處理,就可以保證不會漏掉一幀不完整的數(shù)據(jù)。程序控制流如圖2所示。
圖1 數(shù)據(jù)解析過程
該模塊負(fù)責(zé)判斷何時有新數(shù)據(jù)傳來,并從硬件緩存中取出新數(shù)據(jù)。基本思路是設(shè)計兩個全局變量,一個用來存放當(dāng)前周期的ReciveCοunt值(num),一個用來存放上一個周期的ReciveCοunt值(numοld)。如果當(dāng)前值等于上周期值,則表明無新數(shù)據(jù)傳來,如果當(dāng)前值大于上周期值,則表明有新數(shù)據(jù),需要從硬件內(nèi)存中取出數(shù)據(jù)。新數(shù)據(jù)為從DataRev[numοld]開始到 DataRev[numοld+(num-numοld-1)]的數(shù)據(jù)。上述規(guī)律在ReciveCοunt小于2 048個數(shù)據(jù)時成立;當(dāng)ReciveCοunt大于2 048個數(shù)據(jù)時,新數(shù)據(jù)將保存在緩存區(qū)的開頭,這時情況稍顯復(fù)雜。因為物理內(nèi)存只有2 048 Byte,首先應(yīng)將ReciveCοunt值對2 048取余,這樣就將ReciveCοunt換為2 048以內(nèi)的數(shù)值。則一旦數(shù)據(jù)超過2 048從頭開始時,前述取數(shù)規(guī)律又可以使用。需要分情況討論的就只剩上一周期數(shù)據(jù)(numοld)小于2 048,而當(dāng)前周期數(shù)據(jù)大于2 048的情況了。由于取余處理,當(dāng)前周期的ReciveCοunt實際值為余數(shù)(num),如果一個周期數(shù)據(jù)量不超過2 048個,則一定有num小于numοld。此條件可作為特殊情況的判斷條件,這時新數(shù)據(jù)應(yīng)該是內(nèi)存尾部DataRev[numοld]開始到DataRev[numοld+(2048-numοld-1)]的數(shù)據(jù)與內(nèi)存頭部DataRev[0]開始到DataRev[num-1]的數(shù)據(jù)拼接而成。程序控制流程如圖3所示。
圖2 不完整數(shù)據(jù)保存模塊控制流程圖
在SLIP協(xié)議解析過程中,需要根據(jù)規(guī)則,在數(shù)據(jù)中指定位置插入特定的數(shù)據(jù)。例如“如果幀頭幀尾間出現(xiàn)0xdb,那么就用連續(xù)兩個字符0xdb和0xdd替代”這一條規(guī)則其實就是在0xdb之后插入一個0xdd。為滿足這個需求,設(shè)計了一種鏈表類型結(jié)構(gòu)體,結(jié)構(gòu)體設(shè)計如下:
typedef struct
{
UINT8RevBuf[MAXSIZE];
int charnum;
}SqList;
該結(jié)構(gòu)體由一個字符數(shù)組和一個整型變量組成,字符數(shù)組用來存放消息數(shù)據(jù),整型變量用來存放消息包含的字符個數(shù),MAXSIZE為數(shù)據(jù)最大長度,由實際傳輸數(shù)據(jù)最大長度確定。
鏈表數(shù)據(jù)插入模塊的操作對象就是這種鏈表數(shù)據(jù),輸入一個待操作的鏈表,待插入的數(shù)據(jù),以及插入的位置,鏈表數(shù)據(jù)插入模塊輸出插入數(shù)據(jù)之后的鏈表,并自動更新數(shù)據(jù)長度。程序控制流如圖4所示。
圖3 底層收數(shù)模塊程序流程
在SLIP協(xié)議解析過程中,需要根據(jù)規(guī)則,刪除數(shù)據(jù)中指定位置的數(shù)據(jù)。例如“幀頭幀尾間如果出現(xiàn)0xc0,則以連續(xù)兩個字符0xdb和0xdc取代”這條規(guī)則需要刪掉不是幀頭幀尾的0xc0,然后利用上一小節(jié)設(shè)計的鏈表數(shù)據(jù)插入模塊在刪掉的位置依次插入0xdc,0xdb。刪除指定位置數(shù)據(jù)之后,其后數(shù)據(jù)依次向前移動一位,并讓數(shù)據(jù)長度減1。程序控制流如圖5所示。
協(xié)議解碼模塊的輸入為兩個,一是待解碼的數(shù)據(jù)長度,二是待解碼的數(shù)據(jù)緩存區(qū);輸出為消息幀結(jié)構(gòu)體數(shù)組,返回值為解析出的消息個數(shù)。簡單介紹輸出的消息幀結(jié)構(gòu)體為:
typedef struct
{
UINT16 DataID;
UINT16 DataLen;
UINT8 DataItem[80];
UINT16 CheckSum;
}MessageRev;
該結(jié)構(gòu)體與表1所述數(shù)據(jù)幀結(jié)構(gòu)一致,DataID代表數(shù)據(jù)ID,DataLen代表數(shù)據(jù)長度,DataItem[80]代表有物理意義的數(shù)據(jù)內(nèi)容(假設(shè)數(shù)據(jù)內(nèi)容不會超過80個字符),CheckSum表示校驗和。假設(shè)一個周期內(nèi)傳來的消息不會超過10幀,則需創(chuàng)建一個可存儲10幀消息的結(jié)構(gòu)體數(shù)組MessageRev Message[10]。
圖4 鏈表數(shù)據(jù)插入模塊
圖5 鏈表數(shù)據(jù)刪除模塊
解碼主要分為三個步驟:①從待解碼的數(shù)據(jù)緩存區(qū)中把兩個c0之間的數(shù)據(jù)取出,存放在上述鏈表結(jié)構(gòu)中,同時記錄好數(shù)據(jù)長度,并記錄下解析出的消息幀條數(shù);②如果解析出消息幀條數(shù)大于0,就按照SLIP協(xié)議的規(guī)則,把每一條消息幀中該刪除的刪掉,該插入的插入,得到解析后的數(shù)據(jù)幀;③從解析好的數(shù)據(jù)中提取出MessageRev結(jié)構(gòu)體所需的各變量,按順序存放在Message[10]結(jié)構(gòu)體數(shù)組中,有幾幀消息就占用數(shù)組中的前幾個;返回消息幀條數(shù),這樣就把原始數(shù)據(jù)解析為可以直接用來判斷處理的上層協(xié)議幀格式。
程序流程如圖6所示。
圖6 協(xié)議解碼模塊
待發(fā)送數(shù)據(jù)需要根據(jù)SLIP協(xié)議要求進(jìn)行編碼,調(diào)用ListDelete模塊將需要替換的字符刪除,調(diào)用ListInsert模塊插入規(guī)定的字符。全部替換完畢后,調(diào)用ListInsert模塊在待發(fā)送數(shù)據(jù)開始和結(jié)尾分別插入0xc0,完成數(shù)據(jù)的SLIP協(xié)議編碼。具體流程如圖7所示。
圖7 協(xié)議編碼模塊
首先測試底層接收數(shù)據(jù)功能是否正確,測試設(shè)備通過232串口向本設(shè)備發(fā)送數(shù)據(jù),波特率460.8 Kbps,測試設(shè)備向本設(shè)備發(fā)送1 247 030個字節(jié),在本設(shè)備讀取記錄的接收字節(jié)數(shù)也是1 247 030,說明基本的接收數(shù)據(jù)功能正常。
測試設(shè)備接著向本設(shè)備發(fā)送不同類型的數(shù)據(jù),包括周期性數(shù)據(jù)與事件性數(shù)據(jù),不同消息幀發(fā)送與接收到的幀計數(shù)如表2所示。
表2 消息幀解析實驗結(jié)果
根據(jù)實驗結(jié)果可以看到,周期性消息不同ID的數(shù)據(jù)幀接收到的個數(shù)是一致的,且發(fā)送與接收到的幀計數(shù)相同,說明周期性數(shù)據(jù)幀接收與解析功能正確。在周期性數(shù)據(jù)發(fā)送過程中中隨機(jī)的發(fā)送事件性消息,可以看到發(fā)送幀計數(shù)與接收幀計數(shù)相同,說明隨機(jī)的事件性數(shù)據(jù)幀也可以完整接收并解析出來。具體的幀內(nèi)容,如果消息ID解析正確,則容易根據(jù)項目協(xié)議解析出來,不再贅述。
本文給出了一種基于SLIP協(xié)議的串行數(shù)據(jù)解析方法,對解析過程中的各個模塊進(jìn)行了詳細(xì)描述。特別描述了數(shù)據(jù)幀不完整情況下數(shù)據(jù)的存儲與拼接方法。將各個模塊集成為完整的數(shù)據(jù)解析軟件并經(jīng)過大量數(shù)據(jù)測試,結(jié)果表明數(shù)據(jù)接收無丟數(shù)現(xiàn)象,數(shù)據(jù)幀解析完整,不論周期性數(shù)據(jù)和事件性數(shù)據(jù)均無丟幀現(xiàn)象。本方法可適用于大多數(shù)串行通訊過程,具有適用面廣、解析方法簡單可靠之優(yōu)點。