當(dāng)前位置:首頁 > 公眾號精選 > 嵌入式大雜燴
[導(dǎo)讀]導(dǎo)讀:學(xué)單片機的大概最先、最常寫的通信程序應(yīng)該就是串口程序了,但是如何寫出一個健壯且高效的串口接收程序呢?

導(dǎo)讀:學(xué)單片機的大概最先、最常寫的通信程序應(yīng)該就是串口程序了,但是如何寫出一個健壯且高效的串口接收程序呢?接下來魚鷹將根據(jù)多年的開發(fā)經(jīng)驗教你如何編寫串口接收程序。

本篇文章包含以下內(nèi)容,很長,但干貨滿滿,就看你能吸收多少了:

  1. 傳入?yún)?shù)指針

  2. 互斥鎖釋放順序

  3. 數(shù)據(jù)幀檢查

  4. 串口空閑

  5. 通信吞吐量

內(nèi)容很多,魚鷹慢慢寫,道友您也請慢慢看。


為了更好的理解接下來的知識點,魚鷹將設(shè)計一個串口框架,讓道友心中有一個參考方向。

本篇重點在于解決如何寫一個健壯、高效的串口接收數(shù)據(jù),發(fā)送與接收處理過程略講。

幀格式

先聊聊幀格式,一般來說,一個數(shù)據(jù)幀有以下幾部分內(nèi)容:

幀頭

幀頭用于分辨一個數(shù)據(jù)幀的起始,這個幀頭必須足夠特殊才行,因為它是分辨一個幀的起始,那么什么樣的幀頭是足夠特殊的數(shù)據(jù)呢?保證這個數(shù)據(jù)在一個幀內(nèi)最好只出現(xiàn)一次的數(shù)據(jù),那就是幀頭,比如0x55、0xAA之類的。而且最好有兩個字節(jié)以上,這樣幀頭才更加獨一無二。

但是數(shù)據(jù)域內(nèi)的數(shù)據(jù)你是沒辦法保障不包含和幀頭一樣的數(shù)據(jù)。

那么如果不湊巧,除了幀頭外其他部分也有這樣的兩個字節(jié)的幀頭,那會出現(xiàn)什么問題?

幾乎不會出現(xiàn)問題。因為一般來說數(shù)據(jù)都是一幀一幀發(fā)送的,只要你前面的數(shù)據(jù)幀傳輸正確,那么即使下一幀的數(shù)據(jù)中有和幀頭一樣的數(shù)據(jù)(包括幀頭)也沒有問題,因為幀頭判斷已經(jīng)在開始就判斷成功了,就不會繼續(xù)判斷后面的數(shù)據(jù)是否是幀頭了。

那么為什么說是幾乎,因為如果上一幀數(shù)據(jù)接收錯誤,那么程序必須再找一次幀頭才行(單字節(jié)接收時是如此,采用空閑中斷的話就不需要這么麻煩),這就導(dǎo)致找?guī)^的時候在幀頭數(shù)據(jù)之外尋找了,很可能這些數(shù)據(jù)就有幀頭。

但是即使幀頭數(shù)據(jù)之外的假幀頭真的存在,也沒關(guān)系,還有第二重保障,那就是校驗,即使找到了一個錯誤的幀頭,那么數(shù)據(jù)校驗這一關(guān)也很難過去,所以放寬心。

如果校驗也湊巧通過了,那還有第三重保障:幀尾。應(yīng)該到不了這里吧,畢竟這比中彩票還難。

又要上一幀數(shù)據(jù)接收錯誤,還要當(dāng)前幀除了幀頭之外還有幀頭,另外你還能跳過校驗的檢查(還有功能字、長度信息的檢查),太難了。所以只要通過了這些檢查,你就可以認(rèn)為這個數(shù)據(jù)幀是可用的了。所以一幀數(shù)據(jù)接收錯誤,導(dǎo)致的問題最多只是丟失了這幀數(shù)據(jù),對后續(xù)接收是不會有影響的(前提是你這個接收程序設(shè)計的足夠好),發(fā)送端在發(fā)送超時后再發(fā)送一次即可,所以重發(fā)機制很重要。

事實上,如果你采用串口空閑中斷,幀頭、幀尾都可以不用,但一般來說,幀頭都會保留,幀尾可以不需要,這是為了當(dāng)單片機沒有串口空閑中斷時考慮,當(dāng)然也可能有其他考慮,所以幀頭得保留。

功能字

功能字主要用于說明該數(shù)據(jù)幀的功能,當(dāng)然也可以作為函數(shù)指針的索引,一個索引值代表了一個具體功能,據(jù)此可找到對應(yīng)的功能函數(shù)。

比如,設(shè)計一個函數(shù)指針數(shù)組,通過功能字進(jìn)行索引,進(jìn)而跳轉(zhuǎn)到對應(yīng)的功能函數(shù)中處理。

特別注意的是,設(shè)計功能字的時候,要考慮兼容性,對數(shù)據(jù)幀的功能進(jìn)行劃分,不要想到一個算一個,功能字也不要隨便安排,不然在以后增加數(shù)據(jù)幀的時候會很麻煩。

比如說,只有一個字節(jié)的功能字,前四位作為一個大類,后四位作為大類中具體類。這樣就可以將系統(tǒng)數(shù)據(jù)通信幀分為16個大類,每個大類下有16個可用的具體類,當(dāng)你增加功能字的時候,就可以根據(jù)你的設(shè)計來確定屬于哪個大類了,然后再插入進(jìn)去。這樣在管理、維護(hù)這些通信數(shù)據(jù)時你會發(fā)現(xiàn)很方便。

這個思想其實在ARM內(nèi)核的中斷系統(tǒng)和設(shè)計 uCOS II 任務(wù)優(yōu)先級的時候都有,而魚鷹在設(shè)計項目的通信協(xié)議的時候就是運用了這些思想。

(圖片來源于《權(quán)威指南》)

長度

長度信息也是一個非常關(guān)鍵的數(shù)據(jù),別小看了它,因為它,魚鷹用了將近一個星期的時間才把一個HardFaul問題解決了,雖然這個程序bug不是我寫的(魚鷹一直用的是串口空閑接收方式,這個bug自然而然就跳過了),但確實很容易出錯。

因為它是決定了你這個數(shù)據(jù)域長度的關(guān)鍵信息(一般長度信息代表數(shù)據(jù)域的長度,而不包含其它部分長度),也是這個數(shù)據(jù)幀的長度信息(加上固定字節(jié)長度就是幀長度了),更是接收程序還要接收多少數(shù)據(jù)的關(guān)鍵信息(對于空閑中斷接收方式不算關(guān)鍵,這里的不關(guān)鍵是指不會造成程序異常問題)。

比如說你的程序剛好將幀頭、幀尾、功能字判斷完畢,然后中斷程序因為種種原因?qū)е聸]有及時接收串口數(shù)據(jù),那么你可能得到的就是錯誤的數(shù)據(jù),然后這個錯誤的長度數(shù)據(jù)就可能導(dǎo)致你的棧幀或者全局變量被破壞(單字節(jié)接收情況下就可能出現(xiàn),因為魚鷹碰到過),這是很嚴(yán)重的事情。所以在接收數(shù)據(jù)域的數(shù)據(jù)之前一定一定要判斷這個長度信息(空閑中斷除外)是否合法,不合法的話及時扔掉這幀數(shù)據(jù),開始下一幀的數(shù)據(jù)檢查。

所以為了保證及時接收數(shù)據(jù),最好采用DMA傳輸。

數(shù)據(jù)域

這個沒啥好說的,就是整個幀你真正需要發(fā)送的數(shù)據(jù)。而為了讓你的發(fā)送函數(shù)能接收各種類型的數(shù)據(jù),那么把參數(shù)類型設(shè)置為 void * 會是不錯的選擇。

校驗

一個數(shù)據(jù)在接收過程中可能會被干擾,導(dǎo)致接收到錯誤的數(shù)據(jù),那么如何保證這幀數(shù)據(jù)的完整與準(zhǔn)確性呢,就在校驗這一關(guān)了。

校驗有很多方式,和校驗、CRC校驗等(奇偶校驗是針對一個字節(jié)的,不是數(shù)據(jù)幀)。

和校驗算法簡單,CPU運算量小,累加最后只取最低字節(jié)即可(注意不是高字節(jié),想想為什么),或者保存累加和的變量就是一個字節(jié)空間,這樣就不需要額外操作了。

CRC校驗,這個算法復(fù)雜,理解起來比較困難,但一般來說可以直接拿來用,因為它是對每一位(bit)進(jìn)行校驗,所以糾錯率很高,幾乎不存在發(fā)現(xiàn)不了的數(shù)據(jù)錯誤,但正因為對每一位進(jìn)行檢查,所以CPU運算量較大,但是有的單片機是可以硬件計算CRC校驗值的(比如stm32)。不過現(xiàn)在CPU運算速度都挺快的,軟件運算也是可以接受的。

那么該怎么校驗?zāi)兀渴菑膸^開始到數(shù)據(jù)域部分,還是說直接校驗數(shù)據(jù)部分?其實都可以,區(qū)別就是運算量問題,不過問題不大(最好是從頭開始校驗,以保證整幀數(shù)據(jù)的準(zhǔn)確性)。

幀尾

前面說了,幀尾在空閑中斷中可以不用,RXNE中斷接收時其實也可以不用,當(dāng)然也可以加上,好處就是當(dāng)你用串口助手查看數(shù)據(jù)流時,可以觀察出一幀數(shù)據(jù)是否發(fā)送完整了。

最后再說說為什么在數(shù)據(jù)域前面設(shè)計四個字節(jié)大小,除了協(xié)議本身需要外,還有一個原因就是強制類型轉(zhuǎn)化需要,我們知道,一般來說,賦值時都有字節(jié)對齊的限制(實際上有的CPU可以不對齊進(jìn)行賦值),stm32是32位的,那么四字節(jié)對齊是最合適的,這樣就可以直接將我們收到的數(shù)據(jù)轉(zhuǎn)化為需要的數(shù)據(jù)類型了。

傳輸過程

聊完了幀格式,再從大的方向看串口的傳輸過程:

當(dāng)發(fā)送端發(fā)送第一幀數(shù)據(jù)包時,接收端通過某種方式接收(串口接收非空RXNE中斷、串口空閑IDLE中斷),為了讓串口能夠觸發(fā)空閑中斷,必須在發(fā)送端兩個發(fā)送幀之間插入一段空閑時間(就是在此時間內(nèi)不發(fā)數(shù)據(jù),紅色部分),保證空閑中斷的準(zhǔn)確觸發(fā)。

同理,為了讓發(fā)送端也能正常接收接收端的數(shù)據(jù),也需要控制接收端的發(fā)送,不能在返回一幀數(shù)據(jù)時立馬發(fā)送下一幀數(shù)據(jù),不然觸發(fā)不了發(fā)送端的空閑中斷。

事實上,有些程序員設(shè)計的發(fā)送、接收過程比這個簡單一些。即只有當(dāng)接收端接收到一幀數(shù)據(jù)并返回一幀數(shù)據(jù)之后,發(fā)送端才能繼續(xù)發(fā)送數(shù)據(jù),這樣一來,我們只需要控制好接收端的頻率,就可以控制整個通信過程,也能控制通信頻率。

但為什么還要設(shè)計成第一種傳輸情況呢?這是為了充分利用串口,增大數(shù)據(jù)吞吐率(這個后面再說)。

另外,不知道你是否觀察到圖中的每個數(shù)據(jù)幀占用的時間是不一樣的,這是因為每個數(shù)據(jù)幀不可能都是一樣長的,它們是不定長的數(shù)據(jù)包,所以你的定時不能從發(fā)送開始定時,而是從發(fā)送完成后開始定時控制空閑時間。

軟件設(shè)計

上面所有的內(nèi)容都是設(shè)定一些條件、需求,那么該如何實現(xiàn)軟件設(shè)計呢?畢竟說的再多,如果不能實現(xiàn)這些,又有什么用呢?talk is cheap, show me the code。

下圖設(shè)計了三個數(shù)據(jù)幀:GetVision(),GetSN(),GetMsg()。

GetVision()用于獲取硬件版本號、軟件版本號。

GetSN()用于獲取產(chǎn)品序列號,用于識別唯一設(shè)備。

GetMsg()用于獲取消息,可以獲取各種傳感器數(shù)據(jù),事實上,如果數(shù)據(jù)量多的話,根據(jù)傳感器的不同,會根據(jù)需要設(shè)計各種不同的數(shù)據(jù)幀(功能字不同)。

在軟件設(shè)計上一般都會對這些函數(shù)設(shè)計一個統(tǒng)一的函數(shù)類型,用函數(shù)指針數(shù)組統(tǒng)一管理。

既要統(tǒng)一,又要體現(xiàn)差異性,函數(shù)參數(shù)就顯得很有必要了。

這里設(shè)計了兩個參數(shù),一個是void* (無類型指針),一個是length(長度)。

無類型指針主要是用于傳遞數(shù)據(jù)域的數(shù)據(jù)地址,而數(shù)據(jù)域的數(shù)據(jù)可能是整型、浮點型、結(jié)構(gòu)體、枚舉、聯(lián)合體等,為了保證傳入的各種數(shù)據(jù)類型在不通過強制轉(zhuǎn)化情況下都能兼容,設(shè)計為 void * 就顯得很有必要了。

實際上為了顯得更專業(yè)性,加上 const 修飾會是不錯的選擇,因為這可以保證緩存數(shù)據(jù)不被修改(事實上只能保證不被程序員修改,而不能保證程序本身,這個后面會解釋)。

長度,長度參數(shù)是一個很關(guān)鍵的參數(shù),為了保證長度的準(zhǔn)確性,建議使用sizeof 獲取。

有人覺得sizeof 好像一個函數(shù),會不會導(dǎo)致效率低下啊,畢竟每次通信都要計算一次長度,那你就大特大錯了。事實上,只要你的類型定義定義好了(不管是內(nèi)置的類型定義還是自定義的結(jié)構(gòu)體、枚舉、聯(lián)合體),編譯器都能確定 sizeof 最終的數(shù)據(jù)長度,根本不存在計算過程。

用 sizeof 的兩個好處:

1、可以忽略字節(jié)對齊問題(不同平臺不能忽略,比如window和單片機通信)。因為編譯器為了數(shù)據(jù)讀寫效率更高,一般會對數(shù)據(jù)進(jìn)行地址對齊,這樣一來手工計算一個數(shù)據(jù)類型的長度變得麻煩(當(dāng)然你可以說使用某些手段讓數(shù)據(jù)不進(jìn)行對齊,這個另說),而 sizeof 將智能且準(zhǔn)確計算數(shù)據(jù)大小。

2、當(dāng)你使用 sizeof 時,兼容性更強,也顯得更專業(yè)。程序修修改改很正常,一個數(shù)據(jù)結(jié)構(gòu)改來改去也很正常,特別是開發(fā)初期更是如此。但是不管你怎么改,只要在編譯器看來是固定長度的數(shù)據(jù)類型,那么 sizeof 就能在鏈接程序前計算出來;并且即使你后來加了數(shù)據(jù)不對齊的限制(加了這個限制后,很可能數(shù)據(jù)大小變小),也能準(zhǔn)確計算。別問為什么,就是這么任性。

所以為了減小出錯的可能性、減少勞動量,sizeof 是不錯的選擇。

當(dāng)接收到數(shù)據(jù)地址和長度信息后,就可以進(jìn)行發(fā)送了。

因為只有數(shù)據(jù)域的數(shù)據(jù),為了組成一幀完整的數(shù)據(jù),就必須加入打包過程。加上數(shù)據(jù)幀頭、功能字、數(shù)據(jù)長度、校驗等數(shù)據(jù)。

當(dāng)一幀數(shù)據(jù)打包好之后,就可以進(jìn)行發(fā)送了,發(fā)送可以采用循環(huán)查詢發(fā)送,也可使用發(fā)送空TXE中斷,當(dāng)然還是建議使用DMA發(fā)送,這樣你可以還沒等它發(fā)送完就可處理其它事情了。

以上就是發(fā)送過程,接收過程也是同理,根據(jù)功能字來調(diào)用相應(yīng)的函數(shù)進(jìn)行回復(fù)。

事實上,如果只是數(shù)據(jù)的傳輸過程,完全可以使用一個發(fā)送函數(shù)實現(xiàn)數(shù)據(jù)的特異性傳輸,這樣就可以減少一層數(shù)據(jù)傳遞,但是有些通信幀不只是數(shù)據(jù)的傳輸,可能在接收、發(fā)送時作一些其他處理(比如清除、設(shè)置某些標(biāo)志位),所以需要再增加一層,用于進(jìn)行差異性處理。

以上就是本篇內(nèi)容的基礎(chǔ)內(nèi)容了,你以為快完了?你錯了,現(xiàn)在只是剛開始而已,魚鷹寫本篇筆記的最終目的還在后面。

這只是前菜,正文才剛開始。

串口接收遇到的那些問題

以下內(nèi)容不會用太多的筆墨描述如何寫發(fā)送、接收函數(shù),而是重點關(guān)注串口接收過程中可能遇到的一些問題,如果說描述到了發(fā)送、接收函數(shù),別會錯意,順帶的。

以下大部分問題都是因為采用RXNE(接收不為空)中斷方式導(dǎo)致的問題,只有一個問題是魚鷹從前沒有考慮到,也是IDLE + DMA方式不可忽略的問題。

這就是為什么魚鷹建議采用IDLE + DMA 的原因,不僅是因為效率問題,更因為它能避免很多問題,當(dāng)然水平足夠高的話,采用RXNE也是完全(“完全”就未必,里面有一個問題是RXNE方式難以避免的問題)沒有問題。

事實上,即使魚鷹采用RXNE方式接收數(shù)據(jù),也能避免以下大部分的問題,因為魚鷹的基礎(chǔ)足夠扎實,會在一開始編寫代碼的時候自然而然避免一些問題的出現(xiàn)。

但是看完以下內(nèi)容后,相信各位道友寫出一個高效且健壯的串口接收程序根本不是問題,因為這就是所謂的經(jīng)驗啊。

傳入?yún)?shù)指針

前面魚鷹已經(jīng)提到了需要一個指針作為函數(shù)的參數(shù),這里說說這個指針問題。

我們知道,為了維護(hù)方便,也是為了節(jié)省空間,一般都會將類似的功能整合成一個函數(shù),比如串口經(jīng)常要用的發(fā)送、接收功能,但是所發(fā)送的數(shù)據(jù)內(nèi)存空間可能就處于五湖四海了,他們通過指針來指向?qū)⒁l(fā)送的數(shù)據(jù)。

為了節(jié)省 RAM 空間或者其它不為人知的原因,傳遞給發(fā)送函數(shù)的指針就是實際發(fā)送數(shù)據(jù)的地址,并且在計算校驗值的時候也是直接使用這個地址進(jìn)行校驗計算,然后采用循環(huán)查詢的方式發(fā)送數(shù)據(jù),這樣一來,就不必拷貝一個數(shù)據(jù)的副本進(jìn)行發(fā)送,而是直接從數(shù)據(jù)源的地址進(jìn)行發(fā)送,節(jié)省了部分 RAM 空間。

但是這樣真的好嗎?

你是否考慮過在計算數(shù)據(jù)幀校驗值的時候,數(shù)據(jù)源改變了的問題呢?

比如說你采用和校驗,數(shù)據(jù)一開始是0x55,計算數(shù)據(jù)幀的校驗和值為tx_sum,然后被中斷程序或者DMA修改了這個數(shù)據(jù)源,變成了 0xAA,此時你再使用這個數(shù)據(jù)地址進(jìn)行發(fā)送,接收端接收到了0xAA,接收端計算校驗和的時候是 rx_sum,那么rx_sum 必然不等于 tx_sum,然后接收端就認(rèn)為該數(shù)據(jù)幀是錯誤的,然后丟失這幀數(shù)據(jù),而這種情況是比較少見的,但確實是會偶爾出現(xiàn)接收錯誤的情況(當(dāng)時發(fā)現(xiàn)這個問題時始終不得其解,明明我發(fā)送的是這個校驗值,為什么你計算的校驗值是另一個?開始懷疑是校驗函數(shù)的問題,但其他數(shù)據(jù)幀計算時沒有問題,只有一種數(shù)據(jù)幀會出現(xiàn)問題,然后魚鷹懷疑是數(shù)據(jù)源的問題,是的,魚鷹很快就懷疑數(shù)據(jù)源的問題,但當(dāng)時驗證時,只改了校驗?zāi)遣糠值刂?,發(fā)送時的地址還是使用源地址,導(dǎo)致問題還是沒解決,過了好久之后才發(fā)現(xiàn)這個發(fā)送地址沒改,囧。所以說,即使你的思路是對的,但如果你解決時錯了,問題也很嚴(yán)重)。

如果說接收端(從機)具有重發(fā)機制,那么問題不是很大,丟失一幀數(shù)據(jù)而已,再重發(fā)就是,但事實是,一般串口設(shè)計成主從模式,主機會在沒有接收到從機的應(yīng)答數(shù)據(jù)時會進(jìn)行重發(fā),但是從機一般不會主動重發(fā)數(shù)據(jù)的,它無法判斷主機是否成功接收,而從機一般會在成功發(fā)送完數(shù)據(jù)后開始清除一些標(biāo)志位(比如鍵盤按鍵數(shù)據(jù)清空,不然主機下次獲取按鍵信息時還是同一個按鍵數(shù)據(jù)),事實上這個動作必須在對方成功接收才能進(jìn)行(否則這次按鍵信息就丟失了),從這個角度來看,我們必須設(shè)計一個機制用于判斷主機是否成功接收。

I2C、CAN總線都有應(yīng)答信號,但這是這些是總線自帶的特性,我們不可能在接收到一個字節(jié)后發(fā)送一個應(yīng)答信號給主機,那么是否有其他辦法呢。

人們很容易想到的一個辦法就是在主機收到正確數(shù)據(jù)后,主動發(fā)送一幀專用數(shù)據(jù)幀用于清除這個標(biāo)志(這個幀和普通幀一樣,所以可以確保主機數(shù)據(jù)能準(zhǔn)確送達(dá)從機,因為如果超時沒有送達(dá),會觸發(fā)重發(fā)機制)。這樣只要在獲取完這幀數(shù)據(jù)后,再額外的發(fā)送一幀數(shù)據(jù)用于對方確認(rèn)即可,從機接收到后,即可開始著手清除一些標(biāo)志位。

但這樣會有一個問題,因為這種特殊的需要從機確認(rèn)的數(shù)據(jù)包(其他類型數(shù)據(jù)不需要確認(rèn)是因為如果主機沒有正確收到數(shù)據(jù)還可以繼續(xù)獲取,獲取的數(shù)據(jù)是一樣并沒有關(guān)系,但這種需要從機確認(rèn),一旦從機認(rèn)為發(fā)送成功了,數(shù)據(jù)就被清除了這種情況就需要確認(rèn),典型的就是按鍵信息了),我們需要額外處理并占用發(fā)送帶寬。這是魚鷹不愿忍受的。

那么是否有更好的辦法?

或許我們可以從 USB 協(xié)議中獲得啟發(fā)(這是在寫這篇筆記的時候想到的,當(dāng)時寫按鍵板代碼的時候沒有想到過,但因為當(dāng)時測試時傳輸成功率100%,所以就放棄處理這種情況了)。

USB協(xié)議是典型的主從機制,主機不主動獲取數(shù)據(jù),從機是無法主動發(fā)送數(shù)據(jù)的。那么從機是如何確定對方成功接收數(shù)據(jù)了呢?

一個bit的翻轉(zhuǎn)位。

每當(dāng)主機成功發(fā)送一幀數(shù)據(jù)后再發(fā)送下一幀數(shù)據(jù)時,就會翻轉(zhuǎn)這個位,從機就可以根據(jù)這個位判斷主機是屬于重發(fā)數(shù)據(jù)(重發(fā)數(shù)據(jù)表示主機接收失敗了)還是新數(shù)據(jù)了,這樣從機就能從下一幀數(shù)據(jù)確定上一幀數(shù)據(jù)是否成功發(fā)送了。

而主機發(fā)送的數(shù)據(jù)是由從機發(fā)送應(yīng)答包確定的,和上面的串口協(xié)議類似,所以這個方向的數(shù)據(jù)是沒有問題的。

那么我們該如何重新設(shè)計這個協(xié)議呢?可以盡可能的不改變原來協(xié)議的情況下實現(xiàn)嗎?

或許可以從功能字出發(fā)。

為了保證對功能字的原有定義保持基本不變,使用最高 bit 作為這個特殊的位,這個 bit 開始是 0,之后主機每接收一個從機應(yīng)答數(shù)據(jù)就進(jìn)行翻轉(zhuǎn),如果因為沒有接收到從機的應(yīng)答數(shù)據(jù),就會使用相同的翻轉(zhuǎn)位重復(fù)發(fā)送;而從機也根據(jù)這個bit來確定自己的上一幀數(shù)據(jù)對方是否接收(對比上一幀數(shù)據(jù)的翻轉(zhuǎn)位),如果主機沒有成功接收,就不清除標(biāo)志位(之后主機會重發(fā)數(shù)據(jù)再次獲?。駝t清除標(biāo)志位,。

因為是魚鷹剛想到的,就不多說了,僅提供一個思路。

現(xiàn)在回到指針那塊的問題。

現(xiàn)在已經(jīng)知道,如果你在計算校驗和與發(fā)送的過程中出現(xiàn)源數(shù)據(jù)改變的情況,就會導(dǎo)致數(shù)據(jù)幀校驗失敗,那么有什么解決辦法?

如果說你堅持使用查詢方式發(fā)送來節(jié)省部分空間,那么只要在計算校驗值之前拷貝一份源數(shù)據(jù),然后用這份數(shù)據(jù)計算并發(fā)送即可。

另一種方法就是,直接把整幀數(shù)據(jù)拷貝到一個數(shù)據(jù)緩存中,使用DMA發(fā)送。

現(xiàn)在還有一個問題,那就是如果我想發(fā)送一個數(shù)據(jù)域為空的數(shù)據(jù)該怎么發(fā)送?

一般來說,在使用指針的時候,不會使用 NULL 空指針,但是在數(shù)據(jù)為空的情況下,就需要使用 NULL 指針了,并長度設(shè)置為 0,這個時候在檢查指針的時候,不能看到空指針就退出函數(shù),還要判斷長度信息,當(dāng)長度為0時在打包時就不拷貝源數(shù)據(jù),但最終還是要發(fā)送數(shù)據(jù)幀的(當(dāng)時別人寫的代碼將指針和長度判斷同時放到了 for 循環(huán)的條件里面,魚鷹覺得效率太低,導(dǎo)致修改代碼是沒考慮指針為空的情況,所以導(dǎo)致了一個小bug)。

互斥鎖釋放順序

現(xiàn)在考慮第二個問題:互斥鎖釋放順序問題。

如果沒有采用隊列方式接收數(shù)據(jù),而是主機發(fā)送完成后等待接收從機數(shù)據(jù)后再發(fā)送下一幀數(shù)據(jù),那么該如何處理互斥鎖的問題?

我們知道為了保證數(shù)據(jù)的同步,保證在接收到一幀數(shù)據(jù)進(jìn)行處理時,不能被新的數(shù)據(jù)幀沖掉,這時就要加入一個互斥鎖,表示我正在處理數(shù)據(jù),下面的數(shù)據(jù)我接收不了,這樣就能保證你正在處理的數(shù)據(jù)不會被新來的數(shù)據(jù)修改掉,從而進(jìn)行正確的處理。

那么這個標(biāo)志位(互斥鎖)該什么時候清掉(釋放掉)呢?

一般來說標(biāo)志位,一般越早清掉越好,比如外部中斷標(biāo)志位,進(jìn)入中斷后,一般首先會清理標(biāo)志位,這樣即使你正在處理本次中斷的程序,那么即使這時再來了中斷,也不會丟失中斷信息(有懸起標(biāo)志位),這樣就可以在處理完這次中斷后,立馬進(jìn)行下一次中斷的處理了(前提是優(yōu)先級足夠高)。

但是如果你清理太早或者清理太晚會怎樣?

比如說你在接收到一幀數(shù)據(jù)后(數(shù)據(jù)幀所有檢查完成),開始設(shè)置標(biāo)志位,當(dāng)主程序查詢到這個標(biāo)志時(一般數(shù)據(jù)處理不會放在中斷中),如果馬上開始清除這個標(biāo)志……嗯,一般來說不會有問題。

那么什么時候會出現(xiàn)問題?當(dāng)你的主程序查詢到這這個標(biāo)志時開始清除標(biāo)志,然后處理、返回數(shù)據(jù)給主機,如果此時主機超時重發(fā)數(shù)據(jù)時,,因為這個時候你雖然在處理數(shù)據(jù),但是因為你的標(biāo)志位已經(jīng)被清除了,所以接收程序就會開始往接收緩存區(qū)存數(shù)據(jù)了,當(dāng)你存完之后再回到數(shù)據(jù)處理那里,你的緩存區(qū)可能就不是你想要的數(shù)據(jù)了。

可能你會說,既然是重發(fā),那么數(shù)據(jù)應(yīng)該是相同的吧?好吧,你贏了,魚鷹編不下去了,這種情況(有重發(fā)機制)下清理太早好像是不會出現(xiàn)問題,但你怎么知道對方是采用副本進(jìn)行重發(fā)數(shù)據(jù)的呢,如果重發(fā)時它又從源數(shù)據(jù)中拷貝后再進(jìn)行重發(fā)會出現(xiàn)什么問題?比如時間信息,開始第一幀數(shù)據(jù)是11:59,CPU剛把11拷貝到用戶空間,被串口中斷程序打斷,導(dǎo)致下一幀接收的數(shù)據(jù)是12:00,此時回到主程序繼續(xù)拷貝,拷貝00,數(shù)據(jù)的完整性被破壞,這樣導(dǎo)致的結(jié)果就是11:00,而實際上時間是12:00,這就是你打斷數(shù)據(jù)處理過程的后果。

現(xiàn)在再說說清理太晚會怎么樣。

當(dāng)你的主程序查詢到這個標(biāo)志后,暫時不清除,而是等到從機發(fā)送完應(yīng)答數(shù)據(jù)之后再清除標(biāo)志,此時因為從機采用查詢方式(查詢方式表明從機發(fā)送完最后一個字節(jié)后后開始清除標(biāo)志位,也就代表了主機就差最后一個字節(jié)沒有接收了,這樣發(fā)送和清除之間間隔時間較短,而采用 DMA 方式的話,發(fā)送和清除的間隔時間更短,因為可能DMA還沒開始發(fā)送第一幀數(shù)據(jù),清除工作就已經(jīng)完成了),或者因為其他原因(比如中斷處理)導(dǎo)致發(fā)送和清除之間的時間很長這種特殊情況,這樣可能主機已經(jīng)開始下發(fā)下一幀數(shù)據(jù)了,但是因為此時標(biāo)志還沒有清除,不能接收數(shù)據(jù),所以主機這一幀數(shù)據(jù)就這樣丟失了。

那么這個清除標(biāo)志位最合適的時機是什么時候?

你鎖定資源利用完的時候。

現(xiàn)在來看看,這個互斥鎖鎖定的是什么資源?對,就是接收緩存,那么接收緩存什么時候用完?當(dāng)然是在數(shù)據(jù)處理完成之時。也就是說在數(shù)據(jù)處理完之前、發(fā)送數(shù)據(jù)之前清除最合適。

這樣就不會因為處理其他事情而導(dǎo)致清除操作過晚而丟失下一幀數(shù)據(jù)了,因為此時主機還沒收到從機上傳的數(shù)據(jù),也就不會馬上開始下一幀數(shù)據(jù)的傳輸了。

說到清除,你覺得,需要把整個緩沖區(qū)進(jìn)行清零操作嗎?這個問題在以往的文章解釋過,就看你是否看完了。

數(shù)據(jù)幀檢查

你是否會對接收的數(shù)據(jù)進(jìn)行檢查?如果不進(jìn)行檢查會發(fā)生什么?

我們知道一幀數(shù)據(jù)中,每個部分都有各自的含義,甚至有些部分可能在某些數(shù)據(jù)幀中不存在,比如數(shù)據(jù)域部分,我們需要根據(jù)長度信息來判斷數(shù)據(jù)域部分是否存在。

但是你能保證你所接收的數(shù)據(jù)都是準(zhǔn)確的嗎?你能確保在工作環(huán)境下不會因為各種干擾導(dǎo)致數(shù)據(jù)長度信息由0x05變成0x85(最高位翻轉(zhuǎn))嗎?,如果出現(xiàn)了會導(dǎo)致什么后果?

假設(shè)你采用RXNE中斷方式來一個、一個字節(jié)的接收數(shù)據(jù),分析如下:

因為是單字節(jié)接收數(shù)據(jù),所以你需要把所有接收的數(shù)據(jù)當(dāng)成數(shù)據(jù)流,根據(jù)幀頭信息來確定幀的開始,一旦確定幀頭信息之后,你就可以根據(jù)接下來的一系列數(shù)據(jù)保證一幀數(shù)據(jù)的結(jié)束,同時開始新幀的接收……

初看這個接收流程沒有問題,但是真的如此嗎?

但是就像前面所說,你能保證你的數(shù)據(jù)沒有問題嗎?如果說你接收到一個長度信息,本來是0x05,但是最終接收的數(shù)據(jù)是0x85,這就意味著你接下來的數(shù)據(jù)域的長度是0x85,根據(jù)你的接收流程,你需要再接收0x85個字節(jié)之后,才能判斷校驗字節(jié)是否正確。

可能你會說,雖然你的長度信息由0x05變成了0x85,之后接收校驗過程肯定是失敗的,那么這幀數(shù)據(jù)就會被接收程序丟棄,從而導(dǎo)致接收程序進(jìn)入重新尋找?guī)^的流程,這個過程不是挺正常的嗎?按理說上述異常情況是能被接收流程處理掉的。

那么首先確認(rèn)一點,上述異常能被接收流程處理嗎?

答案是能!

既然上述異常是能被接收狀態(tài)機處理的,那么還會有什么問題?

問題就出在這個錯誤數(shù)據(jù)本身!

因為你是根據(jù)錯誤數(shù)據(jù)來決定接下來需要接收多少數(shù)據(jù),而一般來說,接收緩存大小設(shè)置為最大幀的長度,那么就出現(xiàn)一個問題,你的緩存夠你接收0x85個字節(jié)嗎?

如果說你開辟的接收緩存空間很大,足夠接收這么多數(shù)據(jù),那么就算遇上以上情況,也是沒有任何問題,但是萬一你比較節(jié)省資源,緩存不夠大會出現(xiàn)什么情況?

這就涉及到內(nèi)存分配問題:

你的串口緩存一般在 Data區(qū)域,一旦你接收的數(shù)據(jù)超出了你開辟的空間,那么必然導(dǎo)致緩存空間溢出!

那么緩存空間溢出會導(dǎo)致什么危害?

我們通過上圖可以知道,一旦緩存溢出,必然導(dǎo)致該緩存周圍的數(shù)據(jù)出現(xiàn)異常(數(shù)據(jù)被篡改),如果你的其它代碼剛好需要這個數(shù)據(jù)作為重要參考,而你在使用的時候又沒有對這個數(shù)據(jù)的有效性進(jìn)行檢查,那么可能導(dǎo)致另一個災(zāi)難性后果,而這個后果又導(dǎo)致了其他后果,從而導(dǎo)致雪崩效應(yīng)。

而你修復(fù)這個bug時,你以為修復(fù)了,但你只修復(fù)了表面,真正內(nèi)在bug還存在!

所以,千萬別太相信內(nèi)存中的數(shù)據(jù),每一個數(shù)據(jù)的輸入都要進(jìn)行嚴(yán)格檢查,這個數(shù)據(jù)可以錯誤,但是不能導(dǎo)致程序崩潰!

所以千萬別寫能篡改別人數(shù)據(jù)的代碼,這是很危險的事情,也是很難解決的bug,因為你不知道它會在什么時候篡改哪里的數(shù)據(jù)!

再假如你的接收緩存放在棧中了呢(稍微有C語言常識的程序員都不會把串口接收緩存放在棧中,但魚鷹偏偏遇到過這種代碼,而為了解決這個bug整整花了一星期,這還是在bug復(fù)現(xiàn)率高的情況下)?

根據(jù)前面的圖可知,棧一般存放在高地址,并且一般棧生長方向為向低地址生長。如果出現(xiàn)上述情況(接收的數(shù)據(jù)大于開辟的棧緩存空間)會發(fā)生什么?

棧幀被破壞!

灰色部分因為接收的數(shù)據(jù)太多,導(dǎo)致原本存在的棧數(shù)據(jù)被串口的接收的數(shù)據(jù)修改了(注意篡改的數(shù)據(jù)可能不是連續(xù)的,因為每一次進(jìn)入時,開辟的那部分??臻g可能都不在同一個地址),假如這個數(shù)據(jù)是保存返回寄存器LR的,那么必然導(dǎo)致返回錯誤,極可能觸發(fā)HardFault中斷!

那么有什么辦法解決棧被破壞的問題?

最有效的方式魚鷹覺得是使用ITM,如果無法在線調(diào)試,可以嘗試DMA循環(huán)傳輸PC指針值(但是如何得到這個值?畢竟這個寄存器本身是沒有地址概念的)到一塊內(nèi)存中,這樣就可以得到最后正常執(zhí)行的代碼地址,從而定位錯誤代碼的位置。

如果單片機不支持這些功能呢?魚鷹現(xiàn)有的知識體系好像無法解決,只能佛系調(diào)bug了(看和bug之間的緣分),囧。

前面說了由于外部工作環(huán)境導(dǎo)致數(shù)據(jù)長度信息錯誤從而出現(xiàn)數(shù)組溢出這種情況,如果說你保證工作環(huán)境非常好,不可能出現(xiàn)這種干擾,是否還會出現(xiàn)問題?

當(dāng)然會!

前面分析了外在原因,現(xiàn)在分析內(nèi)在原因,你的接收程序能保證及時接收發(fā)送端發(fā)送過來的數(shù)據(jù)嗎?如果不及時接收數(shù)據(jù)會出現(xiàn)什么問題?

我們知道,一個系統(tǒng)一般都有很多中斷需要處理,如果說你的接收程序的中斷優(yōu)先級不是最高的,那么很可能出現(xiàn)接收程序無法及時接收的情況,即RXNE中斷來臨時,因更高優(yōu)先級中斷需要處理,而且處理時間較長,那么就會出現(xiàn)當(dāng)前接收的字節(jié)因為沒有接收完成而被后續(xù)的數(shù)據(jù)沖掉,即出現(xiàn)ORE(溢出錯誤)。

這樣會導(dǎo)致什么問題?

數(shù)據(jù)域信息(也可能是校驗值等數(shù)據(jù))當(dāng)成了長度信息(為什么只討論長度,而不討論功能字之類的數(shù)據(jù),難道他們不會出現(xiàn)ORE的情況嗎?),這樣一來,如果這個數(shù)據(jù)很大,接收程序就會以為接下來還需要接收很多數(shù)據(jù)才能完成一幀的接收,導(dǎo)致后果和前面分析的數(shù)據(jù)干擾一樣嚴(yán)重。

那么采用RXNE接收方式時該怎么解決這種問題?

檢查長度信息的合理性,只要長度信息不會導(dǎo)致緩存溢出即可。

但是上面的解決方案會導(dǎo)致什么問題?

因為你的程序設(shè)計問題(采用RXNE接收導(dǎo)致不能及時處理),使得原本能接收的數(shù)據(jù)無法及時處理(DMA可以及時處理),最終使得當(dāng)前這一幀數(shù)據(jù)無法正常接收(如果錯誤的長度信息夠大的話,還有可能接下來很多幀數(shù)據(jù)都無法接收),這你能接受嗎?

但是采用DMA為什么就不會有上述問題,除了DMA能自動接收數(shù)據(jù)提高效率之外,還有一點就是它不根據(jù)接收的數(shù)據(jù)來判斷接下來還需要接收多少數(shù)據(jù),而是根據(jù)設(shè)定的接收數(shù)據(jù)長度來接收的(如果加入IDLE中斷,可以提前結(jié)束接收工作),這就避免了上述的緩存溢出接收不及時問題。

最后再分析上述接收的另一個問題,那就是一幀數(shù)據(jù)中可能出現(xiàn)沒有數(shù)據(jù)域的情況,這種情況該怎么處理?

只要根據(jù)長度信息分開處理即可。如果不對沒有數(shù)據(jù)域的情況分開處理,那么你接收的下一個數(shù)據(jù)直接就是校驗值,而你的接收流程卻認(rèn)為這是數(shù)據(jù)域的數(shù)據(jù),必然導(dǎo)致校驗失敗。

現(xiàn)在總結(jié)使用RXNE方式接收的幾個問題:

1、緩存溢出。

緩存溢出有兩種可能,第一種就是環(huán)境干擾導(dǎo)致長度信息出錯,從而出現(xiàn)緩存溢出情況;第二種情況就是因為接收不及時,導(dǎo)致數(shù)據(jù)錯位,如果剛好是長度信息錯誤,并且這個長度信息太大,而你的代碼未對長度進(jìn)行檢查,那么也會出現(xiàn)緩存溢出bug,而這種bug一旦出現(xiàn),很難發(fā)現(xiàn)。所以在代碼中對數(shù)據(jù)的合理性檢查是非常有必要的一件事。

2、中斷及時處理。

如果中斷不及時處理,會導(dǎo)致數(shù)據(jù)錯位,輕則丟失至少一幀數(shù)據(jù),重則緩存溢出!

3、狀態(tài)機是否需要接收數(shù)據(jù)部分。

由于數(shù)據(jù)幀有可能沒有數(shù)據(jù)域的情況,所以必須區(qū)別處理,保證代碼接收的準(zhǔn)確性,否則有可能把校驗值當(dāng)成數(shù)據(jù)了,這樣必然無法通過校驗,這一幀數(shù)據(jù)必然會丟失!

串口空閑

前面一直提到串口空閑,也大概明白串口的作用,但是一些細(xì)節(jié)問題還是需要好好說一下的。

第一個問題,如何清除串口空閑中斷標(biāo)志位?

很多人會使用USART_ClearFlag標(biāo)準(zhǔn)庫函數(shù)進(jìn)行清除,但是當(dāng)你跳轉(zhuǎn)到該函數(shù)原型時,你會看到如下說明:

你會看到很多標(biāo)志位是無法通過該函數(shù)清除的。

那么該如何清除IDLE標(biāo)志呢?其實上面的注釋已經(jīng)進(jìn)行了說明。

PE、FE、NE、ORE、IDLE標(biāo)志位的清除是通過一個軟件序列進(jìn)行清除的:首先通過USART_GetFlagStatus讀取USART_SR寄存器的值,然后通過USART_ReceiveData函數(shù)讀取USART_DR的值即可。

那么這里就有一個問題,是否這些標(biāo)志問題的清除都要單獨編寫清除序列呢?

答案是否定的。

因為這些標(biāo)志位都是由同一種序列進(jìn)行清除的,所以只要一個清除序列就會把所有的標(biāo)志位都進(jìn)行清除了(同樣一旦執(zhí)行了這個序列,也就意味著你無法再通過USART_SR寄存器獲得標(biāo)志位了)。

為了保證獲取標(biāo)志位,我們可以在清除序列之前把USART_SR寄存器的值保存到副本中,然后再讀取USART_DR寄存器的值保存到副本來實現(xiàn)清除功能,注意該序列應(yīng)該無條件執(zhí)行(不在某個判斷語句中)。這樣后續(xù)我們就可以使用這個 USART_SR 的副本判斷哪一個標(biāo)志置位了,同樣也可以使用 USART_DR 的副本獲取串口數(shù)據(jù),而為了實現(xiàn)以上效果,USART_GetFlagStatus這個函數(shù)就不合適了,只能直接操作寄存器去實現(xiàn)。

第二個問題,在線調(diào)試時對空閑中斷會有影響嗎?

我們知道,KEIL能夠?qū)⒁粋€結(jié)構(gòu)體的數(shù)據(jù)全部讀取出來,而庫函數(shù)將串口模塊的所有寄存器都封裝在一個結(jié)構(gòu)體中,這樣就會出現(xiàn)一個問題,如果你的窗口是實時刷新的,當(dāng)你使用KEIL讀取串口模塊寄存器的時候(不管是使用peripheral窗口還是Watch窗口),就會出現(xiàn)先讀取SR再讀取DR的情況, 這樣就有可能出現(xiàn)KEIL和單片機CPU讀取這兩個寄存器沖突的情況。

如果全速運行時,KEIL 先執(zhí)行了這個序列(通過調(diào)試器讀取這兩個寄存器的值),單片機CPU再讀取SR寄存值,必然是無法讀取到正確標(biāo)志位的,因為這些標(biāo)志位已經(jīng)被KEIL的讀取序列清除了(這個情況魚鷹確實碰到過,當(dāng)時明明下發(fā)了數(shù)據(jù),但是單片機無法獲取標(biāo)志位),所以在調(diào)試串口時,注意不要讓 KEIL 去讀取這些寄存器(即關(guān)閉這些窗口,只有在必須的情況下才開啟),防止出現(xiàn)莫名其妙的情況。

第三個問題,空閑中斷能準(zhǔn)確觸發(fā)嗎?

如果從接收端考慮的話,如果觸發(fā)了空閑中斷,那么必然滿足了條件才觸發(fā)的,而不是意外觸發(fā)的(嗯,我們要相信STM32),但從發(fā)送端考慮的話,有可能出現(xiàn)一幀數(shù)據(jù)斷續(xù)發(fā)送,導(dǎo)致一幀數(shù)據(jù)觸發(fā)多次空閑中斷,所以如果是簡單的DMA+空閑中斷方式接收是很有問題的(空閑出現(xiàn)就認(rèn)為一幀結(jié)束了,就會把一幀數(shù)據(jù)當(dāng)成兩幀處理,這樣肯定無法通過數(shù)據(jù)檢查的)。

那么先來分析為什么會出現(xiàn)一幀數(shù)據(jù)多次觸發(fā)空閑中斷情況。我們知道linux、windows系統(tǒng)并不是實時系統(tǒng),當(dāng)應(yīng)用程序需要發(fā)送一幀數(shù)據(jù)時,可能并沒有連續(xù)發(fā)送,而是發(fā)送完一個字節(jié)后去處理其他事情后才發(fā)送下一個字節(jié),這樣一來,如果耽誤的時間夠長,就會觸發(fā)串口的空閑中斷,從而一幀數(shù)據(jù)當(dāng)成兩幀處理了。

那有什么方法可以解決呢?魚鷹提供兩種解決思路。

第一種,使用兩個緩存空間,一個緩存空間專門用于接收串口數(shù)據(jù),將接收到的數(shù)據(jù)存放到另一個緩存,這個緩存采用字節(jié)隊列的方式進(jìn)行管理,應(yīng)用程序從緩存隊列中一個字節(jié)一個字節(jié)的取出數(shù)據(jù)進(jìn)行處理(注意檢查數(shù)據(jù)有效性),這樣就能保證及時處理。但是因為空閑中斷不再可靠,所以空閑中斷不再作為判斷一幀數(shù)據(jù)結(jié)束的依據(jù)(根據(jù)長度信息判斷),而是只在空閑中斷中將已接收數(shù)據(jù)復(fù)制到字節(jié)隊列緩存中,這樣就可以處理意外的空閑中斷。

第二種,還是一個緩存空間,還是DMA+空閑方式處理,但是需要增加額外的條件。就是當(dāng)進(jìn)入空閑中斷后,不再直接處理,而是獲取當(dāng)前接收時刻,然后在處理數(shù)據(jù)的時候根據(jù)這個時刻來判斷是否達(dá)到足夠的空閑時間,只有在進(jìn)入空閑中斷后并達(dá)到一定延時之后才認(rèn)為一幀數(shù)據(jù)結(jié)束了,這樣可以避免一些非常短的空閑時間(魚鷹公眾號提供過的代碼使用這種方式)。

以上問題是就是魚鷹以前使用空閑中斷從未考慮的問題,魚鷹并不知道使用空閑中斷還可能出現(xiàn)誤觸發(fā)的情況,但是既然知道了,就要想辦法解決。但是為什么以前使用空閑中斷時沒有出現(xiàn)通信問題呢?

事實上不是沒有問題,而是有可能把分散的一幀數(shù)據(jù)的兩部分直接丟棄了而已,因為有重發(fā)機制,所以即使丟棄一幀數(shù)據(jù),也能通信正常,而且這種一幀數(shù)據(jù)分散成兩部分的概率還是挺低的,ubuntu(linux系統(tǒng))下大概千分之三左右的樣子。

第四個問題,如果單片機沒有空閑中斷又該如何做?

當(dāng)我們使用 RXNE 的同時其實我們也可以使用空閑中斷,這樣也能確定一幀數(shù)據(jù)的結(jié)束(但是要注意前面的誤觸發(fā)問題)。但是如果有些低端單片機(如 51 )沒有空閑中斷又該怎么辦?

其實我們可以從 stm32 的空閑中斷得到相應(yīng)的啟發(fā)。

所謂空閑中斷,就是當(dāng)串口接收到數(shù)據(jù)后,在應(yīng)該接收數(shù)據(jù)的時刻,發(fā)送方并沒有發(fā)送數(shù)據(jù),所以串口模塊置位空閑標(biāo)志位,從而引起空閑中斷。

那么我們是否可以軟件模擬串口模塊的這個功能,從而確定一幀數(shù)據(jù)的結(jié)束呢?

答案是肯定的(前提是每一幀數(shù)據(jù)之間有空閑時間)。

我們可以使用一個定時器,定時器向上計數(shù)。當(dāng)接收到一個字節(jié)數(shù)據(jù)后,初始化計數(shù)器并啟動定時器,這樣一旦有一段時間沒有接收到串口數(shù)據(jù)(也就不再初始化計數(shù)器),那么定時器溢出,進(jìn)入溢出中斷,而這個溢出中斷就類似于串口的空閑中斷(在溢出中斷中關(guān)閉定時器以達(dá)到清除空閑中斷標(biāo)志的作用),這樣就達(dá)到了串口空閑中斷的效果(和前面問題的第二種解決方案類似)。

通信吞吐量

在以上分析過程中,都是采用主機發(fā)送,從機接收后再回復(fù)主機的方式進(jìn)行通信,雖然通信正常,但實際上效率比較低下,單位時間傳輸?shù)臄?shù)據(jù)量較少,如下圖所示:

紅色部分就是必要的空閑時間,可以看到左右兩張圖的通信頻率是有差異的,右圖中從機必須等待前一幀數(shù)據(jù)發(fā)送完畢才能處理數(shù)據(jù),而左圖可以在接收當(dāng)前幀時處理上一幀數(shù)據(jù),類似CPU的指令執(zhí)行流水線。

(圖片來源于《權(quán)威指南》)

我們也可以將串口接收分為二級流水線:接收、處理,如此一來,我們最少需要兩個緩存空間,當(dāng)一個緩存在接收時,另一個緩存就進(jìn)行數(shù)據(jù)處理。發(fā)送端可能不等接收端發(fā)送完應(yīng)答數(shù)據(jù),它就已經(jīng)開始發(fā)送下一幀數(shù)據(jù)了,只要相鄰兩幀數(shù)據(jù)保證一定發(fā)送間隔,就能正常觸發(fā)中斷。

同理,因為接收端也不再慢悠悠的等待接收數(shù)據(jù),而是可能有好幾幀數(shù)據(jù)等著它處理,所以為了確保發(fā)送端能正常觸發(fā)空閑中斷,也需要控制發(fā)送間隔。

為了最大程度利用串口,我們可以使用隊列管理很多緩存空間(當(dāng)只有兩個緩存時,可以直接使用異或運算進(jìn)行緩存切換),比如 uCOS II 中我們可以利用系統(tǒng)的內(nèi)存管理服務(wù)和隊列服務(wù)實現(xiàn)有效管理,并且當(dāng)有非常緊急的通信任務(wù)時,還可以插入到隊頭優(yōu)先處理。

但是增大吞吐量時,比如對重發(fā)機制和從機數(shù)據(jù)的確認(rèn)有一定影響,需要考慮清楚。

對于如何提高通信量,魚鷹經(jīng)驗不多,就不多說了(或許可以從網(wǎng)絡(luò)通信過程中得到答案)。

如果要用一句話總結(jié)本篇筆記內(nèi)容,那就是使用 空閑中斷+DMA+隊列+內(nèi)存管理+定時控制 方式接收串口數(shù)據(jù)會是不錯的選擇。



免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉