單片機高效收發(fā)數(shù)據(jù)地一種方法
1. 簡介
串口由于使用簡單,價格低廉,配合RS485芯片可以實現(xiàn)長距離、抗干擾能力強的局域網絡而被廣泛使用。隨著產品功能的增多,需要處理的任務也越來越復雜,系統(tǒng)任務也越來越需要及時響應。絕大多數(shù)的現(xiàn)代單片機(ARM7、Cortex-M3)串口都帶有一定數(shù)量的硬件FIFO,本文將介紹如何使用硬件FIFO來減少接收中斷次數(shù),提高發(fā)送效率。在此之前,先來列舉一下傳統(tǒng)串口數(shù)據(jù)收發(fā)的不足之處:(1)每接收一個字節(jié)數(shù)據(jù),產生一次接收中斷。不能有效的利用串口硬件FIFO,減少中斷次數(shù)。(2)應答數(shù)據(jù)采用等待發(fā)送的方法。由于串行數(shù)據(jù)傳輸?shù)臅r間遠遠跟不上CPU的處理時間,等待串口發(fā)送完當前字節(jié)再發(fā)送下一字節(jié)會造成CPU資源浪費,不利于系統(tǒng)整體響應(在1200bps下,發(fā)送一字節(jié)大約需要10ms,如果一次發(fā)送幾十個字節(jié)數(shù)據(jù),CPU會長時間處于等待狀態(tài))。(3)應答數(shù)據(jù)采用中斷發(fā)送。增加一個中斷源,增加系統(tǒng)的中斷次數(shù),這會影響系統(tǒng)整體穩(wěn)定性(從可靠性角度考慮,中斷事件應越少越好)。(4)針對上述的不足之處,將結合一個常用自定義通訊協(xié)議,提供一個完整的解決方案。2.串口FIFO
串口FIFO可以理解為串口專用的緩存,該緩存采用先進先出方式。數(shù)據(jù)接收FIFO和數(shù)據(jù)發(fā)送FIFO通常是獨立的兩個硬件。串口接收的數(shù)據(jù),先放入接收FIFO中,當FIFO中的數(shù)據(jù)達到觸發(fā)值(通常觸發(fā)值為1、2、4、8、14字節(jié))或者FIFO中的數(shù)據(jù)雖然沒有達到設定值但是一段時間(通常為3.5個字符傳輸時間)沒有再接收到數(shù)據(jù),則通知CPU產生接收中斷;發(fā)送的數(shù)據(jù)要先寫入發(fā)送FIFO,只要發(fā)送FIFO未空,硬件會自動發(fā)送FIFO中的數(shù)據(jù)。寫入發(fā)送FIFO的字節(jié)個數(shù)受FIFO最大深度影響,通常一次寫入最多允許16字節(jié)。上述列舉的數(shù)據(jù)跟具體的硬件有關,CPU類型不同,特性也不盡相同,使用前應參考相應的數(shù)據(jù)手冊。3.數(shù)據(jù)接收與打包
FIFO可以緩存串口接收到的數(shù)據(jù),因此我們可以利用FIFO來減少中斷次數(shù)。以NXP的lpc1778芯片為例,接收FIFO的觸發(fā)級別可以設置為1、2、4、8、14字節(jié),推薦使用8字節(jié)或者14字節(jié),這也是PC串口接收FIFO的默認值。這樣,當接收到大量數(shù)據(jù)時,每8個字節(jié)或者14個字節(jié)才會產生一次中斷(最后一次接收除外),相比接收一個字節(jié)即產生一個中斷,這種方法串口接收中斷次數(shù)大大減少。將接收FIFO設置為8或者14字節(jié)也十分簡單,還是以lpc1778為例,只需要設置UART FIFO控制寄存器UnFCR即可。接收的數(shù)據(jù)要符合通訊協(xié)議規(guī)定,數(shù)據(jù)與協(xié)議是密不可分的。通常我們需要將接收到的數(shù)據(jù)根據(jù)協(xié)議打包成一幀,然后交由上層處理。下面介紹一個自定義的協(xié)議幀格式,并給出一個通用打包成幀的方法。自定義協(xié)議格式如圖3-1所示。- 幀首:通常是3~5個0xFF或者0xEE
- 地址號:要進行通訊的設備的地址編號,1字節(jié)
- 命令號:對應不同的功能,1字節(jié)
- 長度:數(shù)據(jù)區(qū)域的字節(jié)個數(shù),1字節(jié)
- 數(shù)據(jù):與具體的命令號有關,數(shù)據(jù)區(qū)長度可以為0,整個幀的長度不應超過256字節(jié)
- 校驗:異或和校驗(1字節(jié))或者CRC16校驗(2字節(jié)),本例使用CRC16校驗
3.1 定義數(shù)據(jù)結構
typedef?struct?{??
????uint8_t?*?dst_buf;??????????????????//指向接收緩存??
????uint8_t?sfd;????????????????????????//幀首標志,為0xFF或者0xEE??
????uint8_t?sfd_flag;???????????????????//找到幀首,一般是3~5個FF或EE??
????uint8_t?sfd_count;??????????????????//幀首的個數(shù),一般3~5個??
????uint8_t?received_len;???????????????//已經接收的字節(jié)數(shù)??
????uint8_t?find_fram_flag;?????????????//找到完整幀后,置1??
????uint8_t?frame_len;??????????????????//本幀數(shù)據(jù)總長度,這個區(qū)域是可選的??
}find_frame_struct;
3.2 初始化數(shù)據(jù)結構,一般放在串口初始化中
/**?*?@brief????初始化尋找?guī)臄?shù)據(jù)結構?
*?@param????p_fine_frame:指向打包幀數(shù)據(jù)結構體變量?
*?@param????dst_buf:指向幀緩沖區(qū)?
*?@param????sfd:幀首標志,一般為0xFF或者0xEE?
*/??
void?init_find_frame_struct(find_frame_struct?*?p_find_frame,uint8_t?*dst_buf,uint8_t?sfd)??
{??
????p_find_frame->dst_buf=dst_buf;??
????p_find_frame->sfd=sfd;??
????p_find_frame->find_fram_flag=0;??
????p_find_frame->frame_len=10;???????
????p_find_frame->received_len=0;??
????p_find_frame->sfd_count=0;??
????p_find_frame->sfd_flag=0;??
}?
3.3 數(shù)據(jù)打包程序
/**?*?@brief????尋找一幀數(shù)據(jù)??返回處理的數(shù)據(jù)個數(shù)?
*?@param????p_find_frame:指向打包幀數(shù)據(jù)結構體變量?
*?@param????src_buf:指向串口接收的原始數(shù)據(jù)?
*?@param????data_len:src_buf本次串口接收到的原始數(shù)據(jù)個數(shù)?
*?@param????sum_len:幀緩存的最大長度?
*?@return???本次處理的數(shù)據(jù)個數(shù)?
*/??
uint32_t?find_one_frame(find_frame_struct?*?p_find_frame,const?uint8_t?*?src_buf,uint32_t?data_len,uint32_t?sum_len)??
{??
????uint32_t?src_len=0;??
????while(data_len--)??
????{??
????????if(p_find_frame?->sfd_flag==0)????????????????????????
????????{???//沒有找到起始幀首??
????????????if(src_buf[src_len ]==p_find_frame?->sfd)??
????????????{??
????????????????p_find_frame?->dst_buf[p_find_frame?->received_len ]=p_find_frame?->sfd;??
????????????????if( p_find_frame?->sfd_count==5)??????????
????????????????{??
????????????????????p_find_frame?->sfd_flag=1;??
????????????????????p_find_frame?->sfd_count=0;??
????????????????????p_find_frame?->frame_len=10;??
????????????????}??
????????????}??
????????????else??
????????????{??
????????????????p_find_frame?->sfd_count=0;???
????????????????p_find_frame?->received_len=0;???
????????????}??
????????}??
????????else???
????????{???//是否是"長度"字節(jié)??Y->獲取這幀的數(shù)據(jù)長度??
????????????if(7==p_find_frame?->received_len)????????????????
????????????{??
????????????????p_find_frame->frame_len=src_buf[src_len] 5 1 1 1 2;?//幀首 地址號 命令號 數(shù)據(jù)長度 校驗???????
????????????????if(p_find_frame->frame_len>=sum_len)??
????????????????{???//這里處理方法根據(jù)具體應用不一定相同??
????????????????????MY_DEBUGF(SLAVE_DEBUG,("數(shù)據(jù)長度超出緩存!\n"));??
????????????????????p_find_frame->frame_len=?sum_len;???????
????????????????}??
????????????}??
??????????????
????????????p_find_frame?->dst_buf[p_find_frame->received_len ]=src_buf[src_len ];????????????????
????????????if(p_find_frame?->received_len==p_find_frame?->frame_len)??????????????????
????????????{??
????????????????p_find_frame?->received_len=0;??????????????//一幀完成????
????????????????p_find_frame?->sfd_flag=0;??
????????????????p_find_frame?->find_fram_flag=1;???????????????????
????????????????return?src_len;??
????????????}??
????????}??
????}??
????p_find_frame?->find_fram_flag=0;??
????return?src_len;??
}?使用例子:定義數(shù)據(jù)結構體變量:find_frame_struct?slave_find_frame_srt;定義接收數(shù)據(jù)緩沖區(qū):#define?SLAVE_REC_DATA_LEN??128
uint8_t?slave_rec_buf[SLAVE_REC_DATA_LEN];在串口初始化中調用結構體變量初始化函數(shù):init_find_frame_struct(